@adhdev/daemon-core 0.8.83 → 0.8.85

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/dist/index.mjs CHANGED
@@ -2498,6 +2498,58 @@ var init_provider_cli_adapter = __esm({
2498
2498
  `[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(summarizeCliTraceText(finalScreenText, 240)).slice(0, 280)}`
2499
2499
  );
2500
2500
  }
2501
+ clearStaleIdleResponseGuard(reason) {
2502
+ const screenText = this.terminalScreen.getText() || "";
2503
+ const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
2504
+ const blockingModal = this.activeModal || this.getStartupConfirmationModal(screenText);
2505
+ if (!this.isWaitingForResponse || this.currentStatus !== "idle" || !visibleIdlePrompt || !!blockingModal) {
2506
+ return false;
2507
+ }
2508
+ if (this.responseTimeout) {
2509
+ clearTimeout(this.responseTimeout);
2510
+ this.responseTimeout = null;
2511
+ }
2512
+ if (this.idleTimeout) {
2513
+ clearTimeout(this.idleTimeout);
2514
+ this.idleTimeout = null;
2515
+ }
2516
+ if (this.approvalExitTimeout) {
2517
+ clearTimeout(this.approvalExitTimeout);
2518
+ this.approvalExitTimeout = null;
2519
+ }
2520
+ if (this.finishRetryTimer) {
2521
+ clearTimeout(this.finishRetryTimer);
2522
+ this.finishRetryTimer = null;
2523
+ }
2524
+ this.clearIdleFinishCandidate(reason);
2525
+ this.responseBuffer = "";
2526
+ this.isWaitingForResponse = false;
2527
+ this.responseSettleIgnoreUntil = 0;
2528
+ this.submitRetryUsed = false;
2529
+ this.submitRetryPromptSnippet = "";
2530
+ this.finishRetryCount = 0;
2531
+ this.currentTurnScope = null;
2532
+ this.activeModal = null;
2533
+ this.recordTrace("stale_idle_response_cleared", {
2534
+ reason,
2535
+ screenText: summarizeCliTraceText(screenText, 240)
2536
+ });
2537
+ return true;
2538
+ }
2539
+ hasMeaningfulResponseBuffer(promptSnippet) {
2540
+ const raw = String(this.responseBuffer || "").trim();
2541
+ if (!raw) return false;
2542
+ const normalizedPrompt = compactPromptText(promptSnippet);
2543
+ if (!normalizedPrompt) return true;
2544
+ const normalizedBuffer = compactPromptText(raw);
2545
+ if (!normalizedBuffer) return false;
2546
+ if (normalizedBuffer === normalizedPrompt) return false;
2547
+ if (normalizedBuffer.startsWith(normalizedPrompt)) {
2548
+ const remainder = normalizedBuffer.slice(normalizedPrompt.length).replace(/[─═\-]+/g, "").replace(/⏵⏵accepteditson\([^)]*\)/gi, "").replace(/accepteditson\([^)]*\)/gi, "").replace(/(?:◐|◑|◒|◓|◔|◕|◉|●|·)?(?:x?high|medium|low|max)·?\/effort/gi, "").replace(/updateavailable!run:[a-z0-9:._\-/]+/gi, "").replace(/esctointerrupt/gi, "").replace(/❯/g, "").replace(/^[\s\-–—:;,.!/?]+/, "").trim();
2549
+ return remainder.length > 0;
2550
+ }
2551
+ return true;
2552
+ }
2501
2553
  evaluateSettled() {
2502
2554
  const now = Date.now();
2503
2555
  if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
@@ -2530,7 +2582,11 @@ var init_provider_cli_adapter = __esm({
2530
2582
  scope: this.currentTurnScope,
2531
2583
  lastOutputAt: this.lastOutputAt
2532
2584
  }) : [];
2585
+ if (this.maybeCommitVisibleIdleTranscript(parsedTranscript)) {
2586
+ return;
2587
+ }
2533
2588
  const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === "assistant");
