@songsid/agend 2.2.0-beta.2 → 2.2.0-beta.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 (75) hide show
  1. package/dist/auth-api.js +2 -0
  2. package/dist/auth-api.js.map +1 -1
  3. package/dist/channel/adapters/discord.d.ts +1 -1
  4. package/dist/channel/adapters/discord.js.map +1 -1
  5. package/dist/channel/adapters/telegram.d.ts +12 -2
  6. package/dist/channel/adapters/telegram.js +39 -8
  7. package/dist/channel/adapters/telegram.js.map +1 -1
  8. package/dist/channel/telegram-markdown.d.ts +6 -0
  9. package/dist/channel/telegram-markdown.js +104 -0
  10. package/dist/channel/telegram-markdown.js.map +1 -0
  11. package/dist/channel/tool-router.js +5 -4
  12. package/dist/channel/tool-router.js.map +1 -1
  13. package/dist/channel/types.d.ts +5 -3
  14. package/dist/config-validator.js +6 -5
  15. package/dist/config-validator.js.map +1 -1
  16. package/dist/daemon.d.ts +12 -0
  17. package/dist/daemon.js +93 -16
  18. package/dist/daemon.js.map +1 -1
  19. package/dist/delivery-outbox.d.ts +11 -2
  20. package/dist/delivery-outbox.js +13 -2
  21. package/dist/delivery-outbox.js.map +1 -1
  22. package/dist/delivery-reconciliation.d.ts +18 -1
  23. package/dist/delivery-reconciliation.js +67 -2
  24. package/dist/delivery-reconciliation.js.map +1 -1
  25. package/dist/fleet-manager.d.ts +22 -1
  26. package/dist/fleet-manager.js +96 -15
  27. package/dist/fleet-manager.js.map +1 -1
  28. package/dist/locale.js +4 -0
  29. package/dist/locale.js.map +1 -1
  30. package/dist/outbound-handlers.d.ts +2 -0
  31. package/dist/outbound-handlers.js +21 -11
  32. package/dist/outbound-handlers.js.map +1 -1
  33. package/dist/outbound-schemas.d.ts +2 -2
  34. package/dist/outbound-schemas.js +1 -1
  35. package/dist/outbound-schemas.js.map +1 -1
  36. package/dist/public-web-gateway.js +4 -2
  37. package/dist/public-web-gateway.js.map +1 -1
  38. package/dist/settings-change.js +2 -2
  39. package/dist/tips.js +1 -1
  40. package/dist/tips.js.map +1 -1
  41. package/dist/transcript-delta-reader.d.ts +3 -3
  42. package/dist/transcript-delta-reader.js +8 -8
  43. package/dist/transcript-delta-reader.js.map +1 -1
  44. package/dist/ui/chat-store.js +1 -1
  45. package/dist/ui/chat-strings.js +12 -12
  46. package/dist/ui/chat-thread.js +22 -3
  47. package/dist/ui/panel-chat.js +3 -0
  48. package/dist/ui/panel-settings.js +15 -8
  49. package/dist/ui/settings-strings.js +6 -2
  50. package/dist/ui/shared/app-i18n.js +35 -13
  51. package/dist/ui/shared/app-prefs.js +249 -0
  52. package/dist/ui/shared/app-shell.js +17 -4
  53. package/dist/ui/shared/app-store.js +3 -1
  54. package/dist/ui/shared/app.css +30 -0
  55. package/dist/ui/shared/app.js +6 -1
  56. package/dist/ui/shared/header-tools.js +55 -5
  57. package/dist/ui/shared/instance-nav.js +99 -31
  58. package/dist/ui/shared/panel-view.js +6 -0
  59. package/dist/ui/shared/theme.js +39 -7
  60. package/dist/ui/shared/ui-icons.js +1 -0
  61. package/dist/ui/shared/usage-dialog.js +13 -4
  62. package/dist/ui/shared/view-strings.js +20 -2
  63. package/dist/view-api.js +5 -0
  64. package/dist/view-api.js.map +1 -1
  65. package/dist/web-api.d.ts +10 -1
  66. package/dist/web-api.js +103 -1
  67. package/dist/web-api.js.map +1 -1
  68. package/dist/web-auth.js +2 -1
  69. package/dist/web-auth.js.map +1 -1
  70. package/dist/web-prefs.d.ts +63 -0
  71. package/dist/web-prefs.js +263 -0
  72. package/dist/web-prefs.js.map +1 -0
  73. package/dist/web-session.js +1 -1
  74. package/dist/web-session.js.map +1 -1
  75. package/package.json +1 -1
