@hienlh/ppm 0.20.3 → 0.20.5

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 (85) hide show
  1. package/.release-bin/zip +15 -0
  2. package/.release-node-shim.sh +11 -0
  3. package/CHANGELOG.md +13 -0
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +4 -4
  6. package/assets/skills/ppm/references/http-api.md +1 -1
  7. package/dist/web/assets/{accounts-settings-section-DzDmIsPe.js → accounts-settings-section-Cmmozwer.js} +1 -1
  8. package/dist/web/assets/{ai-resource-editor-D7C9wmwA.js → ai-resource-editor-BlrWV3xU.js} +1 -1
  9. package/dist/web/assets/{ai-settings-section-DZuwllLy.js → ai-settings-section-PaEh_o0w.js} +1 -1
  10. package/dist/web/assets/{ai-settings-section-BW5a07-Z.js → ai-settings-section-dDsgddSt.js} +1 -1
  11. package/dist/web/assets/{appearance-settings-section-RfH2N1rj.js → appearance-settings-section-DRxziHFt.js} +1 -1
  12. package/dist/web/assets/{chat-tab-Bz0pk0_E.js → chat-tab-MNuc18Os.js} +4 -4
  13. package/dist/web/assets/{code-editor-C4o1SyuO.js → code-editor-DYkmDRRF.js} +2 -2
  14. package/dist/web/assets/{conflict-editor-Dk65mBIe.js → conflict-editor-D9_sr59D.js} +1 -1
  15. package/dist/web/assets/{csv-preview-DENi3CFz.js → csv-preview-BMC8jnpF.js} +1 -1
  16. package/dist/web/assets/{database-viewer-BiBC7ulB.js → database-viewer-Box3z5-Q.js} +1 -1
  17. package/dist/web/assets/{diff-viewer-CSvZJHak.js → diff-viewer-B4PlZ1Dt.js} +1 -1
  18. package/dist/web/assets/{explorer-body-QXS0h4AF.js → explorer-body-SvWDS84f.js} +1 -1
  19. package/dist/web/assets/{explorer-window-content-CQgqoRsj.js → explorer-window-content-DBHBTA7B.js} +1 -1
  20. package/dist/web/assets/{extension-manager-section-CyC7wX35.js → extension-manager-section-BSt5I_H8.js} +1 -1
  21. package/dist/web/assets/{extension-webview-Da8SIlRa.js → extension-webview-DBGMUU97.js} +1 -1
  22. package/dist/web/assets/{files-settings-section-D8cebM51.js → files-settings-section-0dwYLZou.js} +1 -1
  23. package/dist/web/assets/{general-settings-section-1HVTvlIw.js → general-settings-section-BgLy_6yj.js} +1 -1
  24. package/dist/web/assets/{git-log-panel-C7a4rp8f.js → git-log-panel-BkgmcWk1.js} +1 -1
  25. package/dist/web/assets/{glide-data-grid-BT9p_4AN.js → glide-data-grid-CZVu-zCw.js} +1 -1
  26. package/dist/web/assets/{group-chat-tab-BUHGnUyW.js → group-chat-tab-Cy9zIynj.js} +1 -1
  27. package/dist/web/assets/{index-kGmcC9cb.js → index-DYGLdCpe.js} +4 -4
  28. package/dist/web/assets/{jira-watcher-section-BxzwW4uQ.js → jira-watcher-section-DRQx632s.js} +1 -1
  29. package/dist/web/assets/{keybindings-store-DjJum4OS.js → keybindings-store-C2HZ1mT4.js} +1 -1
  30. package/dist/web/assets/{keyboard-shortcuts-section-DDKG3BU1.js → keyboard-shortcuts-section-WiZSZ-Wr.js} +1 -1
  31. package/dist/web/assets/{markdown-renderer-dHeak2mE.js → markdown-renderer-BFYBVcZP.js} +1 -1
  32. package/dist/web/assets/{markdown-renderer-iBw1fCI_.js → markdown-renderer-CklcWnd6.js} +1 -1
  33. package/dist/web/assets/{mobile-explorer-sheet-CzL8SLtz.js → mobile-explorer-sheet-D1OXw7op.js} +1 -1
  34. package/dist/web/assets/{notification-store-DVi6Ds71.js → notification-store-DyKshGRs.js} +1 -1
  35. package/dist/web/assets/{notifications-settings-section-shQ-ugKh.js → notifications-settings-section-BEQEjvan.js} +1 -1
  36. package/dist/web/assets/{pdf-preview-DB26KSzm.js → pdf-preview-DmQazeeV.js} +1 -1
  37. package/dist/web/assets/{postgres-viewer-CJe2meyF.js → postgres-viewer-Cd_wXjkT.js} +1 -1
  38. package/dist/web/assets/{ppmbot-settings-section-Ba4c9AI9.js → ppmbot-settings-section-PzCLPm8a.js} +1 -1
  39. package/dist/web/assets/{process-table-PXwLsMuG.js → process-table-C2TdkJck.js} +1 -1
  40. package/dist/web/assets/{proxy-settings-section-BHBhK6HC.js → proxy-settings-section-DsZ6KcHD.js} +1 -1
  41. package/dist/web/assets/{query-audit-section-BD5ZVqDz.js → query-audit-section-2eX9-6mO.js} +1 -1
  42. package/dist/web/assets/{remote-desktop-mobile-sheet-Di4QG8VX.js → remote-desktop-mobile-sheet-CMXSUcAN.js} +2 -2
  43. package/dist/web/assets/{remote-desktop-mobile-view-DzoZvAqc.js → remote-desktop-mobile-view-Bw6p5ikf.js} +1 -1
  44. package/dist/web/assets/{remote-desktop-readiness-gate-CHO-KJ9Z.js → remote-desktop-readiness-gate-W0lAhHq9.js} +1 -1
  45. package/dist/web/assets/{remote-desktop-window-content-X324gYbW.js → remote-desktop-window-content-P888YEqm.js} +1 -1
  46. package/dist/web/assets/{schedules-settings-section-a4-h6THc.js → schedules-settings-section-C8YDAxlo.js} +1 -1
  47. package/dist/web/assets/{settings-body-Fq7BVDw2.js → settings-body-B5v3_Tnb.js} +2 -2
  48. package/dist/web/assets/{settings-tab-CVLQy-ie.js → settings-tab-CauShulf.js} +1 -1
  49. package/dist/web/assets/{settings-window-content-Bi7M0iaZ.js → settings-window-content-BIosR7RY.js} +1 -1
  50. package/dist/web/assets/{sql-query-editor-D1r265fl.js → sql-query-editor-DMihDPnw.js} +1 -1
  51. package/dist/web/assets/{sqlite-viewer-DHkIotRl.js → sqlite-viewer-jCIGt2GP.js} +1 -1
  52. package/dist/web/assets/{system-monitor-body-y6VUkYTL.js → system-monitor-body-IUamfS5w.js} +2 -2
  53. package/dist/web/assets/{system-monitor-tab-DRAOejhk.js → system-monitor-tab-DaWeif4k.js} +1 -1
  54. package/dist/web/assets/{system-monitor-window-content-B5i57xHD.js → system-monitor-window-content-Bq3BS7Mg.js} +1 -1
  55. package/dist/web/assets/{tab-host-window-content-SkyPFFlc.js → tab-host-window-content-vfYLLIKU.js} +1 -1
  56. package/dist/web/assets/{team-member-sheet-BB63u-Lf.js → team-member-sheet-Be7OSGMT.js} +2 -2
  57. package/dist/web/assets/{team-member-window-content-D6tVTXuV.js → team-member-window-content-D5cYZzBq.js} +1 -1
  58. package/dist/web/assets/{terminal-tab-BYaHAkI5.js → terminal-tab-C89DQllY.js} +1 -1
  59. package/dist/web/assets/tool-cards-CWotDH_2.js +9 -0
  60. package/dist/web/assets/{use-accounts-data-Ba1_uvEE.js → use-accounts-data-lmmmG4ep.js} +1 -1
  61. package/dist/web/assets/{use-monaco-theme-Uakgj3hJ.js → use-monaco-theme-0ziS4xlp.js} +1 -1
  62. package/dist/web/assets/{use-remote-desktop-display-choice-Cd3Xalca.js → use-remote-desktop-display-choice-qBZ8ej7J.js} +1 -1
  63. package/dist/web/assets/{use-websocket-BC_EGf5q.js → use-websocket-DAvfbjxh.js} +1 -1
  64. package/dist/web/assets/{video-preview-BUb43zWb.js → video-preview-B8xALVkh.js} +1 -1
  65. package/dist/web/index.html +1 -1
  66. package/dist/web/sw.js +1 -1
  67. package/package.json +1 -1
  68. package/src/providers/codex-app-server/codex-event-mapper.ts +8 -2
  69. package/src/providers/codex-app-server/codex-history.ts +4 -2
  70. package/src/providers/codex-app-server/codex-protocol.ts +4 -0
  71. package/src/providers/codex-app-server/codex-provider.ts +69 -5
  72. package/src/server/routes/chat.ts +22 -3
  73. package/src/server/ws/chat.ts +6 -1
  74. package/src/services/edge-forwarder.ts +191 -191
  75. package/src/services/windows-process-tree.ts +351 -351
  76. package/src/types/api.ts +1 -1
  77. package/src/types/chat.ts +1 -1
  78. package/src/web/components/chat/message-list.tsx +1 -1
  79. package/src/web/components/chat/tool-cards.tsx +10 -0
  80. package/src/web/hooks/use-chat.ts +22 -11
  81. package/dist/web/assets/tool-cards-DRJu20sL.js +0 -9
  82. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  83. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  84. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  85. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
