@songsid/agend 2.1.8-beta.10 → 2.1.8-beta.12

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.
@@ -110,7 +110,7 @@ const DEFS = [
110
110
  ["preview_emojis", schemas.PreviewEmojisArgs,
111
111
  "Download up to 8 Discord server emojis from list_emojis so you can see them: returns a local image path per emoji to Read before you pick one."],
112
112
  ["set_persona_emoji", schemas.SetPersonaEmojiArgs,
113
- "Set your persona emoji: the status stamp (default: delivered) your bot puts on messages it handles, so people can tell you apart from other bots. Validated the way Settings validates it; takes effect on your next stamp."],
113
+ "Set your persona emoji: the status stamp (default: delivered) your bot puts on messages it handles, so people can tell you apart from other bots. Other statuses: received, queued, processing, failed, progress_prefix, and photo / attachment (the stamp on a photo / file your bot saved). Validated the way Settings validates it; takes effect on your next stamp."],
114
114
  ["set_description", schemas.SetDescriptionArgs,
115
115
  "Set your role description. This is injected into your system prompt as your role definition. Takes effect on next session restart."],
116
116
  ["checkout_repo", schemas.CheckoutRepoArgs,
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/channel/mcp-tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/channel/mcp-tools.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,7 @@ import { ClassicChannelManager } from "./classic-channel-manager.js";
26
26
  import { type ExplicitInstanceRemoval } from "./instance-removal.js";
27
27
  import type { InstanceState } from "./backend/types.js";
28
28
  import { WakeCoordinator } from "./wake-coordinator.js";
29
+ import { TargetQueueWorker } from "./target-queue-worker.js";
29
30
  import { resolveDeliveryWorkerMode } from "./types.js";
30
31
  import { type ConnectionMetadata, type ConnectionBinding, type BindingProbe, type SecretApplyJob, type ProviderSecretApplyJob } from "./connection-secrets.js";
31
32
  import { type ProviderSecretStatus } from "./provider-secret-registry.js";
@@ -130,6 +131,12 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
130
131
  private deliveryOutboxRecovered;
131
132
  /** Phase 2b: wakes paused targets for queued durable work (delivery_worker ≠ off). */
132
133
  wakeCoordinator: WakeCoordinator | null;
134
+ /**
135
+ * Phase 2c: targets whose durable lane a TargetQueueWorker owns
136
+ * (delivery_worker: on). Granted and released only in the pump's
137
+ * synchronous section and only while the lane is empty; the pump skips them.
138
+ */
139
+ readonly queueWorkers: Map<string, TargetQueueWorker>;
133
140
  private deliveryPumpScheduled;
134
141
  private deliveryPumpRunning;
135
142
  private deliveryPumpTimer;
@@ -383,6 +390,17 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
383
390
  sweepReplyObligations(now?: Date): void;
384
391
  private scheduleDeliveryOutboxPump;
385
392
  private runDeliveryOutboxPump;
393
+ /**
394
+ * Phase 2c ownership (design §1.5-A), called synchronously from the pump
395
+ * before its claim loop, so a grant and the pump's own claims cannot
396
+ * interleave. A worker is granted a target's lane only when the target is
397
+ * `on` and its lane is empty: no row in flight in the outbox and no pump
398
+ * dispatch holding it. It gives the lane back only when it is idle and the
399
+ * target is no longer `on` (or has nothing pending). A flag change never
400
+ * takes a lane mid-drain.
401
+ */
402
+ private reconcileQueueWorkers;
403
+ private createQueueWorker;
386
404
  private dispatchDurableDelivery;
387
405
  private waitForDurableLaneRelease;
388
406
  private spawnConcurrency;
@@ -1216,6 +1234,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1216
1234
  * An empty emoji removes that status's override.
1217
1235
  */
1218
1236
  setPersonaEmoji(instanceName: string, args: Record<string, unknown>): Promise<Record<string, unknown>>;
1237
+ /** A ClassicBot instance: it may see (list/preview) emojis, never set a per-instance stamp. */
1238
+ private isClassicPersonaTarget;
1219
1239
  private personaEmojiMissing;
1220
1240
  /** What the instructions tell `instanceName` not to react with (its own status set). */
1221
1241
  statusEmojiAvoidList(instanceName: string): string[];
@@ -1236,6 +1256,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1236
1256
  * own adapter rather than through reactMessageStatus (classic bots).
1237
1257
  */
1238
1258
  private receivedReactionFor;
1259
+ /**
1260
+ * The stamp for an inbound photo / file a classic bot saved (#1080): the
1261
+ * instance's `status_emojis.photo|attachment`, then its connection's, then
1262
+ * the built-in (📸/📎, or 👌/👍 on Telegram — what these paths always stamped).
1263
+ * Anything else a saved attachment can be counts as a file.
1264
+ */
1265
+ private savedAttachmentReactionFor;
1239
1266
  reactMessageStatus(instanceName: string, chatId: string, messageId: string, status: DeliveryStatus, threadId?: string): void;
1240
1267
  /**
1241
1268
  * Apply a terminal delivery verdict. On Telegram the built-in delivered is
@@ -68,6 +68,7 @@ import { validateFleetConfig } from "./config-validator.js";
68
68
  import { readLastInboundAt } from "./daemon.js";
69
69
  import { clearPausedMarker, readPausedAt, readPauseReason, writePausedMarker } from "./pause-marker.js";
70
70
  import { DEFAULT_WARM_OVERFLOW, WakeCoordinator } from "./wake-coordinator.js";
71
+ import { TargetQueueWorker } from "./target-queue-worker.js";
71
72
  import { resolveDeliveryWorkerMode } from "./types.js";
72
73
  import { isFleetStartCommandLine, readProcessCommandLine, releaseProcessFleetLock } from "./fleet-lock.js";
73
74
  import { isSetupComplete, markSetupComplete } from "./setup-marker.js";
@@ -290,6 +291,12 @@ export class FleetManager {
290
291
  deliveryOutboxRecovered = false;
291
292
  /** Phase 2b: wakes paused targets for queued durable work (delivery_worker ≠ off). */
292
293
  wakeCoordinator = null;
294
+ /**
295
+ * Phase 2c: targets whose durable lane a TargetQueueWorker owns
296
+ * (delivery_worker: on). Granted and released only in the pump's
297
+ * synchronous section and only while the lane is empty; the pump skips them.
298
+ */
299
+ queueWorkers = new Map();
293
300
  deliveryPumpScheduled = false;
294
301
  deliveryPumpRunning = false;
295
302
  deliveryPumpTimer = null;
@@ -729,11 +736,13 @@ export class FleetManager {
729
736
  const failure = this.wakeCoordinator?.wakeFailure(target);
730
737
  return failure ? `target could not be woken (${failure})` : undefined;
731
738
  });
