@songsid/agend 2.2.0-alpha.1 → 2.2.0-alpha.2

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 (150) hide show
  1. package/README.md +2 -0
  2. package/README.zh-TW.md +2 -0
  3. package/dist/agent-cli.js +6 -1
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +68 -12
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-token-header.d.ts +13 -0
  8. package/dist/agent-token-header.js +16 -0
  9. package/dist/agent-token-header.js.map +1 -0
  10. package/dist/backend/antigravity.js +4 -3
  11. package/dist/backend/antigravity.js.map +1 -1
  12. package/dist/backend/grok.js +1 -1
  13. package/dist/backend/grok.js.map +1 -1
  14. package/dist/backend/kiro.d.ts +7 -0
  15. package/dist/backend/kiro.js +82 -33
  16. package/dist/backend/kiro.js.map +1 -1
  17. package/dist/backend/muse.js +1 -1
  18. package/dist/backend/muse.js.map +1 -1
  19. package/dist/backend/types.d.ts +6 -0
  20. package/dist/backend/types.js.map +1 -1
  21. package/dist/cache-analysis.d.ts +92 -0
  22. package/dist/cache-analysis.js +147 -0
  23. package/dist/cache-analysis.js.map +1 -0
  24. package/dist/cache-ledger.d.ts +108 -0
  25. package/dist/cache-ledger.js +230 -0
  26. package/dist/cache-ledger.js.map +1 -0
  27. package/dist/cache-prices.d.ts +32 -0
  28. package/dist/cache-prices.js +73 -0
  29. package/dist/cache-prices.js.map +1 -0
  30. package/dist/cache-scan.d.ts +39 -0
  31. package/dist/cache-scan.js +115 -0
  32. package/dist/cache-scan.js.map +1 -0
  33. package/dist/cache-service.d.ts +115 -0
  34. package/dist/cache-service.js +417 -0
  35. package/dist/cache-service.js.map +1 -0
  36. package/dist/cli-entry.d.ts +19 -0
  37. package/dist/cli-entry.js +36 -0
  38. package/dist/cli-entry.js.map +1 -0
  39. package/dist/cli.js +251 -24
  40. package/dist/cli.js.map +1 -1
  41. package/dist/daemon.d.ts +6 -0
  42. package/dist/daemon.js +90 -37
  43. package/dist/daemon.js.map +1 -1
  44. package/dist/event-log.d.ts +4 -1
  45. package/dist/event-log.js +6 -3
  46. package/dist/event-log.js.map +1 -1
  47. package/dist/fleet-control-audit.d.ts +5 -0
  48. package/dist/fleet-control-audit.js +7 -0
  49. package/dist/fleet-control-audit.js.map +1 -1
  50. package/dist/fleet-level-config.js +22 -0
  51. package/dist/fleet-level-config.js.map +1 -1
  52. package/dist/fleet-manager.d.ts +46 -0
  53. package/dist/fleet-manager.js +287 -40
  54. package/dist/fleet-manager.js.map +1 -1
  55. package/dist/full-restart.js +2 -3
  56. package/dist/full-restart.js.map +1 -1
  57. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
  58. package/dist/install-lock.d.ts +49 -0
  59. package/dist/install-lock.js +200 -0
  60. package/dist/install-lock.js.map +1 -0
  61. package/dist/instance-config-impact.js +3 -2
  62. package/dist/instance-config-impact.js.map +1 -1
  63. package/dist/locale.js +14 -0
  64. package/dist/locale.js.map +1 -1
  65. package/dist/outbound-schemas.js +1 -1
  66. package/dist/outbound-schemas.js.map +1 -1
  67. package/dist/package-preimage.d.ts +26 -0
  68. package/dist/package-preimage.js +96 -0
  69. package/dist/package-preimage.js.map +1 -0
  70. package/dist/public-web-gateway.js +2 -2
  71. package/dist/public-web-gateway.js.map +1 -1
  72. package/dist/quickstart.js +10 -4
  73. package/dist/quickstart.js.map +1 -1
  74. package/dist/restart-guard.d.ts +52 -0
  75. package/dist/restart-guard.js +109 -0
  76. package/dist/restart-guard.js.map +1 -0
  77. package/dist/scheduler/db.js +6 -2
  78. package/dist/scheduler/db.js.map +1 -1
  79. package/dist/service-activation.d.ts +47 -5
  80. package/dist/service-activation.js +156 -51
  81. package/dist/service-activation.js.map +1 -1
  82. package/dist/service-installer.d.ts +56 -1
  83. package/dist/service-installer.js +134 -12
  84. package/dist/service-installer.js.map +1 -1
  85. package/dist/service-plan.d.ts +65 -0
  86. package/dist/service-plan.js +108 -0
  87. package/dist/service-plan.js.map +1 -0
  88. package/dist/settings-api.js +46 -5
  89. package/dist/settings-api.js.map +1 -1
  90. package/dist/settings-change.js +34 -6
  91. package/dist/settings-change.js.map +1 -1
  92. package/dist/setup-host.js +2 -3
  93. package/dist/setup-host.js.map +1 -1
  94. package/dist/setup-wizard.js +2 -1
  95. package/dist/setup-wizard.js.map +1 -1
  96. package/dist/sqlite-open-errors.d.ts +12 -0
  97. package/dist/sqlite-open-errors.js +13 -0
  98. package/dist/sqlite-open-errors.js.map +1 -0
  99. package/dist/tmux-control.d.ts +22 -0
  100. package/dist/tmux-control.js +89 -5
  101. package/dist/tmux-control.js.map +1 -1
  102. package/dist/topic-commands.js +14 -5
  103. package/dist/topic-commands.js.map +1 -1
  104. package/dist/ui/chat-commands.js +43 -0
  105. package/dist/ui/chat-render.js +2 -2
  106. package/dist/ui/chat-store.js +30 -1
  107. package/dist/ui/chat-strings.js +22 -2
  108. package/dist/ui/chat-thread.js +47 -10
  109. package/dist/ui/fleet-cache.js +168 -0
  110. package/dist/ui/panel-chat.js +104 -18
  111. package/dist/ui/panel-fleet.js +26 -12
  112. package/dist/ui/panel-settings.js +12 -8
  113. package/dist/ui/preview-panel.js +202 -0
  114. package/dist/ui/preview.js +7 -3
  115. package/dist/ui/settings-strings.js +4 -2
  116. package/dist/ui/shared/app-route.js +1 -1
  117. package/dist/ui/shared/app-shell.js +6 -4
  118. package/dist/ui/shared/app.css +147 -3
  119. package/dist/ui/shared/signin.js +13 -1
  120. package/dist/ui/shared/ui-icons.js +2 -0
  121. package/dist/ui/signin.html +2 -1
  122. package/dist/update-dispatch.d.ts +28 -0
  123. package/dist/update-dispatch.js +73 -0
  124. package/dist/update-dispatch.js.map +1 -0
  125. package/dist/update-install.d.ts +65 -7
  126. package/dist/update-install.js +126 -26
  127. package/dist/update-install.js.map +1 -1
  128. package/dist/web-api.d.ts +14 -0
  129. package/dist/web-api.js +65 -0
  130. package/dist/web-api.js.map +1 -1
  131. package/dist/web-commands.d.ts +77 -0
  132. package/dist/web-commands.js +81 -0
  133. package/dist/web-commands.js.map +1 -0
  134. package/dist/web-shell-routes.d.ts +1 -1
  135. package/dist/web-shell-routes.js +1 -1
  136. package/dist/web-shell-routes.js.map +1 -1
  137. package/launcher/agend +110 -0
  138. package/launcher/agend-agent +110 -0
  139. package/launcher/agend-agent.cjs +4 -0
  140. package/launcher/agend.cjs +4 -0
  141. package/launcher/install-admission.cjs +105 -0
  142. package/launcher/launch.cjs +64 -0
  143. package/launcher/postinstall.cjs +114 -0
  144. package/launcher/run-in-process.cjs +13 -0
  145. package/launcher/runtime-platform.cjs +86 -0
  146. package/launcher/runtime-select.cjs +217 -0
  147. package/package.json +12 -4
  148. package/scripts/preinstall-guard.cjs +40 -21
  149. package/templates/launchd.plist.ejs +2 -2
  150. package/templates/systemd.service.ejs +1 -1