2589
+ const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet || this.currentTurnScope?.prompt || "");
2534
2590
  this.recordTrace("settled", {
2535
2591
  tail: summarizeCliTraceText(tail, 500),
2536
2592
  screenText: summarizeCliTraceText(screenText, 1200),
@@ -2548,6 +2604,24 @@ var init_provider_cli_adapter = __esm({
2548
2604
  scope: this.currentTurnScope
2549
2605
  })
2550
2606
  });
2607
+ if (this.currentTurnScope && !lastParsedAssistant && !this.submitRetryUsed && this.ptyProcess && this.currentStatus !== "waiting_approval" && promptLikelyVisible(screenText, normalizedPromptSnippet) && !this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) {
2608
+ this.submitRetryUsed = true;
2609
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
2610
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key from settled parser (no assistant yet)`);
2611
+ this.recordTrace("submit_write", {
2612
+ mode: "settled_retry",
2613
+ sendKey: this.sendKey,
2614
+ screenText: summarizeCliTraceText(screenText, 500)
2615
+ });
2616
+ this.ptyProcess.write(this.sendKey);
2617
+ if (this.settleTimer) clearTimeout(this.settleTimer);
2618
+ this.settleTimer = setTimeout(() => {
2619
+ this.settleTimer = null;
2620
+ this.settledBuffer = this.recentOutputBuffer;
2621
+ this.evaluateSettled();
2622
+ }, this.timeouts.outputSettle + 150);
2623
+ return;
2624
+ }
2551
2625
  if (this.currentTurnScope && !lastParsedAssistant) {
2552
2626
  LOG.info(
2553
2627
  "CLI",
@@ -2590,7 +2664,15 @@ var init_provider_cli_adapter = __esm({
2590
2664
  }
2591
2665
  const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
2592
2666
  const statusActivityHoldMs = this.getStatusActivityHoldMs();
2593
- const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity;
2667
+ const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
2668
+ const visibleAssistantCandidate = this.looksLikeVisibleAssistantCandidate(screenText);
2669
+ if (this.currentTurnScope && this.cliType === "claude-cli") {
2670
+ LOG.info(
2671
+ "CLI",
2672
+ `[${this.cliType}] settled diagnostics prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} scriptStatus=${String(scriptStatus || "")} parsedStatus=${String(parsedTranscript?.status || "")} parsedMsgCount=${parsedMessages.length} lastParsedAssistant=${JSON.stringify(summarizeCliTraceText(lastParsedAssistant?.content || "", 120)).slice(0, 160)} visibleIdlePrompt=${String(visibleIdlePrompt)} visibleAssistantCandidate=${String(visibleAssistantCandidate)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 160)).slice(0, 220)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 160)).slice(0, 220)}`
2673
+ );
2674
+ }
2675
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity && !(visibleIdlePrompt && visibleAssistantCandidate);
2594
2676
  if (shouldHoldGenerating) {
2595
2677
  this.clearIdleFinishCandidate("hold_generating_recent_activity");
2596
2678
  this.setStatus("generating", "recent_activity_hold");
@@ -2621,8 +2703,8 @@ var init_provider_cli_adapter = __esm({
2621
2703
  if (scriptStatus === "waiting_approval") {
2622
2704
  this.clearIdleFinishCandidate("waiting_approval");
2623
2705
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
2624
- const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
2625
- if ((inCooldown || visibleIdlePrompt) && !modal) {
2706
+ const visibleIdlePrompt2 = this.looksLikeVisibleIdlePrompt(screenText);
2707
+ if ((inCooldown || visibleIdlePrompt2) && !modal) {
2626
2708
  if (this.approvalExitTimeout) {
2627
2709
  clearTimeout(this.approvalExitTimeout);
2628
2710
  this.approvalExitTimeout = null;
@@ -2694,7 +2776,7 @@ var init_provider_cli_adapter = __esm({
2694
2776
  this.lastApprovalResolvedAt = Date.now();
2695
2777
  }
2696
2778
  if (this.isWaitingForResponse) {
2697
- const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
2779
+ const visibleIdlePrompt2 = this.looksLikeVisibleIdlePrompt(screenText);
2698
2780
  const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
2699
2781
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
2700
2782
  const hasAssistantTurn = !!lastParsedAssistant;
@@ -2702,12 +2784,12 @@ var init_provider_cli_adapter = __esm({
2702
2784
  const idleFinishConfirmMs = this.getIdleFinishConfirmMs();
2703
2785
  const idleQuietThresholdMs = Math.max(idleFinishConfirmMs, this.timeouts.outputSettle);
2704
2786
  const idleStableThresholdMs = idleFinishConfirmMs;
2705
- const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
2787
+ const idleReady = visibleIdlePrompt2 && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
2706
2788
  const candidate = this.idleFinishCandidate;
2707
2789
  const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= idleFinishConfirmMs;
2708
2790
  const canFinishImmediately = idleReady && candidateQuiet;
2709
2791
  this.recordTrace("idle_decision", {
2710
- visibleIdlePrompt,
2792
+ visibleIdlePrompt: visibleIdlePrompt2,
2711
2793
  quietForMs,
2712
2794
  screenStableMs,
2713
2795
  hasAssistantTurn,
@@ -2827,6 +2909,70 @@ var init_provider_cli_adapter = __esm({
2827
2909
  this.setStatus("idle", "response_finished");
2828
2910
  this.onStatusChange?.();
2829
2911
  }
2912
+ maybeCommitVisibleIdleTranscript(parsed, options) {
2913
+ const allowImmediateScriptIdleCommit = this.provider.allowInputDuringGeneration === true;
2914
+ if (!allowImmediateScriptIdleCommit) return false;
2915
+ if (!parsed || !Array.isArray(parsed.messages) || parsed.status !== "idle" || !this.isWaitingForResponse || !this.currentTurnScope || this.activeModal || parsed.activeModal) {
2916
+ return false;
2917
+ }
2918
+ if (options?.requireVisibleAssistantCandidate) {
2919
+ const candidateText = options.screenText || this.terminalScreen.getText() || "";
2920
+ if (!this.looksLikeVisibleAssistantCandidate(candidateText)) {
2921
+ return false;
2922
+ }
2923
+ }
2924
+ const hydratedForIdleCommit = normalizeCliParsedMessages(parsed.messages, {
2925
+ committedMessages: this.committedMessages,
2926
+ scope: this.currentTurnScope,
2927
+ lastOutputAt: this.lastOutputAt
2928
+ });
2929
+ const visibleAssistant = [...hydratedForIdleCommit].reverse().find((message) => message.role === "assistant" && message.content.trim());
2930
+ if (!visibleAssistant) return false;
2931
+ this.committedMessages = hydratedForIdleCommit;
2932
+ const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
2933
+ if (promptForTrim) {
2934
+ const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
2935
+ if (lastAssistantForTrim) {
2936
+ lastAssistantForTrim.content = trimPromptEchoPrefix(lastAssistantForTrim.content, promptForTrim);
2937
+ }
2938
+ }
2939
+ if (this.responseTimeout) {
2940
+ clearTimeout(this.responseTimeout);
2941
+ this.responseTimeout = null;
2942
+ }
2943
+ if (this.idleTimeout) {
2944
+ clearTimeout(this.idleTimeout);
2945
+ this.idleTimeout = null;
2946
+ }
2947
+ if (this.approvalExitTimeout) {
2948
+ clearTimeout(this.approvalExitTimeout);
2949
+ this.approvalExitTimeout = null;
2950
+ }
2951
+ if (this.submitRetryTimer) {
2952
+ clearTimeout(this.submitRetryTimer);
2953
+ this.submitRetryTimer = null;
2954
+ }
2955
+ if (this.finishRetryTimer) {
2956
+ clearTimeout(this.finishRetryTimer);
2957
+ this.finishRetryTimer = null;
2958
+ }
2959
+ this.syncMessageViews();
2960
+ this.responseBuffer = "";
2961
+ this.isWaitingForResponse = false;
2962
+ this.responseSettleIgnoreUntil = 0;
2963
+ this.submitRetryUsed = false;
2964
+ this.submitRetryPromptSnippet = "";
2965
+ this.finishRetryCount = 0;
2966
+ this.currentTurnScope = null;
2967
+ this.activeModal = null;
2968
+ this.setStatus("idle", "script_idle_commit");
2969
+ this.onStatusChange?.();
2970
+ this.recordTrace("script_idle_commit", {
2971
+ messageCount: this.committedMessages.length,
2972
+ lastAssistant: summarizeCliTraceText(visibleAssistant.content, 320)
2973
+ });
2974
+ return true;
2975
+ }
2830
2976
  commitCurrentTranscript() {
2831
2977
  const parsed = this.parseCurrentTranscript(
2832
2978
  this.committedMessages,
@@ -2848,6 +2994,12 @@ var init_provider_cli_adapter = __esm({
2848
2994
  }
2849
2995
  this.syncMessageViews();
2850
2996
  const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
2997
+ if (this.currentTurnScope) {
2998
+ LOG.info(
2999
+ "CLI",
3000
+ `[${this.cliType}] commitCurrentTranscript committedMessages=${this.committedMessages.length} finalLastAssistant=${JSON.stringify(summarizeCliTraceText(lastAssistant?.content || "", 220)).slice(0, 260)}`
3001
+ );
3002
+ }
2851
3003
  this.recordTrace("commit_transcript", {
2852
3004
  parsedStatus: parsed.status || null,
2853
3005
  messageCount: this.committedMessages.length,
@@ -2867,11 +3019,18 @@ var init_provider_cli_adapter = __esm({
2867
3019
  `[${this.cliType}] Commit without assistant turn: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
