@hienlh/ppm 0.9.0-beta.0 → 0.9.0-beta.1

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0-beta.1] - 2026-03-26
4
+
5
+ ### Fixed
6
+ - **FE stuck on "Connecting"**: SDK system events (hook_started, init) now yield to streaming loop, transitioning phase `connecting → thinking` immediately instead of waiting 5-10s for first text event
7
+
3
8
  ## [0.9.0-beta.0] - 2026-03-26
4
9
 
5
10
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.9.0-beta.0",
3
+ "version": "0.9.0-beta.1",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -502,22 +502,24 @@ export class ClaudeAgentSdkProvider implements AIProvider {
502
502
 
503
503
  // Log all system events for debugging SDK lifecycle
504
504
  if (msg.type === "system") {
505
- console.log(`[sdk] session=${sessionId} system: subtype=${(msg as any).subtype ?? "none"} ${JSON.stringify(msg).slice(0, 500)}`);
506
- }
507
-
508
- // Capture SDK session metadata from init message
509
- if (msg.type === "system" && (msg as any).subtype === "init") {
510
- const initMsg = msg as any;
511
- // SDK may assign a different session_id than our UUID
512
- if (initMsg.session_id && initMsg.session_id !== sessionId) {
513
- // Persist mapping so resume works after server restart
514
- setSessionMapping(sessionId, initMsg.session_id);
515
- // Update our in-memory mapping
516
- const oldMeta = this.activeSessions.get(sessionId);
517
- if (oldMeta) {
518
- this.activeSessions.set(initMsg.session_id, { ...oldMeta, id: initMsg.session_id });
505
+ const subtype = (msg as any).subtype ?? "none";
506
+ console.log(`[sdk] session=${sessionId} system: subtype=${subtype} ${JSON.stringify(msg).slice(0, 500)}`);
507
+
508
+ // Capture SDK session metadata from init message
509
+ if (subtype === "init") {
510
+ const initMsg = msg as any;
511
+ if (initMsg.session_id && initMsg.session_id !== sessionId) {
512
+ setSessionMapping(sessionId, initMsg.session_id);
513
+ const oldMeta = this.activeSessions.get(sessionId);
514
+ if (oldMeta) {
515
+ this.activeSessions.set(initMsg.session_id, { ...oldMeta, id: initMsg.session_id });
516
+ }
519
517
  }
520
518
  }
519
+
520
+ // Yield system events so streaming loop can transition phases
521
+ // (e.g. connecting → thinking when hooks/init arrive)
522
+ yield { type: "system" as any, subtype } as any;
521
523
  continue;
522
524
  }
523
525
 
@@ -92,8 +92,13 @@ export class MockProvider implements AIProvider {
92
92
  const abortController = new AbortController();
93
93
  this.activeAborts.set(sessionId, abortController);
94
94
 
95
+ // Simulate SDK system events (hooks, init) — real SDK emits these before content
96
+ yield { type: "system" as any, subtype: "hook_started" } as any;
97
+ await sleep(50);
98
+ yield { type: "system" as any, subtype: "init" } as any;
99
+
95
100
  // Simulate thinking delay
96
- await sleep(300);
101
+ await sleep(250);
97
102
 
98
103
  // Pick a response
99
104
  const responseText =
@@ -198,6 +198,16 @@ async function runStreamLoop(sessionId: string, providerId: string, content: str
198
198
  const ev = event as any;
199
199
  const evType = ev.type ?? "unknown";
200
200
 
201
+ // System events (hook_started, init, etc.) → transition connecting → thinking
202
+ // These indicate SDK has connected and is processing, but no content yet.
203
+ if (evType === "system") {
204
+ if (!firstEventReceived) {
205
+ if (heartbeat) clearInterval(heartbeat);
206
+ setPhase(sessionId, "thinking");
207
+ }
208
+ continue; // Don't buffer or broadcast system events
209
+ }
210
+
201
211
  // First content event — stop heartbeat, transition phase
202
212
  const isMetadataEvent = evType === "account_info" || evType === "streaming_status";
203
213
  if (!firstEventReceived && !isMetadataEvent) {