@songsid/agend 2.3.3-beta.8 → 2.3.3

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 (50) hide show
  1. package/dist/backend/claude-code.js +17 -0
  2. package/dist/backend/claude-code.js.map +1 -1
  3. package/dist/backend/codex.d.ts +15 -0
  4. package/dist/backend/codex.js +67 -18
  5. package/dist/backend/codex.js.map +1 -1
  6. package/dist/backend/types.d.ts +7 -0
  7. package/dist/backend/types.js.map +1 -1
  8. package/dist/channel/adapters/discord.js +60 -17
  9. package/dist/channel/adapters/discord.js.map +1 -1
  10. package/dist/daemon.d.ts +17 -0
  11. package/dist/daemon.js +85 -9
  12. package/dist/daemon.js.map +1 -1
  13. package/dist/fleet-manager.d.ts +31 -0
  14. package/dist/fleet-manager.js +106 -12
  15. package/dist/fleet-manager.js.map +1 -1
  16. package/dist/instance-lifecycle.d.ts +13 -0
  17. package/dist/instance-lifecycle.js +30 -6
  18. package/dist/instance-lifecycle.js.map +1 -1
  19. package/dist/locale.js +10 -2
  20. package/dist/locale.js.map +1 -1
  21. package/dist/parked-notices.d.ts +87 -0
  22. package/dist/parked-notices.js +194 -0
  23. package/dist/parked-notices.js.map +1 -0
  24. package/dist/public-link-progress.d.ts +5 -2
  25. package/dist/public-link-progress.js +4 -3
  26. package/dist/public-link-progress.js.map +1 -1
  27. package/dist/public-web-link.d.ts +10 -0
  28. package/dist/public-web-link.js +24 -5
  29. package/dist/public-web-link.js.map +1 -1
  30. package/dist/settings-api.d.ts +10 -0
  31. package/dist/settings-api.js +41 -0
  32. package/dist/settings-api.js.map +1 -1
  33. package/dist/tunnel/cloudflared.d.ts +5 -0
  34. package/dist/tunnel/cloudflared.js +32 -1
  35. package/dist/tunnel/cloudflared.js.map +1 -1
  36. package/dist/tunnel/named-diagnostics.d.ts +2 -1
  37. package/dist/tunnel/named-diagnostics.js +4 -1
  38. package/dist/tunnel/named-diagnostics.js.map +1 -1
  39. package/dist/tunnel/named-link-test.d.ts +90 -0
  40. package/dist/tunnel/named-link-test.js +270 -0
  41. package/dist/tunnel/named-link-test.js.map +1 -0
  42. package/dist/tunnel/purpose-lane.d.ts +3 -1
  43. package/dist/tunnel/purpose-lane.js +2 -0
  44. package/dist/tunnel/purpose-lane.js.map +1 -1
  45. package/dist/ui/panel-settings.js +5 -3
  46. package/dist/ui/settings-named-test.js +144 -0
  47. package/dist/ui/settings-strings.js +6 -6
  48. package/dist/ui/shared/app.css +7 -0
  49. package/dist/ui/shared/web-authorize.js +2 -2
  50. package/package.json +1 -1
