@songsid/agend 2.1.4-beta.17 → 2.1.4-beta.19

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.
Files changed (49) hide show
  1. package/dist/backend/antigravity.d.ts +1 -0
  2. package/dist/backend/antigravity.js +4 -0
  3. package/dist/backend/antigravity.js.map +1 -1
  4. package/dist/backend/codex.d.ts +17 -0
  5. package/dist/backend/codex.js +51 -0
  6. package/dist/backend/codex.js.map +1 -1
  7. package/dist/backend/types.d.ts +10 -0
  8. package/dist/backend/types.js.map +1 -1
  9. package/dist/channel/adapters/discord.js +54 -20
  10. package/dist/channel/adapters/discord.js.map +1 -1
  11. package/dist/channel/adapters/telegram.js +5 -3
  12. package/dist/channel/adapters/telegram.js.map +1 -1
  13. package/dist/channel/types.d.ts +1 -1
  14. package/dist/cli.js +31 -21
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config-validator.js +3 -0
  17. package/dist/config-validator.js.map +1 -1
  18. package/dist/config.js +5 -1
  19. package/dist/config.js.map +1 -1
  20. package/dist/daemon.d.ts +58 -2
  21. package/dist/daemon.js +263 -29
  22. package/dist/daemon.js.map +1 -1
  23. package/dist/doctor.js +34 -27
  24. package/dist/doctor.js.map +1 -1
  25. package/dist/fleet-context.d.ts +4 -0
  26. package/dist/fleet-manager.d.ts +25 -0
  27. package/dist/fleet-manager.js +451 -88
  28. package/dist/fleet-manager.js.map +1 -1
  29. package/dist/locale.js +656 -320
  30. package/dist/locale.js.map +1 -1
  31. package/dist/outbound-handlers.js +5 -1
  32. package/dist/outbound-handlers.js.map +1 -1
  33. package/dist/restart-progress.d.ts +2 -0
  34. package/dist/restart-progress.js +3 -0
  35. package/dist/restart-progress.js.map +1 -1
  36. package/dist/scheduler/db.d.ts +5 -0
  37. package/dist/scheduler/db.js +35 -0
  38. package/dist/scheduler/db.js.map +1 -1
  39. package/dist/tips.d.ts +34 -0
  40. package/dist/tips.js +554 -0
  41. package/dist/tips.js.map +1 -0
  42. package/dist/topic-commands.d.ts +9 -2
  43. package/dist/topic-commands.js +144 -59
  44. package/dist/topic-commands.js.map +1 -1
  45. package/dist/types.d.ts +2 -0
  46. package/dist/ui/dashboard.html +55 -32
  47. package/dist/ui/settings.html +46 -37
  48. package/dist/ui/view.html +23 -15
  49. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGlF,SAAS,gBAAgB,CAAmB,MAAS,EAAE,MAAkB;IACvE,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAA6B,CAAC;IACxD,MAAM,YAAY,GAAG,MAAiC,CAAC;IAEvD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,IACE,SAAS,KAAK,IAAI;YAClB,OAAO,SAAS,KAAK,QAAQ;YAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YACzB,OAAO,SAAS,KAAK,QAAQ;YAC7B,SAAS,KAAK,IAAI;YAClB,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EACzB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAC5B,SAAmB,EACnB,SAA4B,CAC7B,CAAC;QACJ,CAAC;aAAM,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,MAAsB,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,kBAAkB,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,eAAe,EAAE,CAAC,EAAE,eAAe;IACnC,kBAAkB,EAAE,EAAE;IACtB,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ;CAC3D,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAuB;IACvD,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,EAAE;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAuB;IACvD,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAA8C;IAChF,gBAAgB,EAAE,CAAC,EAAE,iCAAiC;IACtD,gBAAgB,EAAE,IAAI,EAAE,gDAAgD;IACxE,eAAe,EAAE,KAAK,EAAE,4EAA4E;IACpG,aAAa,EAAE,KAAK,EAAE,oEAAoE;IAE1F,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE;QACR,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,GAAG;QACZ,IAAI,EAAE,EAAE;KACT;IACD,cAAc,EAAE;QACd,WAAW,EAAE,EAAE;QACf,OAAO,EAAE,aAAa;QACtB,WAAW,EAAE,GAAG;KACjB;IACD,gBAAgB,EAAE;QAChB,eAAe,EAAE,OAAO;QACxB,aAAa,EAAE,CAAC,EAAE,gEAAgE;KACnF;IACD,SAAS,EAAE,MAAM;CAClB,CAAC;AAEF,kFAAkF;AAClF,MAAM,UAAU,4BAA4B,CAC1C,gBAAyC,EAAE;IAE3C,OAAO,gBAAgB,CACrB,uBAAkD,EAClD,aAAa,CACd,CAAC;AACJ,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAAC,EAAU,EAAE,KAAa;IACxD,IAAI,CAAC;QACH,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uBAAuB,EAAE,uCAAuC,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C,0EAA0E;IAC1E,uFAAuF;IACvF,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,qCAAqC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEvF,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAWxB,CAAC;IAET,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACzC,CAAC;IAED,MAAM,aAAa,GAA4B,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrE,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAmC,EAAE,CAAC;IAErD,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7D,MAAM,MAAM,GAAG,gBAAgB,CAC7B,4BAA4B,CAAC,aAAa,CAAC,EAC3C,SAAS,CACiB,CAAC;QAE7B,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;YAC5D,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,kBAAkB,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,CAAC,iBAAiB,GAAG,UAAU,CAAC;QACxC,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,GAAG,MAAwB,CAAC;IAC7C,CAAC;IAED,sEAAsE;IACtE,MAAM,SAAS,GAAkC,EAAE,CAAC;IACpD,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3D,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9D,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,qCAAqC,CAAC,CAAC;YAC1E,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;QACxB,CAAC;IACH,CAAC;IAED,uFAAuF;IACvF,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO;QAClC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,OAAgB,EAAE;QACpH,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,QAAQ,GAA4B,MAAM,CAAC,QAAQ;QACvD,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,OAAgB,EAAE,CAAC,CAAC;QACjG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEhD,kFAAkF;IAClF,MAAM,cAAc,GAAG,aAAa,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;IAErE,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,QAAQ;QACR,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ,EAAE,aAAa;QACvB,SAAS;QACT,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;QACzB,SAAS;QACT,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,GAAG,EAAE,MAAM,CAAC,GAAG;KAChB,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C,+EAA+E;IAC/E,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,qCAAqC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACnE,CAAC,CAAC,MAAwB;QAC1B,CAAC,CAAC,EAAE,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/daemon.d.ts CHANGED