@@ -126,11 +126,13 @@ export function itemToToolResult(item: Item): ChatEvent {
126
126
  const type = item.type;
127
127
  let output = "";
128
128
  let isError = false;
129
+ let exitCode: number | undefined;
129
130
 
130
131
  if (type === "commandExecution") {
131
132
  output = redactTruncate(item.aggregatedOutput ?? "");
132
133
  const exit = item.exitCode;
133
- isError = typeof exit === "number" && exit !== 0;
134
+ exitCode = typeof exit === "number" ? exit : undefined;
135
+ isError = exitCode != null && exitCode !== 0;
134
136
  } else if (type === "mcpToolCall") {
135
137
  output = redactTruncate(item.result ?? item.error ?? "");
136
138
  isError = item.error != null;
@@ -153,7 +155,7 @@ export function itemToToolResult(item: Item): ChatEvent {
153
155
  output = redactTruncate(item);
154
156
  }
155
157
 
156
- return { type: "tool_result", output, isError, toolUseId: item.id };
158
+ return { type: "tool_result", output, isError, ...(exitCode != null ? { exitCode } : {}), toolUseId: item.id };
157
159
  }
158
160
 
159
161
  /**
@@ -168,6 +170,10 @@ export function mapCodexEvent(notif: Notif, sessionId: string): ChatEvent[] {
168
170
  return typeof p.delta === "string" ? [{ type: "text", content: p.delta }] : [];
169
171
 
170
172
  case "item/reasoning/textDelta":
173
+ // `summary` is what turn/start requests for PPM's Thinking switch. Recent
174
+ // app-server builds stream it under this method, while older builds used
175
+ // textDelta directly. Both are safe, user-visible reasoning summaries.
176
+ case "item/reasoning/summaryTextDelta":
171
177
  return typeof p.delta === "string" ? [{ type: "thinking", content: p.delta }] : [];
172
178
 
173
179
  case "item/started": {
@@ -411,10 +411,12 @@ export function getRolloutMessages(sessionsDir: string, threadId: string, reques
411
411
  } catch { return []; }
412
412
  }
413
413
 
414
- /** True when a path points at a codex rollout under ~/.codex/sessions. */
414
+ /** True when a path points at a codex rollout in the ambient or a PPM account home. */
415
415
  export function isCodexRolloutPath(p: string): boolean {
416
416
  const n = normPath(p);
417
- return n.endsWith(".jsonl") && n.includes(normPath(join(homedir(), ".codex", "sessions")));
417
+ const ambient = normPath(join(homedir(), ".codex", "sessions"));
418
+ const managed = normPath(join(homedir(), ".ppm", "codex-accounts"));
419
+ return n.endsWith(".jsonl") && (n.includes(ambient) || (n.includes(managed) && n.includes("/sessions/")));
418
420
  }
419
421
 
420
422
  /**
@@ -56,6 +56,10 @@ export interface TurnStartParams {
56
56
  approvalPolicy?: AskForApproval;
57
57
  sandboxPolicy?: unknown;
58
58
  model?: string;
59
+ /** Codex's per-turn reasoning depth (low through ultra, model dependent). */
60
+ effort?: string;
61
+ /** Controls whether Codex streams a safe reasoning summary to the client. */
62
+ summary?: "none" | "auto" | "concise" | "detailed";
59
63
  }