@@ -75,7 +75,7 @@ import { InstanceLifecycle, SupersededStartError, BACKEND_INSTALLATION_INFO, che
75
75
  import { TopicArchiver } from "./topic-archiver.js";
76
76
  import { StatuslineWatcher } from "./statusline-watcher.js";
77
77
  import { outboundHandlers } from "./outbound-handlers.js";
78
- import { DeliveryOutbox } from "./delivery-outbox.js";
78
+ import { DeliveryOutbox, isSilentScheduleDelivery } from "./delivery-outbox.js";
79
79
  // The target Daemon can legitimately wait 30 minutes for a busy pane before
80
80
  // deciding to defer before submission. This is an alert threshold only: the
81
81
  // active manager/target generation pair owns the lane until the daemon reports
@@ -129,6 +129,7 @@ export function applyTaskListCap(tasks, filterAssignee, filterStatus) {
129
129
  import { handleWebRequest, broadcastSseEvent, SSE_HEARTBEAT_MS } from "./web-api.js";
130
130
  import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId } from "./web-chat-history.js";
131
131
  import { WebChatDiskStore } from "./web-chat-store.js";
132
+ import { WebPrefsStore } from "./web-prefs.js";
132
133
  import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
133
134
  import { ReplyButtonsController } from "./reply-buttons-controller.js";
134
135
  import { publicAttachment, sweepOrphanedUploads, WebFileLedger } from "./web-upload.js";
@@ -762,6 +763,15 @@ export class FleetManager {
762
763
  webChatStore = null;
763
764
  /** Uploaded files and the files the dashboard may fetch back (uploads, reply attachments). */
764
765
  webFiles = new WebFileLedger();
766
+ webPrefsStore;
767
+ /** #1665: the web preferences kept on the host (<home>/web-prefs.json), read on first use. */
768
+ get webPrefs() {
769
+ // #1664: a favorite must be an instance of this fleet now — fleet.yaml's (own keys) or a ClassicBot room's.
770
+ return (this.webPrefsStore ??= new WebPrefsStore(this.dataDir, {
771
+ exists: (name) => Object.hasOwn(this.fleetConfig?.instances ?? {}, name)
772
+ || (this.classicChannels?.getAll() ?? []).some((ch) => ch.instanceName === name),
773
+ }));
774
+ }
765
775
  /**
766
776
  * Read from disk on every access rather than cached at startup: `agend
767
777
  * web-token rotate` runs in a separate process, and a cached copy would keep
@@ -1219,8 +1229,13 @@ export class FleetManager {
1219
1229
  if (event.deliveryId && (event.state === "failed" || event.state === "uncertain")) {
1220
1230
  const row = outbox.get(event.deliveryId);
1221
1231
  if (row && row.kind !== "delivery_outcome_notice" && row.kind !== "post_restart_outcome_notice") {
1222
- const notice = t(`delivery.fleet_${row.state}`, row.targetInstance, row.operationId, row.deliveryId);
1223
- this.logger.error({ deliveryId: row.deliveryId, operationId: row.operationId, target: row.targetInstance, state: row.state }, notice);
1232
+ // #1647: the cause goes in the log (the notice points there), and a silent schedule's paste says what it is.
1233
+ const silent = isSilentScheduleDelivery(row);
1234
+ const notice = silent
1235
+ ? t(`delivery.fleet_${row.state}_silent_schedule`, row.targetInstance, row.operationId, row.deliveryId, row.lastError ?? "unknown")
1236
+ : t(`delivery.fleet_${row.state}`, row.targetInstance, row.operationId, row.deliveryId);
1237
+ this.logger.error({ deliveryId: row.deliveryId, operationId: row.operationId, target: row.targetInstance, state: row.state,
1238
+ cause: row.lastError, ...(silent ? { silentSchedule: true } : {}) }, notice);
1224
1239
  this.notifyFleetError(notice);
1225
1240
  }
1226
1241
  }
@@ -3020,13 +3035,38 @@ export class FleetManager {
3020
3035
  }
3021
3036
  return posted;
3022
3037
  }
3023
- reserveWebChannelEcho(instanceName, sendEcho) {
3038
+ /**
3039
+ * #1666: a delivered web chat message starts the same cancel/progress lifecycle in the instance's topic as a platform
3040
+ * inbound (afterUserInboundDelivered): the same bubble, ticker and retire rules. Not its ✅ tracking — the web message
3041
+ * id is no platform message, and the echo is the fleet's own post. The web send calls this on the echo's lane.
3042
+ */
3043
+ startWebChatProgress(instanceName) {
3044
+ if (this.shuttingDown)
3045
+ return Promise.resolve();
3046
+ return this.sendCancelButton(instanceName);
3047
+ }
3048
+ /**
3049
+ * `copy: false` (#1666/#1668): an entry that is not a copy of a message — the web send's Cancel/progress bubble. It
3050
+ * keeps its place in the order, but the lane's copy rules do not apply to it: its 5 s budget starts when its turn
3051
+ * starts (waiting behind a slow echo is not its delay), its own timeout only releases the lane (the copies queued
3052
+ * behind it are not "behind an ambiguous copy"), and another entry's timeout never drops it. A copy reserved while
3053
+ * any non-copy is still open starts its own budget when the last of them finishes: a slow bubble is no copy's delay,
3054
+ * however far back the copy is. And a timeout only ever drops copies reserved AFTER the entry that timed out — with
3055
+ * deferred budgets, deadlines no longer grow with the order, so that rule is stated, not implied (#1668 r3).
3056
+ */
3057
+ reserveWebChannelEcho(instanceName, sendEcho, opts = {}) {
3058
+ const copy = opts.copy !== false;
3024
3059
  const epoch = this.getDeliveryEpoch(instanceName);
3025
- const deadlineAt = performance.now() + 5_000;
3060
+ const queued = this.webChannelEchoTails.get(instanceName);
3061
+ // The last open non-copy ahead of this copy: its budget starts when that one finishes.
3062
+ const nonCopyAhead = copy && (queued?.nonCopiesOpen ?? 0) > 0 ? queued?.lastNonCopyDone : undefined;
3063
+ let deadlineAt = copy && !nonCopyAhead ? performance.now() + 5_000 : Number.POSITIVE_INFINITY;
3026
3064
  let decide;
3027
3065
  const admission = new Promise(resolve => { decide = resolve; });
3028
- const queue = this.webChannelEchoTails.get(instanceName)
3066
+ const queue = queued
3029
3067
  ?? { tail: Promise.resolve(), pending: new Set() };
3068
+ const seq = queue.nextSeq ?? 0;
3069
+ queue.nextSeq = seq + 1;
3030
3070
  const previous = queue.tail;
3031
3071
  let resolveDone;
3032
3072
  const tail = new Promise(resolve => { resolveDone = resolve; });
@@ -3037,6 +3077,8 @@ export class FleetManager {
3037
3077
  if (done)
3038
3078
  return;
3039
3079
  done = true;
3080
+ if (!copy)
3081
+ queue.nonCopiesOpen = Math.max(0, (queue.nonCopiesOpen ?? 0) - 1);
3040
3082
  clearTimeout(timer);
3041
3083
  queue.pending.delete(entry);
3042
3084
  if (guard && this.webChannelEchoGuards.get(instanceName) === guard) {
@@ -3044,7 +3086,7 @@ export class FleetManager {
3044
3086
  }
3045
3087
  resolveDone();
3046
3088
  };
3047
- const entry = { started: false, drop: () => {
3089
+ const entry = { seq, started: false, copy, drop: () => {
3048
3090
  this.logger.warn({ instanceName }, "Queued web channel echo dropped after ordering timeout");
3049
3091
  finish();
3050
3092
  } };
@@ -3054,14 +3096,16 @@ export class FleetManager {
3054
3096
  expired = true;
3055
3097
  this.logger.warn({ instanceName, inFlight: entry.started }, "Web channel echo ordering timed out");
3056
3098
  finish();
3057
- // Do not post copies that were queued behind an ambiguous platform send.
3058
- for (const pending of queue.pending)
3059
- if (!pending.started)
3060
- pending.drop();
3099
+ // Do not post copies that were queued behind an ambiguous platform send. A non-copy that timed out was no copy
3100
+ // at all (nothing behind it is ambiguous), and a non-copy is never dropped as one.
3101
+ if (copy)
3102
+ for (const pending of queue.pending)
3103
+ if (pending.seq > seq && !pending.started && pending.copy)
3104
+ pending.drop();
3061
3105
  };
3062
3106
  const checkDeadline = () => {
3063
- if (done)
3064
- return;
3107
+ if (done || deadlineAt === Number.POSITIVE_INFINITY)
3108
+ return; // a non-copy's budget has not started yet
3065
3109
  const remaining = deadlineAt - performance.now();
3066
3110
  if (remaining <= 0)
3067
3111
  expire();
@@ -3070,8 +3114,19 @@ export class FleetManager {
3070
3114
  };
3071
3115
  queue.pending.add(entry);
3072
3116
  queue.tail = tail;
3117
+ if (!copy) {
3118
+ queue.nonCopiesOpen = (queue.nonCopiesOpen ?? 0) + 1;
3119
+ queue.lastNonCopyDone = tail;
3120
+ }
3073
3121
  this.webChannelEchoTails.set(instanceName, queue);
3074
3122
  checkDeadline();
3123
+ // Registered before the admission below (which waits on `previous`, itself after the non-copy ahead), so the budget
3124
+ // is set by the time that runs.
3125
+ if (nonCopyAhead)
3126
+ void nonCopyAhead.then(() => { if (!done) {
3127
+ deadlineAt = performance.now() + 5_000;
3128
+ checkDeadline();
3129
+ } });
3075
3130
  void Promise.all([previous, admission]).then(([, accepted]) => {
3076
3131
  if (done)
3077
3132
  return;
@@ -3080,11 +3135,15 @@ export class FleetManager {
3080
3135
  return;
3081
3136
  }
3082
3137
  // A delayed timer callback must not admit an already expired copy.
3083
- if (performance.now() >= deadlineAt) {
3138
+ if (copy && performance.now() >= deadlineAt) {
3084
3139
  expire();
3085
3140
  return;
3086
3141
  }
3087
3142
  entry.started = true;
3143
+ if (!copy) {
3144
+ deadlineAt = performance.now() + 5_000;
3145
+ checkDeadline();
3146
+ }
3088
3147
  guard = { epoch, deadlineAt };
3089
3148
  this.webChannelEchoGuards.set(instanceName, guard);
3090
3149
  let request;
@@ -9350,7 +9409,9 @@ export class FleetManager {
9350
9409
  else
9351
9410
  await this.lifecycle.remove(name, authorization);
9352
9411
  // A later same-name registration owns its files and caches.
9353
- if (this.fleetConfig?.instances[name] || this.daemons.has(name))
9412
+ // Own keys only: an instance named "constructor" is removed as any other (its inherited namesake is not a
9413
+ // re-registration) — #1677 review.
9414
+ if (Object.hasOwn(this.fleetConfig?.instances ?? {}, name) || this.daemons.has(name))
9354
9415
  return;
9355
9416
  execution?.assert();
9356
9417
  // Clean up per-instance tracking maps so they don't grow unbounded
@@ -9362,6 +9423,13 @@ export class FleetManager {
9362
9423
  // must not be shown the old one's conversation, and deleted names must not pile up.
9363
9424
  this.webChatHistory.forget(name);
9364
9425
  this.webFiles.forget(name);
9426
+ // #1664: and it is nobody's favorite any more (every device sees that at its next status frame).
9427
+ try {
9428
+ this.webPrefs.forgetInstance(name);
9429
+ }
9430
+ catch (err) {
9431
+ this.logger.warn({ err: err.message, name }, "Dropping a removed instance from web favorites failed");
9432
+ }
9365
9433
  // Clean up statusline watcher + instance directory
9366
9434
  this.statuslineWatcher.unwatch(name);
9367
9435
  // instanceDirToClean and effectiveBackend are captured before lifecycle.remove
@@ -15639,6 +15707,17 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
15639
15707
  const ch = this.classicChannels.unregister(channelId, adapterId);
15640
15708
  if (!ch)
15641
15709
  return t("classic.no_agent");
15710
+ // #1664: the room is gone (not paused or stopped): nobody's favorite any more — unless the name is still an
15711
+ // instance (fleet.yaml's, or another room's), so a later room of the same name does not inherit an old star.
15712
+ if (!Object.hasOwn(this.fleetConfig?.instances ?? {}, ch.instanceName)
15713
+ && !this.classicChannels.getAll().some((c) => c.instanceName === ch.instanceName)) {
15714
+ try {
15715
+ this.webPrefs.forgetInstance(ch.instanceName);
15716
+ }
15717
+ catch (err) {
15718
+ this.logger.warn({ err: err.message, instanceName: ch.instanceName }, "Dropping a removed room from web favorites failed");
15719
+ }
15720
+ }
15642
15721
  this.instanceWorldBinding.delete(ch.instanceName);
15643
15722
  await this.stopInstance(ch.instanceName).catch(err => this.logger.warn({ err, instanceName: ch.instanceName }, "Failed to stop classic instance"));
15644
15723
  clearPausedMarker(this.getInstanceDir(ch.instanceName));
@@ -18357,6 +18436,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
18357
18436
  uptime: Math.floor((Date.now() - this.startedAt) / 1000),
18358
18437
  // #1519 P7: how many chat connections are configured — 0 is a web-only fleet, which gets the first-run card.
18359
18438
  connections: (this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : [])).length,
18439
+ // #1665: the host preferences' revision — a page that has another re-reads /ui/prefs (no values in this frame).
18440
+ prefs_rev: this.webPrefs.rev(),
18360
18441
  };
18361
18442
  }
18362
18443
  }