739
+ this.reconcileQueueWorkers(outbox);
732
740
  while (this.activeDurableTargets.size < 8) {
733
741
  const claimed = outbox.claimNext(this.managerBootId,
734
742
  // Phase 2b: a wake_only target is claimed only once it is awake; the
735
743
  // wake coordinator, not this pump, wakes it (no claim, no attempt).
736
- target => this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target)
744
+ // Phase 2c: a lane a queue worker owns is never claimed here.
745
+ target => this.queueWorkers.has(target) || this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target)
737
746
  ? null
738
747
  : this.daemons.get(target)?.bootId ?? null, this.activeDurableTargets);
739
748
  if (!claimed)
@@ -763,7 +772,67 @@ export class FleetManager {
763
772
  }
764
773
  }
765
774
  }
766
- async dispatchDurableDelivery(claimed) {
775
+ /**
776
+ * Phase 2c ownership (design §1.5-A), called synchronously from the pump
777
+ * before its claim loop, so a grant and the pump's own claims cannot
778
+ * interleave. A worker is granted a target's lane only when the target is
779
+ * `on` and its lane is empty: no row in flight in the outbox and no pump
780
+ * dispatch holding it. It gives the lane back only when it is idle and the
781
+ * target is no longer `on` (or has nothing pending). A flag change never
782
+ * takes a lane mid-drain.
783
+ */
784
+ reconcileQueueWorkers(outbox) {
785
+ const pendingTargets = new Set(outbox.listPending().map(row => row.targetInstance));
786
+ for (const [target, worker] of this.queueWorkers) {
787
+ const keep = this.deliveryWorkerMode(target) === "on" && pendingTargets.has(target);
788
+ if (!keep && !worker.busy && !worker.inFlight)
789
+ this.queueWorkers.delete(target);
790
+ }
791
+ for (const target of pendingTargets) {
792
+ if (this.queueWorkers.has(target) || this.deliveryWorkerMode(target) !== "on")
793
+ continue;
794
+ const laneEmpty = !this.activeDurableTargets.has(target)
795
+ && outbox.countForTarget(target, ["delivering", "submission_started"]) === 0;
796
+ if (laneEmpty)
797
+ this.queueWorkers.set(target, this.createQueueWorker(target));
798
+ }
799
+ for (const worker of this.queueWorkers.values())
800
+ void worker.drain();
801
+ }
802
+ createQueueWorker(target) {
803
+ const worker = new TargetQueueWorker(target, {
804
+ owns: () => this.queueWorkers.get(target) === worker,
805
+ wanted: () => this.deliveryWorkerMode(target) === "on",
806
+ available: () => !this.shuttingDown && this.deliveryOutbox?.isOpen === true,
807
+ blocked: () => this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target) === true,
808
+ daemonBootId: () => this.daemons.get(target)?.bootId ?? null,
809
+ claim: bootId => this.deliveryOutbox?.claimNext(this.managerBootId, candidate => candidate === target ? bootId : null, new Set()),
810
+ tryAcquireBudget: () => {
811
+ if (this.activeDurableTargets.size >= 8)
812
+ return false;
813
+ this.activeDurableTargets.add(target);
814
+ return true;
815
+ },
816
+ releaseBudget: () => { this.activeDurableTargets.delete(target); },
817
+ dispatch: async (claimed) => {
818
+ try {
819
+ await this.dispatchDurableDelivery(claimed, { holdLaneOnTransportError: true });
820
+ }
821
+ finally {
822
+ // The row's state event can fire before the hand-off settles; the
823
+ // pump pass it starts then still sees this worker busy and keeps it
824
+ // as owner. One kick after the dispatch has really settled lets the
825
+ // pump release an owner that is no longer wanted (or claim the next
826
+ // row). The no-claim path never kicks, so no retry hot loop.
827
+ this.scheduleDeliveryOutboxPump();
828
+ }
829
+ },
830
+ kickCoordinator: () => this.wakeCoordinator?.kick(),
831
+ logger: this.logger,
832
+ });
833
+ return worker;
834
+ }
835
+ async dispatchDurableDelivery(claimed, opts = {}) {
767
836
  const outbox = this.deliveryOutbox;
768
837
  if (!outbox)
769
838
  return;
@@ -817,7 +886,10 @@ export class FleetManager {
817
886
  catch (err) {
818
887
  const latest = outbox.get(claimed.deliveryId);
819
888
  const reason = err instanceof Error ? err.message : String(err);
820
- if (latest?.state === "submission_started") {
889
+ if (opts.holdLaneOnTransportError && (latest?.state === "submission_started" || latest?.state === "reconciliation_pending")) {
890
+ this.logger.warn({ deliveryId: claimed.deliveryId, target, attempt, err: reason }, "Transport failed after the submission began; holding the lane for the daemon's verdict or its generation's reconciliation");
891
+ }
892
+ else if (latest?.state === "submission_started") {
821
893
  outbox.complete(claimed.deliveryId, claimed.targetDaemonBootId, claimed.attemptNo, "uncertain", reason);
822
894
  }
823
895
  else {
@@ -7197,19 +7269,26 @@ export class FleetManager {
7197
7269
  * Settings picker (#1005/#1021), so an agent and an operator see one truth.
7198
7270
  */
7199
7271
  async listEmojisFor(instanceName, refresh = false) {
7200
- const self = this.fleetConfig?.instances[instanceName];
7201
- if (!self)
7272
+ // Seeing is not setting: a ClassicBot instance has no per-instance stamp,
7273
+ // but it reacts in its own channel like any bot and must be able to see
7274
+ // the emojis it can use (only set_persona_emoji refuses it).
7275
+ // Own entries only: an inherited name (constructor, __proto__, toString…)
7276
+ // is not a fleet instance, as set_persona_emoji already holds.
7277
+ const self = this.ownInstanceConfig(instanceName);
7278
+ const classic = !self && this.isClassicPersonaTarget(instanceName);
7279
+ if (!self && !classic)
7202
7280
  return { error: this.personaEmojiMissing(instanceName) };
7203
7281
  const worldId = this.getInstanceAdapterId(instanceName);
7204
7282
  const { platform } = this.resolveStatusEmojisFor(instanceName);
7205
7283
  const channel = (worldId ? this.worlds.get(worldId)?.channelConfig : undefined) ?? this.getChannelConfig(worldId ?? undefined);
7206
- const current = previewStatusEmojis({ platform, platformConfig: channel?.options?.status_emojis, instanceConfig: self.status_emojis });
7284
+ const current = previewStatusEmojis({ platform, platformConfig: channel?.options?.status_emojis, instanceConfig: self?.status_emojis });
7207
7285
  const out = {
7208
7286
  platform: platform ?? null,
7209
7287
  statuses: current.entries.map(e => ({ status: e.key, value: e.value, source: e.source })),
7210
7288
  standard: platform === "telegram"
7211
7289
  ? { reactions: [...TELEGRAM_REACTION_EMOJIS], note: "Telegram reacts only with these; progress_prefix may be any single emoji" }
7212
7290
  : { suggestions: STATUS_EMOJI_SUGGESTIONS, note: "any single emoji works" },
7291
+ ...(classic ? { note: "ClassicBot instances have no per-instance status emojis: the statuses above are the connection's, which an operator sets in Settings. You can still use any emoji listed here in your own reactions." } : {}),
7213
7292
  };
7214
7293
  if (platform === "discord" && worldId) {
7215
7294
  const listed = await this.listGuildEmojis(worldId, refresh);
@@ -7240,9 +7319,10 @@ export class FleetManager {
7240
7319
  * from that listed emoji's numeric id. Static PNG, size-capped, cached by id.
7241
7320
  */
7242
7321
  async previewEmojis(instanceName, args) {
7243
- const self = this.fleetConfig?.instances[instanceName];
7244
- if (!self)
7322
+ // Seeing, like list_emojis: ClassicBot instances may preview too.
7323
+ if (!this.ownInstanceConfig(instanceName) && !this.isClassicPersonaTarget(instanceName)) {
7245
7324
  return { error: this.personaEmojiMissing(instanceName) };
7325
+ }
7246
7326
  const wanted = args.emojis;
7247
7327
  if (!Array.isArray(wanted) || wanted.length === 0 || wanted.some(e => typeof e !== "string")) {
7248
7328
  return { error: "emojis is required: a list of <:name:id> values from list_emojis" };
@@ -7401,6 +7481,10 @@ export class FleetManager {
7401
7481
  const resolved = this.resolveStatusEmojisFor(instanceName);
7402
7482
  return { status, value: value ?? null, now: resolved[status], status_emojis: current.status_emojis ?? null };
7403
7483
  }
7484
+ /** A ClassicBot instance: it may see (list/preview) emojis, never set a per-instance stamp. */
7485
+ isClassicPersonaTarget(instanceName) {
7486
+ return this.classicChannels?.getAll().some(c => c.instanceName === instanceName) ?? false;
7487
+ }
7404
7488
  personaEmojiMissing(instanceName) {
7405
7489
  return this.classicChannels?.getAll().some(c => c.instanceName === instanceName)
7406
7490
  ? "ClassicBot instances have no per-instance status emojis; an operator sets the connection's in Settings"
@@ -7462,6 +7546,16 @@ export class FleetManager {
7462
7546
  const resolved = this.resolveStatusEmojisFor(instanceName, adapterId, adapter);
7463
7547
  return reactionForm(resolved.platform, resolved.received);
7464
7548
  }
7549
+ /**
7550
+ * The stamp for an inbound photo / file a classic bot saved (#1080): the
7551
+ * instance's `status_emojis.photo|attachment`, then its connection's, then
7552
+ * the built-in (📸/📎, or 👌/👍 on Telegram — what these paths always stamped).
7553
+ * Anything else a saved attachment can be counts as a file.
7554
+ */
7555
+ savedAttachmentReactionFor(instanceName, adapter, adapterId, kind) {
7556
+ const resolved = this.resolveStatusEmojisFor(instanceName, adapterId, adapter);
7557
+ return reactionForm(resolved.platform, kind === "photo" ? resolved.photo : resolved.attachment);
7558
+ }
7465
7559
  reactMessageStatus(instanceName, chatId, messageId, status, threadId) {
7466
7560
  // React via the adapter BOUND to this instance — NOT the first discord world.
7467
7561
  // Otherwise, in a same-channel/same-guild multi-bot setup, the inbound 👀
@@ -10579,9 +10673,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10579
10673
  const reactAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
10580
10674
  const noMentionReactChatId = msg.threadId ?? msg.chatId;
10581
10675
  if (reactAdapter && noMentionReactChatId && msg.messageId) {
10582
- const emoji = msg.source === "telegram"
10583
- ? (saved.kind === "photo" ? "👌" : "👍")
10584
- : (saved.kind === "photo" ? "📸" : "📎");
10676
+ const emoji = this.savedAttachmentReactionFor(instanceName, reactAdapter, msg.adapterId, saved.kind);
10585
10677
  reactAdapter.react(noMentionReactChatId, msg.messageId, emoji)
10586
10678
  .catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
10587
10679
  }
@@ -10612,9 +10704,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10612
10704
  return;
10613
10705
  // Attachments already saved at the top of the collab block.
10614
10706
  if (saved && classicAdapter && collabReactChatId && msg.messageId) {
10615
- const emoji = msg.source === "telegram"
10616
- ? (saved.kind === "photo" ? "👌" : "👍")
10617
- : (saved.kind === "photo" ? "📸" : "📎");
10707
+ const emoji = this.savedAttachmentReactionFor(instanceName, classicAdapter, msg.adapterId, saved.kind);
10618
10708
  classicAdapter.react(collabReactChatId, msg.messageId, emoji)
10619
10709
  .catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
10620
10710
  }
@@ -10662,10 +10752,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10662
10752
  const reactAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
10663
10753
  const reactChatId = msg.threadId ?? msg.chatId;
10664
10754
  if (saved && reactAdapter && reactChatId && msg.messageId) {
10665
- // Telegram only supports limited emoji for reactions; use 👌 for photo, 👍 for file
10666
- const emoji = msg.source === "telegram"
10667
- ? (saved.kind === "photo" ? "👌" : "👍")
10668
- : (saved.kind === "photo" ? "📸" : "📎");
10755
+ const emoji = this.savedAttachmentReactionFor(instanceName, reactAdapter, msg.adapterId, saved.kind);
10669
10756
  reactAdapter.react(reactChatId, msg.messageId, emoji)
10670
10757
  .catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
10671
10758
  }
@@ -10711,9 +10798,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10711
10798
  classicMsgAdapter.react(reactChatId, msg.messageId, this.receivedReactionFor(instanceName, classicMsgAdapter, msg.adapterId))
10712
10799
  .catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
10713
10800
  if (saved) {
10714
- const savedEmoji = msg.source === "telegram"
10715
- ? (saved.kind === "photo" ? "👌" : "👍")
10716
- : (saved.kind === "photo" ? "📸" : "📎");
10801
+ const savedEmoji = this.savedAttachmentReactionFor(instanceName, classicMsgAdapter, msg.adapterId, saved.kind);
10717
10802
  classicMsgAdapter.react(reactChatId, msg.messageId, savedEmoji)
10718
10803
  .catch(e => this.logger.debug({ err: e.message }, "Auto-react failed"));
10719
10804
  }