@hienlh/ppm 0.9.77 → 0.9.79

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.79] - 2026-04-10
4
+
5
+ ### Fixed
6
+ - **Silent hang on resume of never-completed sessions**: When a session's first attempt hung (no SDK `init` event received), subsequent messages tried to `resume` a non-existent JSONL file, causing the SDK subprocess to silently hang. Now detects missing SDK mapping and treats as a new session.
7
+
8
+ ## [0.9.78] - 2026-04-10
9
+
10
+ ### Added
11
+ - **SDK subprocess stderr logging**: Real-time stderr output from Claude CLI subprocess is now logged to server console for debugging hangs and crashes.
12
+ - **MCP servers diagnostic log**: Log which MCP servers are being passed to SDK query.
13
+ - **Query creation log**: Log when `query()` async iterator is created to confirm subprocess started.
14
+
3
15
  ## [0.9.77] - 2026-04-10
4
16
 
5
17
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.9.77",
3
+ "version": "0.9.79",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -648,6 +648,14 @@ export class ClaudeAgentSdkProvider implements AIProvider {
648
648
  // Resolve SDK's actual session ID for resume (may differ from PPM's UUID)
649
649
  // For fork: use the source session's SDK id
650
650
  const sdkId = shouldFork ? getSdkSessionId(forkSourceId!) : getSdkSessionId(sessionId);
651
+ // If messageCount > 0 (resumeSession ran) but no real SDK mapping exists,
652
+ // the session never received an init event (prior attempt hung/crashed).
653
+ // Treat as first message to avoid resuming a non-existent JSONL → silent hang.
654
+ const hasRealSdkMapping = getSessionMapping(sessionId) !== null && getSessionMapping(sessionId) !== sessionId;
655
+ const effectiveIsFirst = isFirstMessage || (!shouldFork && !hasRealSdkMapping);
656
+ if (effectiveIsFirst && !isFirstMessage) {
657
+ console.warn(`[sdk] session=${sessionId} no SDK mapping found — treating as new session (was isFirst=false)`);
658
+ }
651
659
  // Fallback cwd: SDK needs a valid working directory even when no project is selected.
652
660
  // On Windows daemons, undefined cwd can cause the subprocess to fail silently.
653
661
  // Resolve path and validate existence — invalid cwd causes spawn to hang on Windows.
@@ -717,25 +725,29 @@ export class ClaudeAgentSdkProvider implements AIProvider {
717
725
  console.warn(`[sdk] session=${sessionId} no account and no API key in env — Claude CLI will use its own auth (if any)`);
718
726
  }
719
727
  }
720
- console.log(`[sdk] query: session=${sessionId} sdkId=${sdkId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`);
728
+ console.log(`[sdk] query: session=${sessionId} sdkId=${sdkId} isFirst=${isFirstMessage} effectiveIsFirst=${effectiveIsFirst} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`);
721
729
 
722
730
  // Read MCP servers from PPM DB (fresh per query — user may add/remove between chats)
723
731
  const mcpServers = mcpConfigService.list();
724
732
  const hasMcp = Object.keys(mcpServers).length > 0;
725
733
 
726
- // Buffer subprocess stderr for crash diagnostics
734
+ // Buffer subprocess stderr for crash diagnostics + log in real-time
727
735
  let stderrBuffer = "";
728
736
  const stderrCallback = (chunk: string) => {
729
737
  stderrBuffer += chunk;
730
- // Keep only last 2KB to avoid unbounded growth
731
738
  if (stderrBuffer.length > 2048) stderrBuffer = stderrBuffer.slice(-2048);
739
+ const trimmed = chunk.trim();
740
+ if (trimmed) console.log(`[sdk] session=${sessionId} stderr: ${trimmed.slice(0, 500)}`);
732
741
  };
742
+ if (hasMcp) {
743
+ console.log(`[sdk] session=${sessionId} mcpServers: ${Object.keys(mcpServers).join(", ")}`);
744
+ }
733
745
 
734
746
  const queryOptions: Record<string, any> = {
735
747
  // On Windows, child_process.spawn("bun") fails with ENOENT — force node
736
748
  ...(process.platform === "win32" && { executable: "node" }),
737
- sessionId: isFirstMessage && !shouldFork ? sessionId : undefined,
738
- resume: (isFirstMessage && !shouldFork) ? undefined : sdkId,
749
+ sessionId: effectiveIsFirst && !shouldFork ? sessionId : undefined,
750
+ resume: (effectiveIsFirst && !shouldFork) ? undefined : sdkId,
739
751
  ...(shouldFork && { forkSession: true }),
740
752
  cwd: effectiveCwd,
741
753
  systemPrompt: systemPromptOpt,
@@ -786,6 +798,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
786
798
  this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl });
787
799
  this.activeQueries.set(sessionId, initialQuery);
788
800
  let eventSource: AsyncIterable<any> = initialQuery;
801
+ console.log(`[sdk] session=${sessionId} query() created, waiting for first SDK event...`);
789
802
 
790
803
  // Helper: close the CURRENT streaming session (not stale closure refs).
791
804
  // All retry paths must use this instead of closing captured variables directly.