@hienlh/ppm 0.11.3 → 0.11.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.
@@ -88,6 +88,8 @@ export function useChat(sessionId: string | null, providerId = "claude", project
88
88
  const pendingMessageRef = useRef<string | null>(null);
89
89
  const sendRef = useRef<(data: string) => void>(() => {});
90
90
  const refetchRef = useRef<(() => void) | null>(null);
91
+ /** True while replaying turn_events — suppresses setPendingApproval */
92
+ const isReplayingRef = useRef(false);
91
93
  const sessionIdRef = useRef(sessionId);
92
94
  sessionIdRef.current = sessionId;
93
95
  const projectNameRef = useRef(projectName);
@@ -251,6 +253,9 @@ export function useChat(sessionId: string | null, providerId = "claude", project
251
253
 
252
254
  case "approval_request": {
253
255
  streamingEventsRef.current.push(ev as ChatEvent);
256
+ // During turn_events replay, session_state already set the correct
257
+ // pendingApproval — skip re-setting it for historical (already-answered) events
258
+ if (isReplayingRef.current) break;
254
259
  setPendingApproval({
255
260
  requestId: ev.requestId,
256
261
  tool: ev.tool,
@@ -361,6 +366,7 @@ export function useChat(sessionId: string | null, providerId = "claude", project
361
366
  // Finalize the streaming message — preserve SDK UUID for fork/rewind
362
367
  const finalContent = streamingContentRef.current;
363
368
  const finalEvents = [...streamingEventsRef.current];
369
+ const finalAccount = streamingAccountRef.current;
364
370
  const doneUuid = ev.lastMessageUuid as string | undefined;
365
371
  setMessages((prev) => {
366
372
  const last = prev[prev.length - 1];
@@ -373,6 +379,19 @@ export function useChat(sessionId: string | null, providerId = "claude", project
373
379
  ...(doneUuid && { sdkUuid: doneUuid }),
374
380
  }];
375
381
  }
382
+ // No assistant message flushed yet (rAF was still pending when cancelled).
383
+ // Create one from accumulated refs so the response isn't silently lost.
384
+ if (finalContent || finalEvents.length > 0) {
385
+ return [...prev, {
386
+ id: `final-${Date.now()}`,
387
+ role: "assistant" as const,
388
+ content: finalContent,
389
+ events: finalEvents,
390
+ timestamp: new Date().toISOString(),
391
+ ...(doneUuid && { sdkUuid: doneUuid }),
392
+ ...finalAccount,
393
+ }];
394
+ }
376
395
  return prev;
377
396
  });
378
397
  streamingContentRef.current = "";
@@ -457,12 +476,30 @@ export function useChat(sessionId: string | null, providerId = "claude", project
457
476
  // Handle turn_events (reconnect sync with rAF chunking)
458
477
  if ((data as any).type === "turn_events") {
459
478
  const events = (data as any).events as unknown[];
460
- if (!events?.length) { setIsReconnecting(false); return; }
479
+ const userMessage = (data as any).userMessage as string | null;
480
+ if (!events?.length && !userMessage) { setIsReconnecting(false); return; }
461
481
 
462
- // Truncate messages after last user message
482
+ // Remove stale streaming assistant message + inject current turn's user message
463
483
  setMessages(prev => {
464
- const lastUserIdx = prev.findLastIndex(m => m.role === "user");
465
- return lastUserIdx >= 0 ? prev.slice(0, lastUserIdx + 1) : prev;
484
+ let updated = prev;
485
+ // Only remove in-progress streaming assistant (not finalized or REST-loaded)
486
+ const last = updated[updated.length - 1];
487
+ if (last?.role === "assistant" && last.id.startsWith("streaming-")) {
488
+ updated = updated.slice(0, -1);
489
+ }
490
+ // Add the current turn's user message if not already present
491
+ if (userMessage) {
492
+ const lastAfter = updated[updated.length - 1];
493
+ if (lastAfter?.role !== "user" || lastAfter.content !== userMessage) {
494
+ updated = [...updated, {
495
+ id: `user-replay-${Date.now()}`,
496
+ role: "user" as const,
497
+ content: userMessage,
498
+ timestamp: new Date().toISOString(),
499
+ }];
500
+ }
501
+ }
502
+ return updated;
466
503
  });
467
504
 
468
505
  // Reset streaming refs
@@ -471,6 +508,7 @@ export function useChat(sessionId: string | null, providerId = "claude", project
471
508
  streamingAccountRef.current = null;
472
509
 
473
510
  // Process events in chunks via requestAnimationFrame to avoid blocking main thread
511
+ isReplayingRef.current = true;
474
512
  const CHUNK_SIZE = 100;
475
513
  let offset = 0;
476
514
  const processChunk = () => {
@@ -482,6 +520,7 @@ export function useChat(sessionId: string | null, providerId = "claude", project
482
520
  if (offset < events.length) {
483
521
  requestAnimationFrame(processChunk);
484
522
  } else {
523
+ isReplayingRef.current = false;
485
524
  setIsReconnecting(false);
486
525
  }
487
526
  };