package/dist/daemon.js CHANGED
@@ -1264,8 +1264,19 @@ export class Daemon extends EventEmitter {
1264
1264
  dialogParkedSince = 0;
1265
1265
  /** Identity of that dialog: its pattern, not its description (two tables may describe one screen differently). */
1266
1266
  dialogParkedKey = null;
1267
+ /**
1268
+ * #2893: the held dialog whose heldReason was last logged. Its own state, not the parked clock's: the delivery probe
1269
+ * starts that clock too, and must not use up the one log line. Cleared with the hold, so a later hold logs again.
1270
+ */
1271
+ heldReasonLoggedKey = null;
1267
1272
  dialogParkedInteractionEpisode = null;
1268
1273
  dialogParkedReported = false;
1274
+ /**
1275
+ * #2897: a "needs a human" notice is out for a dialog, and what it lives with: "parked" — the parked clock (retired when
1276
+ * that dialog leaves the pane or another replaces it); "answer" — an answer episode of a dialog the parked clock does
1277
+ * not track (a non-blocking one, whose scans pass no blocking dialog): retired when that episode ends.
1278
+ */
1279
+ parkedNoticeOut = null;
1269
1280
  /** #2675: the card on screen that a person can answer from the chat — what it asks, since when, and whether it was posted. */
1270
1281
  remoteCardShown = null;
1271
1282
  /** #2675: when (performance.now, taken before its capture) the newest read the card tracker used was started. */
@@ -3724,8 +3735,18 @@ export class Daemon extends EventEmitter {
3724
3735
  this.noteLiveIncidentDialog(dialog, pane);
3725
3736
  if (dialog.blocksDelivery || dialog.holdOnly)
3726
3737
  blockingSeen = dialog;
3727
- if (dialog.holdOnly)
3728
- break; // recognised, deliberately not answered; trackDialogParked reports it
3738
+ if (dialog.holdOnly) {
3739
+ // Recognised, deliberately not answered; trackDialogParked reports it. Why an automatic answer passed it by
3740
+ // is logged once, when this hold begins (#2893).
3741
+ if (this.heldReasonLoggedKey !== Daemon.dialogKey(dialog)) {
3742
+ const reason = Daemon.heldReasonOf(dialog, pane);
3743
+ if (Object.keys(reason).length) {
3744
+ this.heldReasonLoggedKey = Daemon.dialogKey(dialog);
3745
+ this.logger.warn({ description: dialog.description, ...reason }, "Runtime dialog held — not auto-answering");
3746
+ }
3747
+ }
3748
+ break;
3749
+ }
3729
3750
  // Reported as ignoring the daemon: the SAME request (the identity the count was made on — verbatim, the
3730
3751
  // backend's own when it names one) is answered only once per DIALOG_ANSWER_BACKOFF_MS. Anything else — another
3731
3752
  // request, an unexplained difference, the dialog gone — is not held back here; the answer path below ends or
@@ -3788,7 +3809,7 @@ export class Daemon extends EventEmitter {
3788
3809
  return;
3789
3810
  if (dialog.verifyAfterKeys && autoKey)
3790
3811
  this.claimAutoDialog(dialog);
3791
- this.logger.info(`Auto-dismissing runtime dialog: ${dialog.description}`);
3812
+ this.logger.info(Daemon.answerDetailOf(dialog, currentPane), `Auto-dismissing runtime dialog: ${dialog.description}`);
3792
3813
  const SPECIAL_KEYS = new Set(["Up", "Down", "Enter", "Escape", "Right", "Left"]);
3793
3814
  for (const key of dialog.keys) {
3794
3815
  if (stale())
@@ -3879,8 +3900,10 @@ export class Daemon extends EventEmitter {
3879
3900
  // Reached only when no dialog was answered this poll (none on screen, a fenced one, or one waiting out its
3880
3901
  // backoff): unless it is the one backing off, the screen that was being answered is gone, and the next dialog is
3881
3902
  // a new one.
3882
- if (!answersBackedOff)
3903
+ if (!answersBackedOff && this.dialogAnswers) {
3883
3904
  this.dialogAnswers = null;
3905
+ this.retireParkedNotice("answer");
3906
+ }
3884
3907
  this.trackDialogParked(blockingSeen);
3885
3908
  this.trackRemoteCard(blockingSeen, pane, captureMono);
3886
3909
  if (blockingSeen)
@@ -8435,6 +8458,7 @@ export class Daemon extends EventEmitter {
8435
8458
  */
8436
8459
  endDialogEpisode(clearParked = true) {
8437
8460
  this.dialogAnswers = null;
8461
+ this.retireParkedNotice("answer");
8438
8462
  if (clearParked)
8439
8463
  this.trackDialogParked(null);
8440
8464
  }
@@ -8461,8 +8485,11 @@ export class Daemon extends EventEmitter {
8461
8485
  */
8462
8486
  noteDialogAnswer(dialog, outcome, screen) {
8463
8487
  const key = Daemon.answerKey(dialog);
8464
- if (!this.dialogAnswers || this.dialogAnswers.key !== key)
8465
- this.dialogAnswers = { key, ignored: 0, reported: false, screen, lastAnswerAt: 0 }; // the exact screen being counted: every answer in the episode saw this same one
8488
+ if (!this.dialogAnswers || this.dialogAnswers.key !== key) {
8489
+ if (this.dialogAnswers)
8490
+ this.retireParkedNotice("answer"); // #2897: a different screen: the reported one is over
8491
+ this.dialogAnswers = { key, ignored: 0, reported: false, screen, lastAnswerAt: 0 };
8492
+ } // the exact screen being counted: every answer in the episode saw this same one
8466
8493
  const episode = this.dialogAnswers;
8467
8494
  if (outcome !== "ignored") {
8468
8495
  this.endDialogEpisode(outcome === "took");
@@ -8478,6 +8505,8 @@ export class Daemon extends EventEmitter {
8478
8505
  this.dialogParkedReported = true;
8479
8506
  const holdsDeliveries = dialog.blocksDelivery === true || dialog.holdOnly === true;
8480
8507
  this.logger.warn({ dialog: dialog.description, attempts: episode.ignored, holdsDeliveries }, "Runtime dialog is ignoring AgEnD's answer — reporting for a human");
8508
+ // #2897: a dialog the parked clock tracks keeps its notice until it leaves the pane; any other, until this episode ends.
8509
+ this.parkedNoticeOut = this.dialogParkedSince !== 0 && this.dialogParkedKey === Daemon.dialogKey(dialog) ? "parked" : "answer";
8481
8510
  this.emit("dialog_answer_ignored", { name: this.name, description: dialog.description, attempts: episode.ignored, holdsDeliveries });
8482
8511
  }
8483
8512
  /**
@@ -8551,6 +8580,24 @@ export class Daemon extends EventEmitter {
8551
8580
  this.remoteCardAnswering = false;
8552
8581
  }
8553
8582
  }
8583
+ /** #2893: a held dialog's own account of why no automatic answer took it (RuntimeDialog.heldReason); {} when none. */
8584
+ static heldReasonOf(dialog, pane) {
8585
+ try {
8586
+ return dialog.heldReason?.(pane) ?? {};
8587
+ }
8588
+ catch {
8589
+ return {};
8590
+ }
8591
+ }
8592
+ /** What an answered dialog says about the answer (RuntimeDialog.answerDetail); {} when it gives none. */
8593
+ static answerDetailOf(dialog, pane) {
8594
+ try {
8595
+ return dialog.answerDetail?.(pane) ?? {};
8596
+ }
8597
+ catch {
8598
+ return {};
8599
+ }
8600
+ }
8554
8601
  /**
8555
8602
  * Remember how long a blocking dialog has been on screen. Once it has
8556
8603
  * outlived DIALOG_PARKED_NOTIFY_MS — the auto-dismiss did not take, or the
@@ -8559,14 +8606,24 @@ export class Daemon extends EventEmitter {
8559
8606
  * is safe. Identity is the pattern, so the delivery gate and the runtime
8560
8607
  * scanner (same canonical list) never reset each other's clock.
8561
8608
  */
8609
+ /** #2897: the notice out for a dialog is out of date (its `owner` ended): the fleet takes it back. */
8610
+ retireParkedNotice(owner) {
8611
+ if (this.parkedNoticeOut !== owner)
8612
+ return;
8613
+ this.parkedNoticeOut = null;
8614
+ this.emit("dialog_unparked", { name: this.name });
8615
+ }
8562
8616
  trackDialogParked(dialog) {
8563
8617
  if (!dialog) {
8564
8618
  if (this.dialogParkedSince !== 0)
8565
8619
  this.logger.info({ dialog: this.dialogParkedKey }, "Blocking dialog is gone from the pane");
8566
8620
  this.dialogParkedSince = 0;
8567
8621
  this.dialogParkedKey = null;
8622
+ this.heldReasonLoggedKey = null;
8568
8623
  this.dialogParkedInteractionEpisode = null;
8569
8624
  this.dialogParkedReported = false;
8625
+ // #2897: whatever it posted is out of date — the fleet takes its notices back.
8626
+ this.retireParkedNotice("parked");
8570
8627
  return;
8571
8628
  }
8572
8629
  const key = Daemon.dialogKey(dialog);
@@ -8578,6 +8635,9 @@ export class Daemon extends EventEmitter {
8578
8635
  if (episode !== null)
8579
8636
  this.dialogParkedInteractionEpisode = episode;
8580
8637
  if (this.dialogParkedSince === 0 || this.dialogParkedKey !== key) {
8638
+ // #2897: a different dialog replaced the one being tracked — that one's notices are out of date.
8639
+ if (this.dialogParkedSince !== 0)
8640
+ this.retireParkedNotice("parked");
8581
8641
  this.dialogParkedSince = Date.now();
8582
8642
  this.dialogParkedKey = key;
8583
8643
  this.dialogParkedReported = false;
@@ -8589,6 +8649,7 @@ export class Daemon extends EventEmitter {
8589
8649
  this.dialogParkedReported = true;
8590
8650
  if (!this.dialogParkedReported && Date.now() - this.dialogParkedSince >= (unrecognised ? 0 : DIALOG_PARKED_NOTIFY_MS)) {
8591
8651
  this.dialogParkedReported = true;
8652
+ this.parkedNoticeOut = "parked";
8592
8653
  this.logger.warn({ dialog: dialog.description, parkedForMs: Date.now() - this.dialogParkedSince }, "CLI dialog is still on screen — not auto-answering, reporting for a human");
8593
8654
  this.emit("dialog_parked", { name: this.name, description: dialog.description, holdOnly: dialog.holdOnly === true,
8594
8655
  ...(dialog.parkedAdvice ? { advice: dialog.parkedAdvice } : {}),
@@ -11427,6 +11488,13 @@ export class Daemon extends EventEmitter {
11427
11488
  this.recoveryNativeWork = false;
11428
11489
  this.recoveryCancelled = false;
11429
11490
  this.dialogAnswers = null;
11491
+ // #2897: a relaunched CLI shows none of the old dialogs — whatever notice was out for one is out of date.
11492
+ // A dialog still tracked when the new CLI shows it again is reported afresh (its old notice is gone).
11493
+ if (this.parkedNoticeOut) {
11494
+ this.parkedNoticeOut = null;
11495
+ this.dialogParkedReported = false;
11496
+ this.emit("dialog_unparked", { name: this.name });
11497
+ }
11430
11498
  this.cancelCapacityNudge("the instance was stopped, paused or respawned");
11431
11499
  this.lastErrorEpisode = null;
11432
11500
  this.nudgeBaselines = null;
@@ -12809,6 +12877,8 @@ export class Daemon extends EventEmitter {
12809
12877
  this.noteCodexReserveDialog(dialog, pane);
12810
12878
  this.noteLiveIncidentDialog(dialog, pane);
12811
12879
  cleanReadyPolls = 0;
12880
+ // Before the parked clock below claims it: a hold seen for the first time is said once, with its reason (#2893).
12881
+ const firstSight = this.dialogParkedKey !== Daemon.dialogKey(dialog);
12812
12882
  // Start the parked clock for every delivery-blocking dialog, exact
12813
12883
  // ones included: if the auto-dismiss keeps failing here, the human
12814
12884
  // report should count from first sight, not from the runtime scan.
@@ -12837,14 +12907,16 @@ export class Daemon extends EventEmitter {
12837
12907
  }
12838
12908
  if (dialog.holdOnly) {
12839
12909
  // Recognised but deliberately not answered (see RuntimeDialog.holdOnly).
12840
- if (this.dialogParkedKey !== Daemon.dialogKey(dialog)) {
12841
- this.logger.warn({ description: dialog.description }, "Startup dialog held — not auto-answering, deliveries stay blocked");
12910
+ if (firstSight) {
12911
+ const reason = this.heldReasonLoggedKey !== Daemon.dialogKey(dialog) ? Daemon.heldReasonOf(dialog, pane) : {};
12912
+ if (Object.keys(reason).length)
12913
+ this.heldReasonLoggedKey = Daemon.dialogKey(dialog);
12914
+ this.logger.warn({ description: dialog.description, ...reason }, "Startup dialog held — not auto-answering, deliveries stay blocked");
12842
12915
  }
12843
12916
  this.trackDialogParked(dialog);
12844
12917
  matched = true;
12845
12918
  break;
12846
12919
  }
12847
- this.logger.info(`Dismissing startup dialog: ${dialog.description}`);
12848
12920
  // Restart is exactly when inbound messages pile up, and nothing gates
12849
12921
  // delivery on `spawning`. Take the pane lock for the key sequence so a
12850
12922
  // queued message cannot be pasted into a half-dismissed trust dialog.
@@ -12857,6 +12929,7 @@ export class Daemon extends EventEmitter {
12857
12929
  // a pane that is no longer this launch's is not read or keyed.
12858
12930
  if (!owned())
12859
12931
  return false;
12932
+ let answeredPane = pane;
12860
12933
  if (dialog.inputBlocked) {
12861
12934
  const currentPane = await this.tmux.capturePane();
12862
12935
  this.startupAdmission?.();
@@ -12865,7 +12938,10 @@ export class Daemon extends EventEmitter {
12865
12938
  return false;
12866
12939
  if (!Daemon.dialogMatches(dialog, currentPane))
12867
12940
  return false;
12941
+ answeredPane = currentPane;
12868
12942
  }
12943
+ // Said of the screen actually answered — the one re-read under the lock (#2896 review).
12944
+ this.logger.info(Daemon.answerDetailOf(dialog, answeredPane), `Dismissing startup dialog: ${dialog.description}`);
12869
12945
  if (dialog.autoResolutionKey) {
12870
12946
  attemptedSafetyChoices.add(dialog.autoResolutionKey);
12871
12947
  this.claimAutoDialog(dialog);