@songsid/agend 2.1.8-beta.4 → 2.1.8-beta.6

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/daemon.js CHANGED
@@ -3157,13 +3157,16 @@ export class Daemon extends EventEmitter {
3157
3157
  /**
3158
3158
  * Answer a FleetManager state query. Most callers only need the cached state,
3159
3159
  * but lifecycle decisions such as post-reply Cancel retirement need an
3160
- * authoritative pane observation: after startup the last transition can stay
3161
- * "working" even though the CLI has since settled without emitting another
3162
- * control-mode output record.
3160
+ * authoritative pane observation, as do delivery idle gates: after startup
3161
+ * the last transition can stay "working" even though the CLI has since
3162
+ * settled without emitting another control-mode output record.
3163
3163
  */
3164
3164
  async respondToInstanceStateQuery(msg, socket) {
3165
3165
  try {
3166
- if (msg.refresh === true) {
3166
+ if (msg.deliveryIdle === true) {
3167
+ await this.captureAndEvaluateInstanceState("delivery_idle_gate");
3168
+ }
3169
+ else if (msg.refresh === true) {
3167
3170
  await this.captureAndEvaluateInstanceState("state_query");
3168
3171
  }
3169
3172
  const snapshot = this.getInstanceStateSnapshot();
@@ -3832,14 +3835,39 @@ export class Daemon extends EventEmitter {
3832
3835
  }
3833
3836
  this.statePollInFlight = true;
3834
3837
  const captureStartedAt = Date.now();
3838
+ const captureEpoch = `${this.spawnGeneration}:${this.launchAttempt}`;
3839
+ const currentDeliveryCapture = () => captureEpoch === `${this.spawnGeneration}:${this.launchAttempt}`
3840
+ && !this.spawning && !this.runtimeMonitorsFrozen && this.instanceStateMonitorActive;
3835
3841
  try {
3836
3842
  const pane = await this.tmux.capturePane();
3843
+ // An old capture must not retire a new launch's transient guard either.
3844
+ if (reason === "delivery_idle_gate" && !currentDeliveryCapture()) {
3845
+ this.resetFooterFallback();
3846
+ return;
3847
+ }
3848
+ // Delivery's unknown-footer proof also awaits the TTY mode. Validate
3849
+ // output and launch freshness AFTER both awaits, before accepting it.
3850
+ const deliveryCandidate = reason === "delivery_idle_gate"
3851
+ ? await this.probeDeliveryIdleFallback(pane) : null;
3852
+ if (reason === "delivery_idle_gate" && !currentDeliveryCapture()) {
3853
+ this.resetFooterFallback();
3854
+ return;
3855
+ }
3837
3856
  this.updateInputBlockedState(pane);
3857
+ // A monitor/lifecycle capture can see a busy moment between fleet polls.
3858
+ // Even a stale capture of changed content disproves continuous stability;
3859
+ // invalidate it before the output-freshness check can return early.
3860
+ if (currentDeliveryCapture() && this.footerFallbackPaneKey !== null
3861
+ && pane.replace(/⋆/gu, "").replace(/[ \t]+$/gmu, "") !== this.footerFallbackPaneKey) {
3862
+ this.resetFooterFallback();
3863
+ }
3838
3864
  // Output received while capture-pane was in flight makes this snapshot
3839
3865
  // stale. Its output handler has already armed a new debounce.
3840
3866
  const outputMovedDuringCapture = (expectedOutputAt > 0 && this.instanceStateLastOutputAt > expectedOutputAt)
3841
3867
  || (this.instanceStateLastOutputAt > 0 && this.instanceStateLastOutputAt >= captureStartedAt);
3842
3868
  if (outputMovedDuringCapture) {
3869
+ if (reason === "delivery_idle_gate")
3870
+ this.resetFooterFallback();
3843
3871
  if (reason === "output_probe") {
3844
3872
  // A second burst overtook the probe. Do not bless its stale capture as
3845
3873
  // idle: continuous output is the safer working signal, while the next
@@ -3850,6 +3878,9 @@ export class Daemon extends EventEmitter {
3850
3878
  }
3851
3879
  return;
3852
3880
  }
3881
+ // Advance the shared 10s timer only for a fresh capture of this launch.
3882
+ const deliveryFallback = deliveryCandidate === true
3883
+ ? this.deliveryInputReadyPane(pane) : deliveryCandidate;
3853
3884
  const observedChangeAt = expectedOutputAt || captureStartedAt;
3854
3885
  // One observation per capture. This used to call observe() twice with the
3855
3886
  // same pane: the second call always saw an unchanged pane, so the
@@ -3865,7 +3896,17 @@ export class Daemon extends EventEmitter {
3865
3896
  // observe twice, which silently disabled the "content moved" branch.
3866
3897
  const structuredPeriodicIdle = this.backend?.isPeriodicRedrawIdlePane;
3867
3898
  let snapshot;
3868
- if (structuredPeriodicIdle && reason === "output_probe") {
3899
+ if (deliveryFallback !== null) {
3900
+ // A missing footer cannot fall through to a broad ready match. All
3901
+ // of the same #1032 conditions must hold continuously for 10 seconds.
3902
+ snapshot = this.instanceStateMachine.observe(pane, Date.now(), {
3903
+ settled: true,
3904
+ changeAt: observedChangeAt,
3905
+ forceReady: deliveryFallback,
3906
+ forceBusy: !deliveryFallback || paneActivity !== null,
3907
+ });
3908
+ }
3909
+ else if (structuredPeriodicIdle && reason === "output_probe") {
3869
3910
  const idlePane = structuredPeriodicIdle.call(this.backend, pane);
3870
3911
  if (idlePane) {
3871
3912
  if (this.instanceState === "idle") {
@@ -4006,6 +4047,8 @@ export class Daemon extends EventEmitter {
4006
4047
  }
4007
4048
  }
4008
4049
  catch (err) {
4050
+ if (reason === "delivery_idle_gate")
4051
+ this.resetFooterFallback();
4009
4052
  this.logger.debug({ err: err.message, reason }, "Instance state capture failed");
4010
4053
  }
4011
4054
  finally {
@@ -5244,6 +5287,35 @@ export class Daemon extends EventEmitter {
5244
5287
  return !!this.backend?.isDeliveryInputReadyPane
5245
5288
  && this.inputTransientGuardGeneration === this.spawnGeneration;
5246
5289
  }
5290
+ /**
5291
+ * null preserves native readiness; false is an ineligible unknown layout.
5292
+ * true means the caller may advance the timer after checking freshness.
5293
+ * Share #1032's candidate/timer with the later paste gate, rather than
5294
+ * reporting idle after 10s only to make that gate wait another 10s (#1035).
5295
+ */
5296
+ async probeDeliveryIdleFallback(pane) {
5297
+ const inputReady = this.backend?.isDeliveryInputReadyPane;
5298
+ const stableUnknown = this.backend?.isStableUnknownLayoutIdlePane;
5299
+ if (!inputReady || !stableUnknown || inputReady.call(this.backend, pane)) {
5300
+ this.resetFooterFallback();
5301
+ return null;
5302
+ }
5303
+ const busy = this.backend?.getBusyPattern?.();
5304
+ if (busy)
5305
+ busy.lastIndex = 0;
5306
+ if (!stableUnknown.call(this.backend, pane) || busy?.test(pane)
5307
+ || this.backend?.getPaneActivity?.(pane)
5308
+ || this.deliveryBlockingDialogs().some(dialog => Daemon.dialogMatches(dialog, pane))
5309
+ || this.inputTransientInPane(pane)) {
5310
+ this.resetFooterFallback();
5311
+ return false;
5312
+ }
5313
+ if (await this.tmux?.getPaneInputMode?.() !== "raw") {
5314
+ this.resetFooterFallback();
5315
+ return false;
5316
+ }
5317
+ return true;
5318
+ }
5247
5319
  /** Codex can paint a prompt before the TTY enters raw mode. Both are required. */
5248
5320
  async hasPositiveDeliveryInput() {
5249
5321
  if (!this.needsStartupInputProof())