@hienlh/ppm 0.11.3 → 0.11.4

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,
@@ -457,12 +462,30 @@ export function useChat(sessionId: string | null, providerId = "claude", project
457
462
  // Handle turn_events (reconnect sync with rAF chunking)
458
463
  if ((data as any).type === "turn_events") {
459
464
  const events = (data as any).events as unknown[];
460
- if (!events?.length) { setIsReconnecting(false); return; }
465
+ const userMessage = (data as any).userMessage as string | null;
466
+ if (!events?.length && !userMessage) { setIsReconnecting(false); return; }
461
467
 
462
- // Truncate messages after last user message
468
+ // Remove stale streaming assistant message + inject current turn's user message
463
469
  setMessages(prev => {
464
- const lastUserIdx = prev.findLastIndex(m => m.role === "user");
465
- return lastUserIdx >= 0 ? prev.slice(0, lastUserIdx + 1) : prev;
470
+ let updated = prev;
471
+ // Only remove in-progress streaming assistant (not finalized or REST-loaded)
472
+ const last = updated[updated.length - 1];
473
+ if (last?.role === "assistant" && last.id.startsWith("streaming-")) {
474
+ updated = updated.slice(0, -1);
475
+ }
476
+ // Add the current turn's user message if not already present
477
+ if (userMessage) {
478
+ const lastAfter = updated[updated.length - 1];
479
+ if (lastAfter?.role !== "user" || lastAfter.content !== userMessage) {
480
+ updated = [...updated, {
481
+ id: `user-replay-${Date.now()}`,
482
+ role: "user" as const,
483
+ content: userMessage,
484
+ timestamp: new Date().toISOString(),
485
+ }];
486
+ }
487
+ }
488
+ return updated;
466
489
  });
467
490
 
468
491
  // Reset streaming refs
@@ -471,6 +494,7 @@ export function useChat(sessionId: string | null, providerId = "claude", project
471
494
  streamingAccountRef.current = null;
472
495
 
473
496
  // Process events in chunks via requestAnimationFrame to avoid blocking main thread
497
+ isReplayingRef.current = true;
474
498
  const CHUNK_SIZE = 100;
475
499
  let offset = 0;
476
500
  const processChunk = () => {
@@ -482,6 +506,7 @@ export function useChat(sessionId: string | null, providerId = "claude", project
482
506
  if (offset < events.length) {
483
507
  requestAnimationFrame(processChunk);
484
508
  } else {
509
+ isReplayingRef.current = false;
485
510
  setIsReconnecting(false);
486
511
  }
487
512
  };