2868
3020
  );
2869
3021
  }
3022
+ const hasAssistant = !!lastAssistant;
2870
3023
  return {
2871
- hasAssistant: !!lastAssistant,
3024
+ hasAssistant,
2872
3025
  assistantContent: lastAssistant?.content || ""
2873
3026
  };
2874
3027
  }
3028
+ if (this.currentTurnScope) {
3029
+ LOG.info(
3030
+ "CLI",
3031
+ `[${this.cliType}] commitCurrentTranscript parsed.messages=none responseBufferLen=${this.responseBuffer.length} accumulatedBufferLen=${this.accumulatedBuffer.length} parsedStatus=${parsed?.status || "-"} providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"}`
3032
+ );
3033
+ }
2875
3034
  return {
2876
3035
  hasAssistant: false,
2877
3036
  assistantContent: ""
@@ -2957,19 +3116,25 @@ var init_provider_cli_adapter = __esm({
2957
3116
  this.currentTurnScope,
2958
3117
  screenText
2959
3118
  );
2960
- const shouldPreferCommittedMessages = !this.currentTurnScope && this.currentStatus === "idle" && !this.activeModal;
3119
+ if (this.maybeCommitVisibleIdleTranscript(parsed)) {
3120
+ return this.getScriptParsedStatus();
3121
+ }
3122
+ const shouldPreferCommittedMessages = !this.currentTurnScope && !this.activeModal && this.currentStatus === "idle";
2961
3123
  let result;
2962
3124
  if (parsed && Array.isArray(parsed.messages)) {
2963
- const hydratedMessages = shouldPreferCommittedMessages ? this.committedMessages.map((message, index) => buildChatMessage({
2964
- ...message,
2965
- id: message.id || `msg_${index}`,
2966
- index: typeof message.index === "number" ? message.index : index,
2967
- receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
2968
- })) : hydrateCliParsedMessages(parsed.messages, {
3125
+ const parsedHydratedMessages = hydrateCliParsedMessages(parsed.messages, {
2969
3126
  committedMessages: this.committedMessages,
2970
3127
  scope: this.currentTurnScope,
2971
3128
  lastOutputAt: this.lastOutputAt
2972
3129
  });
3130
+ const committedHydratedMessages = this.committedMessages.map((message, index) => buildChatMessage({
3131
+ ...message,
3132
+ id: message.id || `msg_${index}`,
3133
+ index: typeof message.index === "number" ? message.index : index,
3134
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
3135
+ }));
3136
+ const shouldPreferCommittedHistoryReplay = !this.currentTurnScope && !this.activeModal && committedHydratedMessages.length > parsedHydratedMessages.length;
3137
+ const hydratedMessages = shouldPreferCommittedMessages || shouldPreferCommittedHistoryReplay ? committedHydratedMessages : parsedHydratedMessages;
2973
3138
  result = {
2974
3139
  id: parsed.id || "cli_session",
2975
3140
  status: parsed.status || this.currentStatus,
@@ -2993,6 +3158,23 @@ var init_provider_cli_adapter = __esm({
2993
3158
  activeModal: this.activeModal
2994
3159
  };
2995
3160
  }
3161
+ const hasVisibleAssistantMessage = Array.isArray(result?.messages) && result.messages.some((message) => message?.role === "assistant" && typeof message?.content === "string" && message.content.trim());
3162
+ const shouldClampStaleGeneratingToIdle = result?.status === "generating" && this.currentStatus === "idle" && !this.currentTurnScope && !result?.activeModal && hasVisibleAssistantMessage;
3163
+ if (shouldClampStaleGeneratingToIdle) {
3164
+ result = {
3165
+ ...result,
3166
+ status: "idle",
3167
+ messages: Array.isArray(result.messages) ? result.messages.map((message) => {
3168
+ if (message?.role !== "assistant" || !message?.meta?.streaming) return message;
3169
+ const nextMeta = { ...message.meta || {} };
3170
+ delete nextMeta.streaming;
3171
+ return {
3172
+ ...message,
3173
+ ...Object.keys(nextMeta).length > 0 ? { meta: nextMeta } : { meta: void 0 }
3174
+ };
3175
+ }) : result.messages
3176
+ };
3177
+ }
2996
3178
  this.parsedStatusCache = {
2997
3179
  committedMessagesRef: this.committedMessages,
2998
3180
  responseBuffer: this.responseBuffer,
@@ -3125,9 +3307,27 @@ ${data.message || ""}`.trim();
3125
3307
  }
3126
3308
  }
3127
3309
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
3128
- if (this.isWaitingForResponse && !allowInputDuringGeneration) {
3310
+ const parsedStatusBeforeSend = !allowInputDuringGeneration ? (() => {
3311
+ try {
3312
+ return this.getScriptParsedStatus?.() || null;
3313
+ } catch {
3314
+ return null;
3315
+ }
3316
+ })() : null;
3317
+ const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === "string" ? String(parsedStatusBeforeSend.status) : "";
3318
+ const parsedMessagesBeforeSend = Array.isArray(parsedStatusBeforeSend?.messages) ? parsedStatusBeforeSend.messages.filter((message) => message && (message.role === "user" || message.role === "assistant")) : [];
3319
+ const shouldCommitParsedIdleBeforeSend = !allowInputDuringGeneration && parsedSessionStatus === "idle" && parsedMessagesBeforeSend.length > this.committedMessages.length && parsedMessagesBeforeSend.some((message) => message?.role === "assistant" && typeof message?.content === "string" && message.content.trim());
3320
+ if (shouldCommitParsedIdleBeforeSend) {
3321
+ this.commitCurrentTranscript();
3322
+ }
3323
+ if (!allowInputDuringGeneration && (parsedSessionStatus === "generating" || parsedSessionStatus === "long_generating")) {
3129
3324
  throw new Error(`${this.cliName} is still processing the previous prompt`);
3130
3325
  }
3326
+ if (this.isWaitingForResponse && !allowInputDuringGeneration) {
3327
+ if (!this.clearStaleIdleResponseGuard("send_message_guard")) {
3328
+ throw new Error(`${this.cliName} is still processing the previous prompt`);
3329
+ }
3330
+ }
3131
3331
  const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
3132
3332
  if (blockingModal || this.currentStatus === "waiting_approval") {
3133
3333
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
@@ -3207,7 +3407,7 @@ ${data.message || ""}`.trim();
3207
3407
  this.submitRetryTimer = null;
3208
3408
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
3209
3409
  if (this.currentStatus === "waiting_approval") return;
3210
- if ((this.responseBuffer || "").trim()) return;
3410
+ if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
3211
3411
  const screenText2 = this.terminalScreen.getText();
3212
3412
  if (!promptLikelyVisible(screenText2, normalizedPromptSnippet)) return;
3213
3413
  if (/Esc to interrupt|Do you want to proceed|This command requires approval|Allow Codex to|Approve and run now|Always approve this session|Running…|Running\.\.\./i.test(screenText2)) return;
@@ -3244,7 +3444,7 @@ ${data.message || ""}`.trim();
3244
3444
  this.submitRetryTimer = null;
3245
3445
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
3246
3446
  if (this.currentStatus === "waiting_approval") return;
3247
- if ((this.responseBuffer || "").trim()) return;
3447
+ if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
3248
3448
  const screenText = this.terminalScreen.getText();
3249
3449
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
3250
3450
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
@@ -9609,6 +9809,7 @@ function normalizeReadChatCommandStatus(status, activeModal) {
9609
9809
  }
9610
9810
  function buildReadChatCommandResult(payload, args) {
9611
9811
  let validatedPayload;
9812
+ const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === "object" ? payload.debugReadChat : void 0;
9612
9813
  try {
9613
9814
  validatedPayload = validateReadChatResultPayload({
9614
9815
  ...payload,
@@ -9629,7 +9830,8 @@ function buildReadChatCommandResult(payload, args) {
9629
9830
  syncMode: "full",
9630
9831
  replaceFrom: 0,
9631
9832
  totalMessages: messages.length,
9632
- lastMessageSignature
9833
+ lastMessageSignature,
9834
+ ...debugReadChat ? { debugReadChat } : {}
9633
9835
  };
9634
9836
  }
9635
9837
  const sync = computeReadChatSync(messages, cursor);
@@ -9640,7 +9842,8 @@ function buildReadChatCommandResult(payload, args) {
9640
9842
  syncMode: sync.syncMode,
9641
9843
  replaceFrom: sync.replaceFrom,
9642
9844
  totalMessages: sync.totalMessages,
9643
- lastMessageSignature: sync.lastMessageSignature
9845
+ lastMessageSignature: sync.lastMessageSignature,
9846
+ ...debugReadChat ? { debugReadChat } : {}
9644
9847
  };
9645
9848
  }
9646
9849
  function didProviderConfirmSend(result) {
@@ -9731,14 +9934,33 @@ async function handleReadChat(h, args) {
9731
9934
  }
9732
9935
  }
9733
9936
  const parsedRecord = parsedStatus && typeof parsedStatus === "object" ? parsedStatus : null;
9734
- const status = parsedRecord || adapter.getStatus();
9937
+ const adapterStatus = adapter.getStatus();
9938
+ const shouldPreferAdapterMessages = Array.isArray(adapterStatus.messages) && adapterStatus.messages.length > 0 && Array.isArray(parsedRecord?.messages) && adapterStatus.messages.length > parsedRecord.messages.length;
9939
+ const status = parsedRecord ? {
9940
+ ...parsedRecord,
9941
+ messages: shouldPreferAdapterMessages ? adapterStatus.messages : parsedRecord.messages,
9942
+ status: adapterStatus.status !== "idle" ? adapterStatus.status : parsedRecord.status || adapterStatus.status,
9943
+ activeModal: parsedRecord.activeModal || adapterStatus.activeModal
9944
+ } : adapterStatus;
9735
9945
  const title = typeof parsedRecord?.title === "string" ? parsedRecord.title : void 0;
9736
9946
  const providerSessionId = typeof parsedRecord?.providerSessionId === "string" ? parsedRecord.providerSessionId : void 0;
9737
9947
  if (status) {
9948
+ LOG.info("Command", `[read_chat] cli-like resolved provider=${adapter.cliType} target=${String(args?.targetSessionId || "")} adapterStatus=${String(adapterStatus.status || "")} parsedStatus=${String(parsedRecord?.status || "")} shouldPreferAdapterMessages=${String(shouldPreferAdapterMessages)} adapterMsgCount=${Array.isArray(adapterStatus.messages) ? adapterStatus.messages.length : 0} parsedMsgCount=${Array.isArray(parsedRecord?.messages) ? parsedRecord.messages.length : 0} returnedMsgCount=${Array.isArray(status.messages) ? status.messages.length : 0}`);
9738
9949
  return buildReadChatCommandResult({
9739
9950
  messages: status.messages || [],
9740
9951
  status: status.status,
9741
9952
  activeModal: status.activeModal,
9953
+ debugReadChat: {
9954
+ provider: adapter.cliType,
9955
+ targetSessionId: String(args?.targetSessionId || ""),
9956
+ adapterStatus: String(adapterStatus.status || ""),
9957
+ parsedStatus: String(parsedRecord?.status || ""),
9958
+ returnedStatus: String(status.status || ""),
9959
+ shouldPreferAdapterMessages,
9960
+ adapterMsgCount: Array.isArray(adapterStatus.messages) ? adapterStatus.messages.length : 0,
9961
+ parsedMsgCount: Array.isArray(parsedRecord?.messages) ? parsedRecord.messages.length : 0,
9962
+ returnedMsgCount: Array.isArray(status.messages) ? status.messages.length : 0
9963
+ },
9742
9964
  ...title ? { title } : {},
9743
9965
  ...providerSessionId ? { providerSessionId } : {}
9744
9966
  }, args);
@@ -11984,6 +12206,30 @@ import { createRequire } from "module";
11984
12206
  init_provider_cli_adapter();
11985
12207
  init_logger();
11986
12208
  init_chat_message_normalization();
12209
+ function normalizePersistableCliHistoryContent(content) {
12210
+ return flattenContent(content).replace(/\s+/g, " ").trim();
12211
+ }
12212
+ function buildPersistableCliHistorySignature(message) {
12213
+ return [
12214
+ String(message.role || ""),
12215
+ String(message.kind || ""),
12216
+ String(message.senderName || ""),
12217
+ normalizePersistableCliHistoryContent(message.content)
12218
+ ].join("|");
12219
+ }
12220
+ function buildIncrementalHistoryAppendMessages(previousMessages, currentMessages) {
12221
+ if (!Array.isArray(currentMessages) || currentMessages.length === 0) return [];
12222
+ if (!Array.isArray(previousMessages) || previousMessages.length === 0) return currentMessages;
12223
+ const previousSignatures = previousMessages.map(buildPersistableCliHistorySignature);
12224
+ const currentSignatures = currentMessages.map(buildPersistableCliHistorySignature);
12225
+ let sharedPrefixLength = 0;
12226
+ while (sharedPrefixLength < previousSignatures.length && sharedPrefixLength < currentSignatures.length && previousSignatures[sharedPrefixLength] === currentSignatures[sharedPrefixLength]) {
12227
+ sharedPrefixLength += 1;
12228
+ }
12229
+ if (sharedPrefixLength === currentSignatures.length) return [];
12230
+ if (sharedPrefixLength === previousSignatures.length) return currentMessages.slice(sharedPrefixLength);
12231
+ return currentMessages;
12232
+ }
11987
12233
  var CachedDatabaseSync = null;
11988
12234
  function getDatabaseSync() {
11989
12235
  if (CachedDatabaseSync) return CachedDatabaseSync;
@@ -12061,6 +12307,7 @@ var CliProviderInstance = class {
12061
12307
  appliedEffectKeys = /* @__PURE__ */ new Set();
12062
12308
  historyWriter;
12063
12309
  runtimeMessages = [];
12310
+ lastPersistedHistoryMessages = [];
12064
12311
  instanceId;
12065
12312
  suppressIdleHistoryReplay = false;
12066
12313
  errorMessage = void 0;
@@ -12101,6 +12348,13 @@ var CliProviderInstance = class {
12101
12348
  this.providerSessionId,
12102
12349
  this.instanceId
12103
12350
  );
12351
+ this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
12352
+ role: message.role,
12353
+ content: message.content,
12354
+ kind: message.kind,
12355
+ senderName: message.senderName,
12356
+ receivedAt: message.receivedAt
12357
+ }));
12104
12358
  this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
12105
12359
  if (restoredHistory.messages.length > 0) {
12106
12360
  this.adapter.seedCommittedMessages(
@@ -12232,15 +12486,24 @@ var CliProviderInstance = class {
12232
12486
  messagesToSave = messagesToSave.slice(0, lastIdx);
12233
12487
  }
12234
12488
  }
12235
- if (!shouldSkipReplayPersist && messagesToSave.length > 0) {
12489
+ const normalizedMessagesToSave = messagesToSave.map((message) => ({
12490
+ role: message.role,
12491
+ content: flattenContent(message.content),
12492
+ kind: typeof message.kind === "string" ? message.kind : void 0,
12493
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0,
12494
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
12495
+ }));
12496
+ if (!shouldSkipReplayPersist && normalizedMessagesToSave.length > 0) {
12497
+ const incrementalMessages = buildIncrementalHistoryAppendMessages(this.lastPersistedHistoryMessages, normalizedMessagesToSave);
12236
12498
  this.historyWriter.appendNewMessages(
12237
12499
  this.type,
12238
- messagesToSave,
12500
+ incrementalMessages,
12239
12501
  parsedStatus?.title || dirName,
12240
12502
  this.instanceId,
12241
12503
  this.providerSessionId
12242
12504
  );
12243
12505
  }
12506
+ this.lastPersistedHistoryMessages = normalizedMessagesToSave;
12244
12507
  }
12245
12508
  this.applyProviderResponse(parsedStatus, { phase: "immediate" });
12246
12509
  const surface = resolveProviderStateSurface({
@@ -12256,7 +12519,7 @@ var CliProviderInstance = class {
12256
12519
  activeChat: {
12257
12520
  id: `${this.type}_${this.workingDir}`,
12258
12521
  title: parsedStatus?.title || dirName,
12259
- status: parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : parsedStatus?.status || visibleStatus,
12522
+ status: parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : parsedStatus?.status || visibleStatus,
12260
12523
  messages: mergedMessages,
12261
12524
  activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
12262
12525
  inputContent: ""
@@ -12488,6 +12751,7 @@ var CliProviderInstance = class {
12488
12751
  }
12489
12752
  if (data.sessionEvent === "new_session") {
12490
12753
  this.runtimeMessages = [];
12754
+ this.lastPersistedHistoryMessages = [];
12491
12755
  this.suppressIdleHistoryReplay = false;
12492
12756
  this.adapter.clearHistory();
12493
12757
  }
@@ -24702,6 +24966,13 @@ init_logger();
24702
24966
  import {
24703
24967
  SessionHostClient
24704
24968
  } from "@adhdev/session-host-core";
24969
+ function shouldResumeAttachedSession(record) {
24970
+ if (!record) return false;
24971
+ if (record.lifecycle === "interrupted") return true;
24972
+ if (record.lifecycle !== "stopped") return false;
24973
+ if (record.meta?.restoredFromStorage === true) return true;
24974
+ return typeof record.meta?.runtimeRecoveryState === "string" && String(record.meta.runtimeRecoveryState).trim().length > 0;
24975
+ }
24705
24976
  var SessionHostRuntimeTransport = class {
24706
24977
  constructor(options) {
24707
24978
  this.options = options;
@@ -24875,7 +25146,7 @@ var SessionHostRuntimeTransport = class {
24875
25146
  payload: {}
24876
25147
  });
24877
25148
  const existingRecord = existingRecords.success && existingRecords.result ? existingRecords.result.find((item) => item.sessionId === this.options.runtimeId) || null : null;
24878
- if (existingRecord?.lifecycle === "interrupted") {
25149
+ if (shouldResumeAttachedSession(existingRecord)) {
24879
25150
  const resumeResponse = await this.client.request({
24880
25151
  type: "resume_session",
24881
25152
  payload: {
@@ -25174,6 +25445,8 @@ async function listHostedCliRuntimes(endpoint) {
25174
25445
  // src/session-host/startup-restore-policy.js
25175
25446
  function shouldAutoRestoreHostedSessionsOnStartup(env = process.env) {
25176
25447
  const raw = typeof env.ADHDEV_RESTORE_HOSTED_SESSIONS_ON_STARTUP === "string" ? env.ADHDEV_RESTORE_HOSTED_SESSIONS_ON_STARTUP.trim().toLowerCase() : "";
25448
+ if (!raw) return true;
25449
+ if (raw === "0" || raw === "false" || raw === "no") return false;
25177
25450
  return raw === "1" || raw === "true" || raw === "yes";
25178
25451
  }
25179
25452