60
64
 
61
65
  // ── Decisions (approval responses) ──
@@ -10,7 +10,7 @@ import type {
10
10
  UsageInfo,
11
11
  } from "../provider.interface.ts";
12
12
  import { configService } from "../../services/config.service.ts";
13
- import { setSessionMetadata, getSessionProjectPath, setSessionProvider, setSessionCodexAccount, getSessionCodexAccount, getSessionTitles } from "../../services/db.service.ts";
13
+ import { setSessionMetadata, getSessionProjectPath, setSessionProvider, setSessionCodexAccount, getSessionCodexAccount, getSessionTitles, insertTurnUsage } from "../../services/db.service.ts";
14
14
  import {
15
15
  resolveCodexAccountForSession,
16
16
  getCodexAccount,
@@ -29,6 +29,7 @@ import { join } from "node:path";
29
29
  import { CodexJsonRpcClient, CONTROL_REQUEST_TIMEOUT_MS } from "./codex-jsonrpc-client.ts";
30
30
  import { permissionModeToCodex, type CodexPermission } from "./codex-permission-map.ts";
31
31
  import { mapCodexEvent, parseTokenUsage } from "./codex-event-mapper.ts";
32
+ import { subagentCardId } from "./codex-subagent-thread.ts";
32
33
  import { decisionFor, isApprovalMethod, type ApprovalMethod } from "./codex-approval-decision.ts";
33
34
  import { parseModelList } from "./codex-model-parser.ts";
34
35
  import { getOrFetchUsage, registerUsageSource } from "../../services/provider-usage/usage-registry.ts";
@@ -147,6 +148,8 @@ interface LiveSession {
147
148
  compactRequested?: boolean;
148
149
  /** Token counts from the most recent usage notification, attached to `done`. */
149
150
  lastUsage?: import("../../shared/turn-usage.ts").TurnUsage;
151
+ /** Spawned thread ids. Notifications from these threads belong inside their Agent card. */
152
+ subagentThreadIds: Set<string>;
150
153
  /** A turn is running on the thread. Codex accepts exactly one at a time: a
151
154
  * second `turn/start` resolves with the ALREADY-RUNNING turn (same id,
152
155
  * status `inProgress`) and silently discards the new input — no error, so
@@ -219,6 +222,21 @@ function createEventChannel(): EventChannel {
219
222
  };
220
223
  }
221
224
 
225
+ /** The app-server puts the owner thread on every item notification. An
226
+ * `agentThreadId` inside a SubAgentActivity is the newly spawned child, so it
227
+ * must never be used here: that would nest the launch card inside itself. */
228
+ export function threadIdFromNotification(params: unknown): string | null {
229
+ if (!params || typeof params !== "object") return null;
230
+ const p = params as Record<string, unknown>;
231
+ const direct = p.threadId ?? p.thread_id;
232
+ if (typeof direct === "string" && direct) return direct;
233
+ const thread = p.thread;
234
+ if (thread && typeof thread === "object" && typeof (thread as Record<string, unknown>).id === "string") {
235
+ return (thread as Record<string, unknown>).id as string;
236
+ }
237
+ return null;
238
+ }
239
+
222
240
  /**
223
241
  * Drop a model id that isn't a codex model. PPM's per-session model can fall back
224
242
  * to the global Claude default (e.g. `claude-opus-4-8`), which codex rejects
@@ -425,6 +443,7 @@ export class CodexAppServerProvider implements AIProvider {
425
443
  live.lastTurnInput = { message, opts };
426
444
  live.currentAssistant = "";
427
445
  live.currentEvents = [];
446
+ live.lastUsage = undefined;
428
447
  // Codex takes an image as a path, never as a payload, so the uploaded copy is what it
429
448
  // gets. Images lead: the text usually refers to them ("what is this?").
430
449
  const input: UserInput[] = [
@@ -432,10 +451,17 @@ export class CodexAppServerProvider implements AIProvider {
432
451
  { type: "text" as const, text: message, text_elements: [] },
433
452
  ];
434
453
  const turnModel = codexModel(opts?.model);
454
+ const turnEffort = opts?.effort ?? this.config?.effort;
455
+ const configuredThinking = this.config?.thinking_budget_tokens;
456
+ // Codex does not accept a token budget. Its equivalent is an effort level,
457
+ // while `summary` controls the safe thinking text the UI can show.
458
+ const thinkingEnabled = (opts?.thinkingBudget ?? configuredThinking ?? -1) !== 0;
435
459
  live.client.request("turn/start", {
436
460
  threadId: live.threadId,
437
461
  input,
438
462
  ...(turnModel ? { model: turnModel } : {}),
463
+ ...(turnEffort ? { effort: turnEffort } : {}),
464
+ summary: thinkingEnabled ? "detailed" : "none",
439
465
  }).then((res) => {
440
466
  // Backup for the `turn/started` notification, which is what normally names
441
467
  // the turn — an interrupt arriving in between has nothing to address.
@@ -678,7 +704,7 @@ export class CodexAppServerProvider implements AIProvider {
678
704
  client, threadId: null, cwd, channel, permission, model,
679
705
  pendingApprovals: new Map(), answeredCodexIds: new Set(),
680
706
  history: [], transcript: [], currentAssistant: "", currentEvents: [],
681
- pendingTurns: [],
707
+ pendingTurns: [], subagentThreadIds: new Set(),
682
708
  };
683
709
  this.live.set(sessionId, live);
684
710
 
@@ -750,14 +776,26 @@ export class CodexAppServerProvider implements AIProvider {
750
776
  const usage = parseTokenUsage(notif.params, live.model);
751
777
  if (usage) live.lastUsage = usage;
752
778
  }
779
+ const notificationThreadId = threadIdFromNotification(notif.params);
780
+ const parentToolUseId = notificationThreadId && live.subagentThreadIds.has(notificationThreadId)
781
+ ? subagentCardId(notificationThreadId)
782
+ : undefined;
753
783
  const events = mapCodexEvent(notif, live.threadId ?? "");
754
784
  for (const ev of events) {
755
785
  // The counts arrive on their own notification just before the turn ends,
756
786
  // so `done` is where they become visible to a consumer.
757
- if (ev.type === "done" && live.lastUsage) ev.usage = live.lastUsage;
758
- live.channel.push(ev);
787
+ if (ev.type === "done" && live.lastUsage) {
788
+ ev.usage = live.lastUsage;
789
+ this.recordTurnUsage(live, live.lastUsage);
790
+ }
791
+ const nested = parentToolUseId ? { ...ev, parentToolUseId } as ChatEvent : ev;
792
+ live.channel.push(nested);
759
793
  // Accumulate tool calls into the turn so getMessages (live) keeps them.
760
- if (ev.type === "tool_use" || ev.type === "tool_result") live.currentEvents.push(ev);
794
+ if (nested.type === "tool_use" || nested.type === "tool_result") live.currentEvents.push(nested);
795
+ if (nested.type === "tool_use" && nested.tool === "Agent" && typeof nested.toolUseId === "string"
796
+ && nested.toolUseId.startsWith("subagent-")) {
797
+ live.subagentThreadIds.add(nested.toolUseId.slice("subagent-".length));
798
+ }
761
799
  }
762
800
  if (notif.method === "turn/completed") {
763
801
  if (live.currentAssistant || live.currentEvents.length) {
@@ -798,6 +836,32 @@ export class CodexAppServerProvider implements AIProvider {
798
836
  }
799
837
  }
800
838
 
839
+ /** Persist Codex's per-turn token and prompt-cache split for the debug panel. */
840
+ private recordTurnUsage(live: LiveSession, usage: import("../../shared/turn-usage.ts").TurnUsage): void {
841
+ const accountId = live.threadId ? getSessionCodexAccount(live.threadId) ?? undefined : undefined;
842
+ const account = accountId ? getCodexAccount(accountId) : null;
843
+ try {
844
+ insertTurnUsage({
845
+ sessionId: live.threadId ?? "",
846
+ model: usage.model,
847
+ inputTokens: usage.inputTokens,
848
+ outputTokens: usage.outputTokens,
849
+ cacheReadTokens: usage.cacheReadTokens,
850
+ cacheWriteTokens: usage.cacheWriteTokens,
851
+ contextWindow: usage.contextWindow,
852
+ costUsd: usage.costUsd,
853
+ // A cache miss alone does not prove a process restart: a first turn and
854
+ // an evicted prompt cache look the same on the wire. Keep this false
855
+ // until the provider has an explicit lifecycle reason to record.
856
+ coldStart: false,
857
+ ...(accountId ? { accountId } : {}),
858
+ ...(account?.label ? { accountLabel: account.label } : {}),
859
+ });
860
+ } catch (err) {
861
+ console.warn(`[usage] failed to persist codex usage: ${(err as Error).message}`);
862
+ }
863
+ }
864
+
801
865
  private handleServerRequest(live: LiveSession, req: ServerRequest): void {
802
866
  const method = req.method;
803
867
  if (isApprovalMethod(method)) {
@@ -19,6 +19,8 @@ import { parseJsonlTranscript, validateJsonlPath } from "../../services/jsonl-tr
19
19
  import { aggregateTasks } from "../../services/task-status-aggregator.ts";
20
20
  import { MANY_IMAGE_DIMENSION_LIMIT, type StripMode } from "../../services/transcript-images.ts";
21
21
  import { auditTranscriptImagesFile, stripTranscriptImagesFile } from "../../services/transcript-images-file.ts";
22
+ import { listCodexAccounts } from "../../services/codex-account.service.ts";
23
+ import { findRolloutByThreadId } from "../../providers/codex-app-server/codex-history.ts";
22
24
  import { getSessionProjectPath, setSessionMetadata, setSessionTitle, getSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionMetadata, deleteSessionTitle, getAllUnread, clearSessionUnread, setSessionUnread } from "../../services/db.service.ts";
23
25
  import { setSessionTag, bulkSetSessionTag, getTagById, getSessionTags, getProjectDefaultTagId } from "../../services/tag.service.ts";
24
26
  import { recordBranch, resolveVersionGroup, resolveVersionMap, collapseTreesToHeads, hasChildren, deleteBranchesFor, getRootId } from "../../services/session-branch.service.ts";
@@ -469,8 +471,11 @@ chatRoutes.patch("/sessions/:id", async (c) => {
469
471
  const projectPath = c.get("projectPath");
470
472
  // Persist to PPM DB (authoritative source for user-set titles)
471
473
  setSessionTitle(id, title);
472
- // Also persist to SDK so Claude Code CLI sees the custom title
473
- await sdkRenameSession(id, title, { dir: projectPath });
474
+ // Codex owns its rollout title and has no Claude SDK session to rename.
475
+ // PPM's title table is the authoritative title for both providers.
476
+ if (getSessionProvider(id) !== "codex") {
477
+ await sdkRenameSession(id, title, { dir: projectPath });
478
+ }
474
479
  // Also update in-memory session
475
480
  const session = chatService.getSession(id);
476
481
  if (session) session.title = title;
@@ -763,9 +768,22 @@ chatRoutes.get("/sessions/:id/logs", (c) => {
763
768
  */
764
769
  function resolveSessionJsonlPath(sessionId: string): { jsonlPath: string; jsonlDir: string; projectPath: string; exists: boolean } {
765
770
  const homedir = process.env.HOME ?? process.env.USERPROFILE ?? "";
771
+ const providerId = getSessionProvider(sessionId);
772
+ const storedProjectPath = getSessionProjectPath(sessionId) ?? "";
773
+ if (providerId === "codex") {
774
+ const dirs = [
775
+ ...listCodexAccounts().map((account) => join(account.home, "sessions")),
776
+ ...(homedir ? [resolve(homedir, ".codex", "sessions")] : []),
777
+ ];
778
+ for (const dir of dirs) {
779
+ const found = findRolloutByThreadId(dir, sessionId, storedProjectPath || undefined);
780
+ if (found) return { jsonlPath: found, jsonlDir: dir, projectPath: storedProjectPath, exists: true };
781
+ }
782
+ return { jsonlPath: "", jsonlDir: "", projectPath: storedProjectPath, exists: false };
783
+ }
766
784
  const provider = providerRegistry.get("claude") as any;
767
785
  const projectPath = provider?.activeSessions?.get(sessionId)?.projectPath
768
- ?? getSessionProjectPath(sessionId)
786
+ ?? storedProjectPath
769
787
  ?? "";
770
788
  const projectsRoot = homedir ? resolve(homedir, ".claude", "projects") : "";
771
789
  // SDK encodes cwd by replacing path separators + drive colon with "-".
@@ -815,6 +833,7 @@ chatRoutes.get("/sessions/:id/debug", (c) => {
815
833
  return c.json(ok({
816
834
  ppmSessionId: sessionId,
817
835
  sdkSessionId: sessionId,
836
+ providerId: getSessionProvider(sessionId) ?? "claude",
818
837
  sessionId,
819
838
  jsonlPath: exists ? jsonlPath : null,
820
839
  jsonlDir,
@@ -333,7 +333,7 @@ function bufferAndBroadcast(sessionId: string, event: unknown): void {
333
333
  for (let i = entry.turnEvents.length - 1; i >= 0; i--) {
334
334
  const buffered = entry.turnEvents[i] as any;
335
335
  if (buffered.type === "tool_use" && buffered.toolUseId === toolUseId) {
336
- buffered.result = { output: (event as any).output, isError: (event as any).isError };
336
+ buffered.result = { output: (event as any).output, isError: (event as any).isError, exitCode: (event as any).exitCode };
337
337
  break;
338
338
  }
339
339
  }
@@ -1286,10 +1286,15 @@ export const chatWebSocket = {
1286
1286
  } else {
1287
1287
  // Follow-up: push into existing generator via provider
1288
1288
  if (provider && "pushMessage" in provider && parsed.type === "message") {
1289
+ const effort = getSessionEffort(sessionId) ?? undefined;
1290
+ const thinkingBudget = getSessionThinking(sessionId);
1289
1291
  (provider as any).pushMessage(sessionId, parsed.content, {
1290
1292
  priority: parsed.priority ?? 'next',
1291
1293
  images: parsed.images,
1292
1294
  imagePaths: parsed.imagePaths,
1295
+ ...(entry.model ? { model: entry.model } : {}),
1296
+ ...(effort ? { effort } : {}),
1297
+ ...(thinkingBudget != null ? { thinkingBudget } : {}),
1293
1298
  });
1294
1299
  }
1295
1300
  // Clear turn events for new turn display + transition phase