package/dist/daemon.d.ts CHANGED
@@ -536,6 +536,8 @@ export declare class Daemon extends EventEmitter {
536
536
  /** Generation/signature fence for safety dialogs while their screen is still visible. */
537
537
  private autoResolvedDialogKey;
538
538
  private autoResolvedDialogGeneration;
539
+ /** Per-launch safety phases survive startup handoff, redraws and failed/uncertain key ACKs. */
540
+ private launchDialogClaims;
539
541
  /**
540
542
  * (#1103) Proof that THIS spawn's Codex went onto Luna Reserve: AgEnD saw (and
541
543
  * answered) the usage-limit menu that codex shows when it switches itself to
@@ -1493,6 +1495,8 @@ export declare class Daemon extends EventEmitter {
1493
1495
  private static dialogMatches;
1494
1496
  /** Refresh the separate stdin-blocked observation from one pane capture. */
1495
1497
  private updateInputBlockedState;
1498
+ private hasAutoDialogClaim;
1499
+ private claimAutoDialog;
1496
1500
  /** Whether a runtime prompt currently owns the pane's stdin. */
1497
1501
  isInputBlocked(): boolean;
1498
1502
  /**
@@ -1767,6 +1771,8 @@ export declare class Daemon extends EventEmitter {
1767
1771
  * an invariant maintained by comments.
1768
1772
  */
1769
1773
  private sendDeliveryEnter;
1774
+ /** Write half only: callers must finish the availability wait before any final recovery ownership proof. */
1775
+ private sendDeliveryEnterAfterAvailability;
1770
1776
  private writeMessageToPane;
1771
1777
  /**
1772
1778
  * The single place a pasted message is judged submitted, shared by the
package/dist/daemon.js CHANGED
@@ -1168,6 +1168,8 @@ export class Daemon extends EventEmitter {
1168
1168
  /** Generation/signature fence for safety dialogs while their screen is still visible. */
1169
1169
  autoResolvedDialogKey = null;
1170
1170
  autoResolvedDialogGeneration = 0;
1171
+ /** Per-launch safety phases survive startup handoff, redraws and failed/uncertain key ACKs. */
1172
+ launchDialogClaims = null;
1171
1173
  /**
1172
1174
  * (#1103) Proof that THIS spawn's Codex went onto Luna Reserve: AgEnD saw (and
1173
1175
  * answered) the usage-limit menu that codex shows when it switches itself to
@@ -3166,8 +3168,9 @@ export class Daemon extends EventEmitter {
3166
3168
  const readyPattern = this.backend.getReadyPattern();
3167
3169
  const busyPattern = this.backend.getBusyPattern?.() ?? null;
3168
3170
  this.errorMonitorTimer = setInterval(async () => {
3169
- if (!this.tmux || this.spawning)
3171
+ if (!this.tmux || this.spawning || this.deliveryWritesStopping)
3170
3172
  return;
3173
+ const pollTmux = this.tmux;
3171
3174
  // This poll belongs to the spawn and the monitors it started under. Every await below can outlive them (a stop,
3172
3175
  // a pause, a respawn); a stale poll must not touch state a newer one owns, so it checks before committing.
3173
3176
  const pollSpawn = this.spawnGeneration;
@@ -3176,18 +3179,19 @@ export class Daemon extends EventEmitter {
3176
3179
  const captureOrder = ++this.interactionCaptureSerial;
3177
3180
  const captureMono = performance.now();
3178
3181
  const stale = () => pollSpawn !== this.spawnGeneration || pollFence !== this.launchFenceEpoch
3179
- || !sameInteractionOwner(pollOwner, this.interactionOwner()) || this.runtimeMonitorsFrozen || this.spawning;
3182
+ || !sameInteractionOwner(pollOwner, this.interactionOwner()) || this.runtimeMonitorsFrozen || this.spawning
3183
+ || this.deliveryWritesStopping || this.tmux !== pollTmux;
3180
3184
  let scanStartedAt = null;
3181
3185
  const endScan = () => { if (scanStartedAt !== null)
3182
3186
  noteSyncWork("daemon.errorMonitorScan", scanStartedAt); scanStartedAt = null; };
3183
3187
  try {
3184
- const alive = await this.tmux.isWindowAlive();
3188
+ const alive = await pollTmux.isWindowAlive();
3185
3189
  if (stale())
3186
3190
  return;
3187
3191
  if (!alive)
3188
3192
  return;
3189
3193
  const captureAt = Date.now();
3190
- const pane = await this.tmux.capturePane();
3194
+ const pane = await pollTmux.capturePane();
3191
3195
  // The scan of this pane is synchronous until it answers a dialog (an await) or ends: attributed (#1235).
3192
3196
  scanStartedAt = performance.now();
3193
3197
  if (stale())
@@ -3264,9 +3268,7 @@ export class Daemon extends EventEmitter {
3264
3268
  continue;
3265
3269
  }
3266
3270
  const autoKey = dialog.autoResolutionKey;
3267
- if (dialog.verifyAfterKeys && autoKey
3268
- && this.autoResolvedDialogGeneration === this.spawnGeneration
3269
- && this.autoResolvedDialogKey === autoKey) {
3271
+ if (dialog.verifyAfterKeys && autoKey && this.hasAutoDialogClaim(dialog)) {
3270
3272
  // A previous poll sent the safety choice but the CLI has not
3271
3273
  // repainted yet. Never send another Enter into the same screen.
3272
3274
  continue;
@@ -3285,7 +3287,7 @@ export class Daemon extends EventEmitter {
3285
3287
  const currentAt = Date.now();
3286
3288
  const currentMono = performance.now();
3287
3289
  const currentOrder = ++this.interactionCaptureSerial;
3288
- const currentPane = await this.tmux.capturePane();
3290
+ const currentPane = await pollTmux.capturePane();
3289
3291
  // Every await below can outlive the spawn / monitors this poll started under: after each one, before
3290
3292
  // any state is touched or any further key is sent, `stale()` (fixed at the poll's start) is asked again.
3291
3293
  if (stale())
@@ -3309,28 +3311,26 @@ export class Daemon extends EventEmitter {
3309
3311
  // not evidence that the old request was answered, and the one-minute fallback must keep running.
3310
3312
  if (this.dialogAnswers?.key === Daemon.answerKey(dialog) && this.dialogAnswers.screen !== before)
3311
3313
  this.endDialogEpisode(dialog.requestIdentity !== undefined);
3312
- if (dialog.verifyAfterKeys && autoKey
3313
- && this.autoResolvedDialogGeneration === this.spawnGeneration
3314
- && this.autoResolvedDialogKey === autoKey)
3314
+ if (dialog.verifyAfterKeys && autoKey && this.hasAutoDialogClaim(dialog))
3315
3315
  return;
3316
- if (dialog.verifyAfterKeys && autoKey) {
3317
- this.autoResolvedDialogGeneration = this.spawnGeneration;
3318
- this.autoResolvedDialogKey = autoKey;
3319
- }
3316
+ if (dialog.verifyAfterKeys && autoKey)
3317
+ this.claimAutoDialog(dialog);
3320
3318
  this.logger.info(`Auto-dismissing runtime dialog: ${dialog.description}`);
3321
3319
  const SPECIAL_KEYS = new Set(["Up", "Down", "Enter", "Escape", "Right", "Left"]);
3322
3320
  for (const key of dialog.keys) {
3321
+ if (stale())
3322
+ return;
3323
3323
  let sent = false;
3324
3324
  if (SPECIAL_KEYS.has(key)) {
3325
- sent = await this.tmux.sendSpecialKey(key);
3325
+ sent = await pollTmux.sendSpecialKey(key);
3326
3326
  }
3327
3327
  else {
3328
- sent = await this.tmux.pasteText(key, this.systemPasteOptions());
3328
+ sent = await pollTmux.pasteText(key, this.systemPasteOptions());
3329
3329
  }
3330
3330
  if (stale())
3331
3331
  return;
3332
3332
  if (!sent) {
3333
- if (dialog.verifyAfterKeys && autoKey) {
3333
+ if (dialog.verifyAfterKeys && autoKey && !dialog.oncePerLaunch) {
3334
3334
  this.autoResolvedDialogGeneration = 0;
3335
3335
  this.autoResolvedDialogKey = null;
3336
3336
  }
@@ -3345,7 +3345,7 @@ export class Daemon extends EventEmitter {
3345
3345
  const afterKeysAt = Date.now();
3346
3346
  const afterKeysMono = performance.now();
3347
3347
  const afterKeysOrder = ++this.interactionCaptureSerial;
3348
- const afterKeysPane = await this.tmux.capturePane();
3348
+ const afterKeysPane = await pollTmux.capturePane();
3349
3349
  if (stale())
3350
3350
  return;
3351
3351
  const dialogStillActive = dialog.inputBlocked
@@ -3375,7 +3375,7 @@ export class Daemon extends EventEmitter {
3375
3375
  // backend-named identity any difference on the screen (a ticking timer) proves nothing either way: nothing
3376
3376
  // is reported and the parked clock is left alone. Two requests that look exactly alike cannot be told apart.
3377
3377
  await new Promise(r => setTimeout(r, DIALOG_ANSWER_SETTLE_MS));
3378
- const afterPane = await this.tmux.capturePane();
3378
+ const afterPane = await pollTmux.capturePane();
3379
3379
  if (stale())
3380
3380
  return; // (nothing was touched since the last check: only a read)
3381
3381
  const stillThere = Daemon.dialogMatches(dialog, afterPane);
@@ -6727,6 +6727,32 @@ export class Daemon extends EventEmitter {
6727
6727
  }
6728
6728
  return active;
6729
6729
  }
6730
+ hasAutoDialogClaim(dialog) {
6731
+ const key = dialog.autoResolutionKey;
6732
+ if (!key)
6733
+ return false;
6734
+ if (dialog.oncePerLaunch) {
6735
+ const claims = this.launchDialogClaims;
6736
+ return claims !== null && claims.spawn === this.spawnGeneration && claims.attempt === this.launchAttempt
6737
+ && claims.keys.has(key);
6738
+ }
6739
+ return this.autoResolvedDialogGeneration === this.spawnGeneration && this.autoResolvedDialogKey === key;
6740
+ }
6741
+ claimAutoDialog(dialog) {
6742
+ const key = dialog.autoResolutionKey;
6743
+ if (!key)
6744
+ return;
6745
+ if (dialog.oncePerLaunch) {
6746
+ if (!this.launchDialogClaims || this.launchDialogClaims.spawn !== this.spawnGeneration
6747
+ || this.launchDialogClaims.attempt !== this.launchAttempt) {
6748
+ this.launchDialogClaims = { spawn: this.spawnGeneration, attempt: this.launchAttempt, keys: new Set() };
6749
+ }
6750
+ this.launchDialogClaims.keys.add(key);
6751
+ return;
6752
+ }
6753
+ this.autoResolvedDialogGeneration = this.spawnGeneration;
6754
+ this.autoResolvedDialogKey = key;
6755
+ }
6730
6756
  /** Whether a runtime prompt currently owns the pane's stdin. */
6731
6757
  isInputBlocked() {
6732
6758
  return this.inputBlockedDialogKey !== null;
@@ -6788,8 +6814,7 @@ export class Daemon extends EventEmitter {
6788
6814
  // this spawn is not what the screen is waiting on any more — the entry
6789
6815
  // after it (typically a hold) describes it.
6790
6816
  if (dialog.verifyAfterKeys && dialog.autoResolutionKey
6791
- && this.autoResolvedDialogGeneration === this.spawnGeneration
6792
- && this.autoResolvedDialogKey === dialog.autoResolutionKey
6817
+ && this.hasAutoDialogClaim(dialog)
6793
6818
  && dialogs.some(other => other !== dialog && Daemon.dialogMatches(other, pane)))
6794
6819
  continue;
6795
6820
  this.trackDialogParked(dialog);
@@ -7766,6 +7791,10 @@ export class Daemon extends EventEmitter {
7766
7791
  async sendDeliveryEnter(phase, stillCurrent, durableAttempt) {
7767
7792
  if (!(await this.waitForInputTransientToClear(phase)))
7768
7793
  return false;
7794
+ return this.sendDeliveryEnterAfterAvailability(phase, stillCurrent, durableAttempt);
7795
+ }
7796
+ /** Write half only: callers must finish the availability wait before any final recovery ownership proof. */
7797
+ async sendDeliveryEnterAfterAvailability(phase, stillCurrent, durableAttempt) {
7769
7798
  // A recovery Enter may have waited for a transient while cancel or spawn
7770
7799
  // replaced the delivery. Check at the last point before the tmux write.
7771
7800
  if (this.deliveryWritesStopping || (stillCurrent && !stillCurrent()))
@@ -8547,12 +8576,12 @@ export class Daemon extends EventEmitter {
8547
8576
  // read as ours, we would press Enter to "recover" it, and the turn IT
8548
8577
  // starts would confirm a message that never reached the pane.
8549
8578
  //
8550
- // For a structural box reader (claude-code), "did not already show it" needs a baseline whose box was READ: an
8551
- // unreadable one (a dialog, a redraw the reader refused) says nothing about what the box held, and counting it as
8552
- // empty would claim an older message as ours and press Enter on it (#1353 review). The prompt-row backends (codex,
8553
- // kiro) keep their existing attribution.
8579
+ // Structured evidence (Claude's box and Codex's current input/footer pair) requires both snapshots to be READ.
8580
+ // An unreadable baseline says nothing about what the box held; treating it as empty would claim an older draft
8581
+ // as ours and press Enter on it (#1353/#1359). Codex's native warning viewer can ignore the paste and first Enter,
8582
+ // then reveal an older draft when the user dismisses it. Row-only backends retain their existing attribution.
8554
8583
  const attributable = baseline != null
8555
- && (!this.backend?.readInputRow || (baseline.inputReadable && after.inputReadable));
8584
+ && (!this.structuredInputEvidence() || (baseline.inputReadable && after.inputReadable));
8556
8585
  if (after.strandedInput && (signature.unique || (attributable && baseline.strandedInput === false)))
8557
8586
  return "stranded";
8558
8587
  // A paste the CLI shows collapsed carries no signature at all. One the box did not hold before we pasted is
@@ -8802,12 +8831,36 @@ export class Daemon extends EventEmitter {
8802
8831
  return true; // best effort, exactly as before — nothing here is verified
8803
8832
  }
8804
8833
  if (proof !== "submitted") {
8805
- // A bare Enter is a no-op at an empty prompt, so this is safe even if the
8806
- // first one did land; when the text is still in the input row it is the
8807
- // submit it never got. Re-pasting would append the text to itself.
8808
8834
  await new Promise(r => setTimeout(r, NORMAL_ENTER_SETTLE_MS));
8809
- if (!(await this.sendDeliveryEnter(`${label}-retry`, current)))
8835
+ if (current && !current())
8810
8836
  return false;
8837
+ const retryPhase = `${label}-retry`;
8838
+ if (this.structuredInputEvidence()) {
8839
+ // Both the retry delay and the Enter availability wait may outlive the
8840
+ // paste. Complete those waits BEFORE the last ownership/positive proof.
8841
+ if (!(await this.waitForInputTransientToClear(retryPhase)))
8842
+ return false;
8843
+ if (this.deliveryWritesStopping || (current && !current()))
8844
+ return false;
8845
+ proof = await this.confirmSubmitted(signature, baseline);
8846
+ if (this.deliveryWritesStopping || (current && !current()))
8847
+ return false;
8848
+ if (proof === "submitted")
8849
+ return true;
8850
+ if (proof !== "stranded") {
8851
+ this.logger.warn({ label, proof }, "System paste recovery input could not be attributed");
8852
+ return false;
8853
+ }
8854
+ // No further availability wait may invalidate this proof. The write
8855
+ // half retains the same stopping/admission fence immediately before IPC.
8856
+ if (!(await this.sendDeliveryEnterAfterAvailability(retryPhase, current)))
8857
+ return false;
8858
+ }
8859
+ else {
8860
+ // Row-only backends retain their ordinary availability wait and retry.
8861
+ if (!(await this.sendDeliveryEnter(retryPhase, current)))
8862
+ return false;
8863
+ }
8811
8864
  proof = await this.confirmSubmitted(signature, baseline);
8812
8865
  if (current && !current())
8813
8866
  return false;
@@ -9353,7 +9406,8 @@ export class Daemon extends EventEmitter {
9353
9406
  instanceName: this.name,
9354
9407
  mcpServers: isCliMode ? {} : {
9355
9408
  "agend": {
9356
- command: "node",
9409
+ // The Node this daemon runs on (#1450 C5): the CLIs start the MCP server with it, not a `node` from PATH.
9410
+ command: process.execPath,
9357
9411
  args: [serverJs],
9358
9412
  env: mcpEnv,
9359
9413
  },
@@ -10499,7 +10553,8 @@ export class Daemon extends EventEmitter {
10499
10553
  const ownerTmux = this.tmux;
10500
10554
  const ownerSpawn = this.spawnGeneration;
10501
10555
  const ownerFence = this.launchFenceEpoch;
10502
- const owned = () => this.tmux === ownerTmux && this.spawnGeneration === ownerSpawn && this.launchFenceEpoch === ownerFence;
10556
+ const owned = () => !this.deliveryWritesStopping && this.tmux === ownerTmux
10557
+ && this.spawnGeneration === ownerSpawn && this.launchFenceEpoch === ownerFence;
10503
10558
  do {
10504
10559
  this.startupAdmission?.();
10505
10560
  attempts++;
@@ -10545,8 +10600,7 @@ export class Daemon extends EventEmitter {
10545
10600
  if (Daemon.dialogMatches(dialog, pane)) {
10546
10601
  if (dialog.autoResolutionKey
10547
10602
  && (attemptedSafetyChoices.has(dialog.autoResolutionKey)
10548
- || (this.autoResolvedDialogGeneration === this.spawnGeneration
10549
- && this.autoResolvedDialogKey === dialog.autoResolutionKey)))
10603
+ || this.hasAutoDialogClaim(dialog)))
10550
10604
  continue;
10551
10605
  lastDialog = dialog;
10552
10606
  this.noteCodexReserveDialog(dialog, pane);
@@ -10610,8 +10664,7 @@ export class Daemon extends EventEmitter {
10610
10664
  }
10611
10665
  if (dialog.autoResolutionKey) {
10612
10666
  attemptedSafetyChoices.add(dialog.autoResolutionKey);
10613
- this.autoResolvedDialogGeneration = ownerSpawn;
10614
- this.autoResolvedDialogKey = dialog.autoResolutionKey;
10667
+ this.claimAutoDialog(dialog);
10615
10668
  }
10616
10669
  for (const key of dialog.keys) {
10617
10670
  this.startupAdmission?.();