@@ -11,6 +11,8 @@ export declare function buildInstructionReloadNotice(binaryName: string, instanc
11
11
  export declare const DEFAULT_STUCK_TIMEOUT_MS: number;
12
12
  export declare const DEFAULT_STATE_IDLE_DEBOUNCE_MS = 2000;
13
13
  export declare const DEFAULT_STATE_SAFETY_SWEEP_MS = 60000;
14
+ /** Coalesce one cosmetic-redraw burst before comparing the visible pane. */
15
+ export declare const PERIODIC_REDRAW_PROBE_MS = 25;
14
16
  /** A foreground server/port-forward should hand control back or be acknowledged. */
15
17
  export declare const DEFAULT_BLOCKING_PROCESS_GRACE_MS: number;
16
18
  /**
@@ -348,6 +350,7 @@ export declare class Daemon extends EventEmitter {
348
350
  /** Fallback safety sweep used only when no shared tmux control client exists. */
349
351
  private instanceStateMonitorTimer;
350
352
  private instanceStateIdleTimer;
353
+ private instanceStateOutputProbeTimer;
351
354
  private instanceStateStuckTimer;
352
355
  private instanceStateOutputListener;
353
356
  private instanceStateOutputEventName;
@@ -374,6 +377,20 @@ export declare class Daemon extends EventEmitter {
374
377
  * would silently no-op exactly when we most need to stop feeding the CLI.
375
378
  */
376
379
  private pausePending;
380
+ /**
381
+ * An auth failure was detected and nothing has resolved it yet.
382
+ *
383
+ * Separate from `pausePending` because it must outlive it: the deferred pause
384
+ * clears `pausePending` as soon as it fires, but if that pause is refused the
385
+ * instance is still auth-broken and must not resume raising alarms.
386
+ */
387
+ private authFailureUnresolved;
388
+ /**
389
+ * Let the next pause() proceed from a stuck pane. Set only for the auth-deferred
390
+ * pause — see the pausePending consumption site for why waiting for idle there
391
+ * is waiting forever.
392
+ */
393
+ private pauseAllowStuck;
377
394
  private pauseWakeState;
378
395
  private pauseWakeTransition;
379
396
  private modelOverride;
@@ -385,6 +402,12 @@ export declare class Daemon extends EventEmitter {
385
402
  * and ⏳/👀/✅ reactions live here). It does NOT cover the other writers that
386
403
  * reach the pane — {@link paneWriteLock} does. */
387
404
  private pasteLock;
405
+ /**
406
+ * Fleet cancellation epoch. Queue entries retain the epoch they arrived with;
407
+ * advancing it invalidates only work received before the cancel, never a new
408
+ * user message which arrives immediately afterwards.
409
+ */
410
+ private deliveryEpoch;
388
411
  /** Mutual exclusion for *every* write into the pane, whichever subsystem it
389
412
  * comes from. See PaneWriteLock for why interleaving is destructive. */
390
413
  private readonly paneWriteLock;
@@ -533,6 +556,16 @@ export declare class Daemon extends EventEmitter {
533
556
  * that message, which is what the user asked for anyway.
534
557
  */
535
558
  sendEscape(): Promise<void>;
559
+ /**
560
+ * Drop every not-yet-started pane delivery which predates a user cancel.
561
+ * Promise chains cannot be removed, so entries become generation-checked
562
+ * no-ops. An entry already past its check is allowed to finish its current PTY
563
+ * transaction; the interrupt key still cancels the CLI generation.
564
+ */
565
+ clearPendingDeliveries(fleetEpoch?: number): void;
566
+ private captureDeliveryEpoch;
567
+ private isDeliveryEpochCurrent;
568
+ private queueRawPaste;
536
569
  /** Send the backend-specific graceful quit command/key sequence. */
537
570
  private sendQuitSequence;
538
571
  stop(): Promise<void>;
@@ -576,6 +609,13 @@ export declare class Daemon extends EventEmitter {
576
609
  /** Relay the pane's final text to the channel, marked as a daemon proxy reply. */
577
610
  private sendProxyReply;
578
611
  private clearInstanceStateIdleTimer;
612
+ private clearInstanceStateOutputProbeTimer;
613
+ /**
614
+ * Periodic status-line hooks can generate `%output` without changing a single
615
+ * visible cell. Probe the pane once per burst: a changed capture is real work;
616
+ * an identical ready capture remains idle and emits no state edge.
617
+ */
618
+ private scheduleInstanceStateOutputProbe;
579
619
  private clearInstanceStateStuckTimer;
580
620
  private scheduleInstanceStateStuckDeadline;
581
621
  private scheduleInstanceStateIdleCapture;
@@ -670,13 +710,20 @@ export declare class Daemon extends EventEmitter {
670
710
  * The steered text goes through formatInboundMessage so the agent sees a
671
711
  * normal inbound message, with a steering notice prepended for context.
672
712
  */
673
- steerMessage(content: string, meta: Record<string, string>): void;
713
+ steerMessage(content: string, meta: Record<string, string>, deliveryEpoch?: number): void;
714
+ /**
715
+ * /btw: submit a separately-labelled side question immediately while asking
716
+ * the agent not to change the active task. Claude's native `/btw` opens a
717
+ * branchAndResume panel whose answer never reaches the channel or transcript,
718
+ * so channel delivery intentionally uses a normal wrapped inbound instead.
719
+ */
720
+ btwMessage(content: string, meta: Record<string, string>, deliveryEpoch?: number): void;
674
721
  /**
675
722
  * Push an inbound channel message to a specific MCP session.
676
723
  * If targetSession is provided, only send to the matching socket.
677
724
  * Otherwise send to the instance's own session (this.name).
678
725
  */
679
- pushChannelMessage(content: string, meta: Record<string, string>, _targetSession?: string): void;
726
+ pushChannelMessage(content: string, meta: Record<string, string>, _targetSession?: string, deliveryEpoch?: number): void;
680
727
  /**
681
728
  * Deliver a single message and drive its status reactions:
682
729
  * ⏳ message_queued — CLI busy; queued locally or by the backend
@@ -694,6 +741,15 @@ export declare class Daemon extends EventEmitter {
694
741
  * serial in both cases so separate PTY writes can never overlap.
695
742
  */
696
743
  private deliverMessage;
744
+ /**
745
+ * A raw `%output` silence check is authoritative for ordinary TUIs. It is not
746
+ * for a backend such as agy whose status-line hook periodically redraws an
747
+ * identical screen. In that case trust an idle pane-state snapshot only when
748
+ * it was captured after both the latest output and the latest control-client
749
+ * observation reset.
750
+ */
751
+ private isPaneIdleForDelivery;
752
+ private waitForPaneIdleForDelivery;
697
753
  /**
698
754
  * The write half of a delivery: paste → Enter → confirm, with retries.
699
755
  *
package/dist/daemon.js CHANGED
@@ -47,6 +47,8 @@ export function buildInstructionReloadNotice(binaryName, instanceName, instanceD
47
47
  export const DEFAULT_STUCK_TIMEOUT_MS = 10 * 60_000;
48
48
  export const DEFAULT_STATE_IDLE_DEBOUNCE_MS = 2_000;
49
49
  export const DEFAULT_STATE_SAFETY_SWEEP_MS = 60_000;
50
+ /** Coalesce one cosmetic-redraw burst before comparing the visible pane. */
51
+ export const PERIODIC_REDRAW_PROBE_MS = 25;
50
52
  /** A foreground server/port-forward should hand control back or be acknowledged. */
51
53
  export const DEFAULT_BLOCKING_PROCESS_GRACE_MS = 2 * 60_000;
52
54
  const LAST_INBOUND_FILE = "last-inbound-at";
@@ -732,6 +734,7 @@ export class Daemon extends EventEmitter {
732
734
  /** Fallback safety sweep used only when no shared tmux control client exists. */
733
735
  instanceStateMonitorTimer = null;
734
736
  instanceStateIdleTimer = null;
737
+ instanceStateOutputProbeTimer = null;
735
738
  instanceStateStuckTimer = null;
736
739
  instanceStateOutputListener = null;
737
740
  instanceStateOutputEventName = null;
@@ -762,6 +765,20 @@ export class Daemon extends EventEmitter {
762
765
  * would silently no-op exactly when we most need to stop feeding the CLI.
763
766
  */
764
767
  pausePending = false;
768
+ /**
769
+ * An auth failure was detected and nothing has resolved it yet.
770
+ *
771
+ * Separate from `pausePending` because it must outlive it: the deferred pause
772
+ * clears `pausePending` as soon as it fires, but if that pause is refused the
773
+ * instance is still auth-broken and must not resume raising alarms.
774
+ */
775
+ authFailureUnresolved = false;
776
+ /**
777
+ * Let the next pause() proceed from a stuck pane. Set only for the auth-deferred
778
+ * pause — see the pausePending consumption site for why waiting for idle there
779
+ * is waiting forever.
780
+ */
781
+ pauseAllowStuck = false;
765
782
  pauseWakeState = "active";
766
783
  pauseWakeTransition = null;
767
784
  // Model failover: override model on next spawn when rate-limited
@@ -775,6 +792,12 @@ export class Daemon extends EventEmitter {
775
792
  * and ⏳/👀/✅ reactions live here). It does NOT cover the other writers that
776
793
  * reach the pane — {@link paneWriteLock} does. */
777
794
  pasteLock = Promise.resolve();
795
+ /**
796
+ * Fleet cancellation epoch. Queue entries retain the epoch they arrived with;
797
+ * advancing it invalidates only work received before the cancel, never a new
798
+ * user message which arrives immediately afterwards.
799
+ */
800
+ deliveryEpoch = 0;
778
801
  /** Mutual exclusion for *every* write into the pane, whichever subsystem it
779
802
  * comes from. See PaneWriteLock for why interleaving is destructive. */
780
803
  paneWriteLock = new PaneWriteLock();
@@ -1000,36 +1023,33 @@ export class Daemon extends EventEmitter {
1000
1023
  // Fleet manager routed a message to us (topic mode)
1001
1024
  const meta = msg.meta;
1002
1025
  const targetSession = msg.targetSession;
1026
+ const deliveryEpoch = this.captureDeliveryEpoch(msg.delivery_epoch);
1003
1027
  void this.wake().then(() => {
1004
- this.pushChannelMessage(msg.content, meta, targetSession);
1028
+ this.pushChannelMessage(msg.content, meta, targetSession, deliveryEpoch);
1005
1029
  }).catch(err => {
1006
1030
  this.logger.error({ err: err.message }, "Wake failed for inbound delivery");
1007
1031
  });
1008
1032
  }
1009
1033
  else if (msg.type === "raw_paste") {
1010
1034
  // Paste raw text directly to CLI without [user:] wrapping.
1011
- if (this.tmux) {
1012
- const rawText = msg.content;
1013
- this.pasteLock = this.pasteLock.then(async () => {
1014
- await this.wake();
1015
- await this.deliverMessage(rawText);
1016
- this.logger.debug({ text: rawText.slice(0, 100) }, "Raw paste delivered");
1017
- }).catch(err => {
1018
- this.logger.warn({ err: err.message }, "raw_paste delivery error");
1019
- });
1020
- }
1035
+ this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch));
1021
1036
  }
1022
1037
  else if (msg.type === "config_update") {
1023
1038
  this.applyConfigUpdate(msg.config);
1024
1039
  }
1025
1040
  else if (msg.type === "steer") {
1026
1041
  const meta = (msg.meta ?? {});
1027
- this.steerMessage(msg.content, meta);
1042
+ this.steerMessage(msg.content, meta, this.captureDeliveryEpoch(msg.delivery_epoch));
1043
+ }
1044
+ else if (msg.type === "btw") {
1045
+ const meta = (msg.meta ?? {});
1046
+ this.btwMessage(msg.content, meta, this.captureDeliveryEpoch(msg.delivery_epoch));
1028
1047
  }
1029
1048
  else if (msg.type === "fleet_schedule_trigger") {
1030
1049
  const payload = msg.payload;
1031
1050
  const meta = msg.meta;
1032
- void this.wake().then(() => this.pushChannelMessage(payload.message, meta)).catch(err => {
1051
+ const deliveryEpoch = this.captureDeliveryEpoch(msg.delivery_epoch);
1052
+ void this.wake().then(() => this.pushChannelMessage(payload.message, meta, undefined, deliveryEpoch)).catch(err => {
1033
1053
  this.logger.error({ err: err.message }, "Wake failed for scheduled delivery");
1034
1054
  });
1035
1055
  }
@@ -1767,6 +1787,7 @@ export class Daemon extends EventEmitter {
1767
1787
  if (seen > 0)
1768
1788
  this.lastErrorCount.set(Daemon.errorPatternKey(ep), seen);
1769
1789
  }
1790
+ this.authFailureUnresolved = false;
1770
1791
  this.clearErrorRecoveryGate();
1771
1792
  this.logger.info({ downtime_s: downtime }, "PTY error recovered — agent is ready again");
1772
1793
  this.emit("pty_recovered", { name: this.name, downtime_s: downtime });
@@ -1828,6 +1849,11 @@ export class Daemon extends EventEmitter {
1828
1849
  if (ep.action === "failover")
1829
1850
  this.lastFailoverAt = now;
1830
1851
  const message = this.resolveErrorMessage(pane, ep);
1852
+ // Remember an auth failure for as long as it is unresolved. An auth-broken
1853
+ // CLI cannot finish the turn it is holding, so every other detector in this
1854
+ // scan would otherwise keep reporting the same dead instance forever.
1855
+ if (ep.type === "auth_error")
1856
+ this.authFailureUnresolved = true;
1831
1857
  this.logger.warn({ errorType: ep.type, action: ep.action }, `PTY error detected: ${message}`);
1832
1858
  this.emit("pty_error", { name: this.name, ...ep, message });
1833
1859
  break; // Only handle first unsuppressed new error per scan
@@ -1871,6 +1897,46 @@ export class Daemon extends EventEmitter {
1871
1897
  const cancelKey = this.backend?.getCancelKey() ?? "Escape";
1872
1898
  await this.tmux?.sendSpecialKey(cancelKey);
1873
1899
  }
1900
+ /**
1901
+ * Drop every not-yet-started pane delivery which predates a user cancel.
1902
+ * Promise chains cannot be removed, so entries become generation-checked
1903
+ * no-ops. An entry already past its check is allowed to finish its current PTY
1904
+ * transaction; the interrupt key still cancels the CLI generation.
1905
+ */
1906
+ clearPendingDeliveries(fleetEpoch) {
1907
+ this.deliveryEpoch = fleetEpoch === undefined
1908
+ ? this.deliveryEpoch + 1
1909
+ : Math.max(this.deliveryEpoch, fleetEpoch);
1910
+ this.logger.info({ deliveryEpoch: this.deliveryEpoch }, "Pending delivery queue cleared by user cancel");
1911
+ }
1912
+ captureDeliveryEpoch(value) {
1913
+ const epoch = typeof value === "number" && Number.isSafeInteger(value) && value >= 0
1914
+ ? value
1915
+ : this.deliveryEpoch;
1916
+ if (epoch > this.deliveryEpoch)
1917
+ this.deliveryEpoch = epoch;
1918
+ return epoch;
1919
+ }
1920
+ isDeliveryEpochCurrent(epoch) {
1921
+ return epoch === this.deliveryEpoch;
1922
+ }
1923
+ queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch) {
1924
+ if (!this.tmux || !this.isDeliveryEpochCurrent(deliveryEpoch))
1925
+ return;
1926
+ this.pasteLock = this.pasteLock.then(async () => {
1927
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
1928
+ this.logger.info("Pending raw delivery dropped by user cancel");
1929
+ return;
1930
+ }
1931
+ await this.wake();
1932
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
1933
+ return;
1934
+ await this.deliverMessage(rawText, undefined, { deliveryEpoch });
1935
+ this.logger.debug({ text: rawText.slice(0, 100) }, "Raw paste delivered");
1936
+ }).catch(err => {
1937
+ this.logger.warn({ err: err.message }, "raw_paste delivery error");
1938
+ });
1939
+ }
1874
1940
  /** Send the backend-specific graceful quit command/key sequence. */
1875
1941
  async sendQuitSequence() {
1876
1942
  if (!this.tmux || !this.backend)
@@ -2018,7 +2084,14 @@ export class Daemon extends EventEmitter {
2018
2084
  if (this.getPauseWakeState() !== "active")
2019
2085
  return;
2020
2086
  }
2021
- if (this.instanceState !== "idle" || this.pasteQueueDepth > 0) {
2087
+ // `pauseAllowStuck` is consumed here, not merely read: it authorises exactly
2088
+ // one pause from a stuck pane (the auth-deferred one) and must never leak
2089
+ // into an ordinary idle-timeout pause of a busy instance.
2090
+ const allowStuck = this.pauseAllowStuck;
2091
+ this.pauseAllowStuck = false;
2092
+ const pausableState = this.instanceState === "idle"
2093
+ || (allowStuck && this.instanceState === "stuck");
2094
+ if (!pausableState || this.pasteQueueDepth > 0) {
2022
2095
  this.pauseRequested = false;
2023
2096
  return;
2024
2097
  }
@@ -2082,6 +2155,7 @@ export class Daemon extends EventEmitter {
2082
2155
  // An explicit wake (e.g. after the user re-logs in) cancels a deferred
2083
2156
  // auth pause — otherwise the instance would pause again the moment it idles.
2084
2157
  this.pausePending = false;
2158
+ this.authFailureUnresolved = false;
2085
2159
  if (this.pauseWakeState === "active")
2086
2160
  return;
2087
2161
  if (this.pauseWakeState === "pausing")
@@ -2178,8 +2252,13 @@ export class Daemon extends EventEmitter {
2178
2252
  if (snapshot.state !== "idle")
2179
2253
  this.pauseRequested = false;
2180
2254
  // A pause deferred by an auth failure: retry the moment the pane settles.
2181
- if (this.pausePending && snapshot.state === "idle" && this.pasteQueueDepth === 0) {
2255
+ // "stuck" counts as settled here, and that is the whole point — an expired
2256
+ // session cannot complete the turn it is holding, so the pane never returns
2257
+ // to idle and waiting for idle meant waiting forever. The instance stayed
2258
+ // live, kept its warm slot, and re-raised a hang alert on every scan.
2259
+ if (this.pausePending && (snapshot.state === "idle" || snapshot.state === "stuck") && this.pasteQueueDepth === 0) {
2182
2260
  this.pausePending = false;
2261
+ this.pauseAllowStuck = snapshot.state === "stuck";
2183
2262
  this.emit("auto_pause_requested", { name: this.name, idleSince: snapshot.stateChangedAt });
2184
2263
  return;
2185
2264
  }
@@ -2306,6 +2385,25 @@ export class Daemon extends EventEmitter {
2306
2385
  clearTimeout(this.instanceStateIdleTimer);
2307
2386
  this.instanceStateIdleTimer = null;
2308
2387
  }
2388
+ clearInstanceStateOutputProbeTimer() {
2389
+ if (this.instanceStateOutputProbeTimer)
2390
+ clearTimeout(this.instanceStateOutputProbeTimer);
2391
+ this.instanceStateOutputProbeTimer = null;
2392
+ }
2393
+ /**
2394
+ * Periodic status-line hooks can generate `%output` without changing a single
2395
+ * visible cell. Probe the pane once per burst: a changed capture is real work;
2396
+ * an identical ready capture remains idle and emits no state edge.
2397
+ */
2398
+ scheduleInstanceStateOutputProbe() {
2399
+ if (this.instanceStateOutputProbeTimer)
2400
+ return;
2401
+ this.instanceStateOutputProbeTimer = setTimeout(() => {
2402
+ this.instanceStateOutputProbeTimer = null;
2403
+ void this.captureAndEvaluateInstanceState("output_probe", this.instanceStateLastOutputAt);
2404
+ }, PERIODIC_REDRAW_PROBE_MS);
2405
+ this.instanceStateOutputProbeTimer.unref?.();
2406
+ }
2309
2407
  clearInstanceStateStuckTimer() {
2310
2408
  if (this.instanceStateStuckTimer)
2311
2409
  clearTimeout(this.instanceStateStuckTimer);
@@ -2350,6 +2448,8 @@ export class Daemon extends EventEmitter {
2350
2448
  if (this.statePollInFlight) {
2351
2449
  if (reason === "idle_debounce")
2352
2450
  this.scheduleInstanceStateIdleCapture();
2451
+ if (reason === "output_probe")
2452
+ this.scheduleInstanceStateOutputProbe();
2353
2453
  return;
2354
2454
  }
2355
2455
  this.statePollInFlight = true;
@@ -2358,9 +2458,19 @@ export class Daemon extends EventEmitter {
2358
2458
  const pane = await this.tmux.capturePane();
2359
2459
  // Output received while capture-pane was in flight makes this snapshot
2360
2460
  // stale. Its output handler has already armed a new debounce.
2361
- if ((expectedOutputAt > 0 && this.instanceStateLastOutputAt > expectedOutputAt)
2362
- || (this.instanceStateLastOutputAt > 0 && this.instanceStateLastOutputAt >= captureStartedAt))
2461
+ const outputMovedDuringCapture = (expectedOutputAt > 0 && this.instanceStateLastOutputAt > expectedOutputAt)
2462
+ || (this.instanceStateLastOutputAt > 0 && this.instanceStateLastOutputAt >= captureStartedAt);
2463
+ if (outputMovedDuringCapture) {
2464
+ if (reason === "output_probe") {
2465
+ // A second burst overtook the probe. Do not bless its stale capture as
2466
+ // idle: continuous output is the safer working signal, while the next
2467
+ // quiet probe/debounce can still prove that it was only repainting.
2468
+ this.applyInstanceStateSnapshot(this.instanceStateMachine.recordOutput(this.instanceStateLastOutputAt));
2469
+ this.scheduleInstanceStateIdleCapture();
2470
+ this.scheduleInstanceStateStuckDeadline(this.instanceStateLastOutputAt);
2471
+ }
2363
2472
  return;
2473
+ }
2364
2474
  const observedChangeAt = expectedOutputAt || captureStartedAt;
2365
2475
  // One observation per capture. This used to call observe() twice with the
2366
2476
  // same pane: the second call always saw an unchanged pane, so the
@@ -2412,6 +2522,14 @@ export class Daemon extends EventEmitter {
2412
2522
  if (!windowId || event.windowId !== windowId || !this.instanceStateMachine)
2413
2523
  return;
2414
2524
  this.instanceStateLastOutputAt = event.at;
2525
+ if (this.backend?.hasPeriodicPaneRedraw?.() === true && this.instanceState === "idle") {
2526
+ // Do not emit idle→working solely because agy repainted an identical
2527
+ // footer. The short capture probe below still observes genuine pane
2528
+ // changes promptly and moves the state to working.
2529
+ this.scheduleInstanceStateOutputProbe();
2530
+ this.scheduleInstanceStateIdleCapture();
2531
+ return;
2532
+ }
2415
2533
  const snapshot = this.instanceStateMachine.recordOutput(event.at);
2416
2534
  this.applyInstanceStateSnapshot(snapshot);
2417
2535
  this.scheduleInstanceStateIdleCapture();
@@ -2462,6 +2580,7 @@ export class Daemon extends EventEmitter {
2462
2580
  stopInstanceStateMonitor() {
2463
2581
  this.instanceStateMonitorActive = false;
2464
2582
  this.clearInstanceStateIdleTimer();
2583
+ this.clearInstanceStateOutputProbeTimer();
2465
2584
  this.clearInstanceStateStuckTimer();
2466
2585
  if (this.instanceStateMonitorTimer)
2467
2586
  clearInterval(this.instanceStateMonitorTimer);
@@ -2496,6 +2615,15 @@ export class Daemon extends EventEmitter {
2496
2615
  this.logger.debug(diagnostic, "Suppressing stuck notification without pending work");
2497
2616
  return;
2498
2617
  }
2618
+ // An auth-broken instance IS stuck, and stays stuck: it cannot finish the
2619
+ // turn it is holding, so pendingWork never clears and this fired on every
2620
+ // scan forever. The notification is also useless — it offers Restart/Wait
2621
+ // and asks General for help, but only the user re-logging in fixes this, and
2622
+ // they have already been told exactly that by the auth alert.
2623
+ if (this.authFailureUnresolved) {
2624
+ this.logger.debug(diagnostic, "Suppressing stuck notification — unresolved auth failure (re-login required)");
2625
+ return;
2626
+ }
2499
2627
  this.logger.warn(diagnostic, "Instance pane stuck with pending work");
2500
2628
  this.hangDetector?.emit("hang", { unchangedForMs: snapshot.unchangedForMs });
2501
2629
  }
@@ -2785,7 +2913,9 @@ export class Daemon extends EventEmitter {
2785
2913
  * The steered text goes through formatInboundMessage so the agent sees a
2786
2914
  * normal inbound message, with a steering notice prepended for context.
2787
2915
  */
2788
- steerMessage(content, meta) {
2916
+ steerMessage(content, meta, deliveryEpoch = this.deliveryEpoch) {
2917
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
2918
+ return;
2789
2919
  this.updateLastChat(meta.chat_id, meta.thread_id, meta.adapter_id);
2790
2920
  this.pendingWork.recordInbound();
2791
2921
  this.recordRecentUserMessage(content, meta);
@@ -2797,20 +2927,60 @@ export class Daemon extends EventEmitter {
2797
2927
  ? { chatId: meta.thread_id || chatId, messageId }
2798
2928
  : undefined;
2799
2929
  this.steerLock = this.steerLock.then(async () => {
2930
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
2931
+ return;
2800
2932
  await this.wake();
2801
- if (await this.deliverMessage(formatted, status, { steer: true })) {
2933
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
2934
+ return;
2935
+ if (await this.deliverMessage(formatted, status, { steer: true, deliveryEpoch })) {
2802
2936
  this.markTurnStarted(meta, formatted);
2803
2937
  }
2804
2938
  }).catch(err => {
2805
2939
  this.logger.warn({ err: err.message }, "steer delivery error");
2806
2940
  });
2807
2941
  }
2942
+ /**
2943
+ * /btw: submit a separately-labelled side question immediately while asking
2944
+ * the agent not to change the active task. Claude's native `/btw` opens a
2945
+ * branchAndResume panel whose answer never reaches the channel or transcript,
2946
+ * so channel delivery intentionally uses a normal wrapped inbound instead.
2947
+ */
2948
+ btwMessage(content, meta, deliveryEpoch = this.deliveryEpoch) {
2949
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
2950
+ return;
2951
+ this.updateLastChat(meta.chat_id, meta.thread_id, meta.adapter_id);
2952
+ this.pendingWork.recordInbound();
2953
+ this.recordRecentUserMessage(content, meta);
2954
+ const formatted = "[BTW — side question from the user. Answer it separately without changing or interrupting the CURRENT work.]\n"
2955
+ + this.formatInboundMessage(content, meta);
2956
+ const chatId = meta.chat_id;
2957
+ const messageId = meta.message_id;
2958
+ const status = (chatId && messageId)
2959
+ ? { chatId: meta.thread_id || chatId, messageId }
2960
+ : undefined;
2961
+ this.steerLock = this.steerLock.then(async () => {
2962
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
2963
+ return;
2964
+ await this.wake();
2965
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
2966
+ return;
2967
+ if (await this.deliverMessage(formatted, status, { steer: true, deliveryEpoch })) {
2968
+ this.markTurnStarted(meta, formatted);
2969
+ }
2970
+ }).catch(err => {
2971
+ this.logger.warn({ err: err.message }, "btw delivery error");
2972
+ });
2973
+ }
2808
2974
  /**
2809
2975
  * Push an inbound channel message to a specific MCP session.
2810
2976
  * If targetSession is provided, only send to the matching socket.
2811
2977
  * Otherwise send to the instance's own session (this.name).
2812
2978
  */
2813
- pushChannelMessage(content, meta, _targetSession) {
2979
+ pushChannelMessage(content, meta, _targetSession, deliveryEpoch = this.deliveryEpoch) {
2980
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
2981
+ this.logger.info("Pending channel delivery dropped by user cancel");
2982
+ return;
2983
+ }
2814
2984
  if (!this.tmux) {
2815
2985
  this.logger.warn("Cannot push channel message: tmux not running");
2816
2986
  return;
@@ -2844,7 +3014,9 @@ export class Daemon extends EventEmitter {
2844
3014
  const rawText = content.slice(5);
2845
3015
  this.logger.info({ user }, "Raw paste from topic mode user");
2846
3016
  this.pasteLock = this.pasteLock.then(async () => {
2847
- if (await this.deliverMessage(rawText))
3017
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
3018
+ return;
3019
+ if (await this.deliverMessage(rawText, undefined, { deliveryEpoch }))
2848
3020
  this.markTurnStarted(meta, rawText);
2849
3021
  }).catch(err => {
2850
3022
  this.logger.warn({ err: err.message }, "pasteLock raw delivery error");
@@ -2854,8 +3026,7 @@ export class Daemon extends EventEmitter {
2854
3026
  const formatted = this.formatInboundMessage(content, meta);
2855
3027
  // Serialize deliveries: each message waits for the previous to complete,
2856
3028
  // and each waits for the CLI to be idle before pasting. Messages are never
2857
- // dropped for age — a long-busy CLI just queues them until it frees up
2858
- // (the user can press Cancel to interrupt and let the queue drain sooner).
3029
+ // dropped for age — only an explicit user Cancel invalidates queued work.
2859
3030
  const chatId = meta.chat_id;
2860
3031
  const messageId = meta.message_id;
2861
3032
  const wasQueued = this.pasteQueueDepth > 0;
@@ -2868,12 +3039,20 @@ export class Daemon extends EventEmitter {
2868
3039
  }
2869
3040
  this.pasteLock = this.pasteLock.then(async () => {
2870
3041
  try {
3042
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
3043
+ this.logger.info("Pending channel delivery dropped by user cancel");
3044
+ return;
3045
+ }
2871
3046
  if (this.config.pre_task_command) {
2872
- await this.deliverMessage(this.config.pre_task_command);
3047
+ await this.deliverMessage(this.config.pre_task_command, undefined, { deliveryEpoch });
3048
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
3049
+ return;
2873
3050
  }
2874
3051
  if (this.pendingInstructionsNotice) {
2875
3052
  this.pendingInstructionsNotice = false;
2876
- await this.deliverMessage(buildInstructionReloadNotice(this.backend?.binaryName ?? "unknown", this.name, this.instanceDir));
3053
+ await this.deliverMessage(buildInstructionReloadNotice(this.backend?.binaryName ?? "unknown", this.name, this.instanceDir), undefined, { deliveryEpoch });
3054
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch))
3055
+ return;
2877
3056
  }
2878
3057
  const status = (chatId && messageId)
2879
3058
  ? { chatId: meta.thread_id || chatId, messageId }
@@ -2881,7 +3060,7 @@ export class Daemon extends EventEmitter {
2881
3060
  // A fresh delivery begins a fresh turn — its bubble must not inherit
2882
3061
  // the previous turn's tool list.
2883
3062
  this.resetToolProgress();
2884
- if (await this.deliverMessage(formatted, status))
3063
+ if (await this.deliverMessage(formatted, status, { deliveryEpoch }))
2885
3064
  this.markTurnStarted(meta, formatted);
2886
3065
  }
2887
3066
  finally {
@@ -2909,6 +3088,10 @@ export class Daemon extends EventEmitter {
2909
3088
  * serial in both cases so separate PTY writes can never overlap.
2910
3089
  */
2911
3090
  async deliverMessage(formatted, status, opts) {
3091
+ const cancelled = () => opts?.deliveryEpoch !== undefined
3092
+ && !this.isDeliveryEpochCurrent(opts.deliveryEpoch);
3093
+ if (cancelled())
3094
+ return false;
2912
3095
  // Sanitize unclosed code fences — they cause CLI to wait for closure on Enter
2913
3096
  const fenceCount = (formatted.match(/```/g) || []).length;
2914
3097
  if (fenceCount % 2 !== 0) {
@@ -2917,13 +3100,15 @@ export class Daemon extends EventEmitter {
2917
3100
  }
2918
3101
  // Before anything reads the window id: a spawn in progress is about to change it.
2919
3102
  await this.waitForSpawnToSettle();
3103
+ if (cancelled())
3104
+ return false;
2920
3105
  let windowId = this.getWindowId();
2921
3106
  const supportsQueuedInput = this.backend?.supportsQueuedInput?.() === true;
2922
3107
  let handingOffToNativeQueue = false;
2923
3108
  // If the CLI is busy, either hand the complete submission to its native input
2924
3109
  // queue or wait for idle. Native queue support is an explicit backend capability:
2925
3110
  // normal Enter input has steering/interrupt semantics in several other CLIs.
2926
- if (windowId && this.controlClient && !this.controlClient.isIdle(windowId)) {
3111
+ if (windowId && this.controlClient && !this.isPaneIdleForDelivery(windowId)) {
2927
3112
  if (status)
2928
3113
  this.emit("message_queued", status);
2929
3114
  if (supportsQueuedInput || opts?.steer) {
@@ -2937,7 +3122,9 @@ export class Daemon extends EventEmitter {
2937
3122
  }
2938
3123
  else {
2939
3124
  this.logger.debug("CLI busy — queuing message until idle");
2940
- const becameIdle = await this.controlClient.waitUntilIdle(windowId);
3125
+ const becameIdle = await this.waitForPaneIdleForDelivery(windowId);
3126
+ if (cancelled())
3127
+ return false;
2941
3128
  if (!becameIdle) {
2942
3129
  // The pane never freed up. Report the failure instead of pasting into a
2943
3130
  // wedged CLI (where the text would sit unsubmitted and the next message
@@ -2953,7 +3140,54 @@ export class Daemon extends EventEmitter {
2953
3140
  // held under the pane lock — holding it across the idle wait (up to 30 min)
2954
3141
  // would starve the runtime-dialog dismisser, which is often the very thing
2955
3142
  // that would let the pane go idle again.
2956
- return this.paneWriteLock.run(() => this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status));
3143
+ return this.paneWriteLock.run(async () => {
3144
+ if (cancelled())
3145
+ return false;
3146
+ return this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status);
3147
+ });
3148
+ }
3149
+ /**
3150
+ * A raw `%output` silence check is authoritative for ordinary TUIs. It is not
3151
+ * for a backend such as agy whose status-line hook periodically redraws an
3152
+ * identical screen. In that case trust an idle pane-state snapshot only when
3153
+ * it was captured after both the latest output and the latest control-client
3154
+ * observation reset.
3155
+ */
3156
+ isPaneIdleForDelivery(windowId) {
3157
+ if (!this.controlClient)
3158
+ return true;
3159
+ if (this.controlClient.isIdle(windowId))
3160
+ return true;
3161
+ if (this.backend?.hasPeriodicPaneRedraw?.() !== true || this.instanceState !== "idle")
3162
+ return false;
3163
+ const lastOutputAt = this.controlClient.getLastOutputAt(windowId);
3164
+ if (lastOutputAt === undefined || !this.instanceStateMachine)
3165
+ return false;
3166
+ const observedAt = this.instanceStateMachine.snapshot().observedAt;
3167
+ return observedAt >= lastOutputAt
3168
+ && observedAt >= this.controlClient.getObservationResetAt();
3169
+ }
3170
+ waitForPaneIdleForDelivery(windowId, timeoutMs = 30 * 60_000) {
3171
+ if (!this.controlClient)
3172
+ return Promise.resolve(true);
3173
+ if (this.backend?.hasPeriodicPaneRedraw?.() !== true) {
3174
+ return this.controlClient.waitUntilIdle(windowId, timeoutMs);
3175
+ }
3176
+ if (this.isPaneIdleForDelivery(windowId))
3177
+ return Promise.resolve(true);
3178
+ return new Promise(resolve => {
3179
+ const check = setInterval(() => {
3180
+ if (!this.isPaneIdleForDelivery(windowId))
3181
+ return;
3182
+ clearInterval(check);
3183
+ clearTimeout(timer);
3184
+ resolve(true);
3185
+ }, 50);
3186
+ const timer = setTimeout(() => {
3187
+ clearInterval(check);
3188
+ resolve(false);
3189
+ }, timeoutMs);
3190
+ });
2957
3191
  }
2958
3192
  /**
2959
3193
  * The write half of a delivery: paste → Enter → confirm, with retries.