@songsid/agend 2.1.4-beta.32 → 2.1.4-beta.34

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.
@@ -3932,8 +3932,6 @@ export class FleetManager {
3932
3932
  }
3933
3933
  /** Handle outbound tool calls from a daemon instance */
3934
3934
  async handleOutboundFromInstance(instanceName, msg) {
3935
- if (this.worlds.size === 0)
3936
- return;
3937
3935
  this.touchActivity(instanceName);
3938
3936
  this.setTopicIcon(instanceName, "green");
3939
3937
  const tool = msg.tool;
@@ -3943,13 +3941,21 @@ export class FleetManager {
3943
3941
  const senderSessionName = msg.senderSessionName;
3944
3942
  const respond = (result, error) => {
3945
3943
  const ipc = this.instanceIpcClients.get(instanceName);
3944
+ let sent = false;
3946
3945
  if (fleetRequestId) {
3947
- ipc?.send({ type: "fleet_outbound_response", fleetRequestId, result, error });
3946
+ sent = ipc?.send({ type: "fleet_outbound_response", fleetRequestId, result, error }) ?? false;
3948
3947
  }
3949
3948
  else {
3950
- ipc?.send({ type: "fleet_outbound_response", requestId, result, error });
3949
+ sent = ipc?.send({ type: "fleet_outbound_response", requestId, result, error }) ?? false;
3950
+ }
3951
+ if (!sent) {
3952
+ this.logger.warn({ instanceName, tool, requestId, fleetRequestId, error }, "Fleet outbound result could not be returned — instance IPC is disconnected");
3951
3953
  }
3952
3954
  };
3955
+ if (this.worlds.size === 0) {
3956
+ respond(null, "Channel adapters are not ready — retry shortly");
3957
+ return;
3958
+ }
3953
3959
  // Resolve threadId: use sender's topic_id if sender is a known fleet instance,
3954
3960
  // fall back to general topic if sender is unknown, or IPC owner if no sender.
3955
3961
  const senderInstanceName = senderSessionName && this.fleetConfig?.instances[senderSessionName]
@@ -3994,17 +4000,30 @@ export class FleetManager {
3994
4000
  if (tool === "reply") {
3995
4001
  const ticket = this.replyDeduper.begin(instanceName, String(args.text ?? ""), Array.isArray(args.files) ? args.files : []);
3996
4002
  if (ticket.duplicate) {
3997
- this.logger.info({ instanceName }, "Duplicate reply suppressed — replaying the original send's outcome");
4003
+ this.logger.info({ instanceName }, "Concurrent duplicate reply joined — awaiting the original send's outcome");
3998
4004
  ticket.subscribe(respond);
3999
4005
  return;
4000
4006
  }
4001
4007
  const original = respond;
4002
4008
  const respondAndRecord = (result, error) => {
4003
4009
  ticket.complete(result, error);
4010
+ // Return the platform outcome first. Bookkeeping below must never turn
4011
+ // a confirmed Discord/Telegram POST into a tool error if a secondary
4012
+ // log/button side effect happens to throw.
4004
4013
  original(result, error);
4014
+ // The adapter resolves only after the platform POST returns. Keep
4015
+ // outward-facing logs/cancel state on the same confirmation boundary:
4016
+ // a routed-but-failed reply is not a delivered reply.
4017
+ if (!error && result != null) {
4018
+ try {
4019
+ this.afterReplyRouted(instanceName, args, senderSessionName);
4020
+ }
4021
+ catch (err) {
4022
+ this.logger.warn({ err, instanceName }, "Reply delivered but post-delivery bookkeeping failed");
4023
+ }
4024
+ }
4005
4025
  };
4006
4026
  if (routeToolCall(outAdapter, tool, args, threadId, respondAndRecord)) {
4007
- this.afterReplyRouted(instanceName, args, senderSessionName);
4008
4027
  return;
4009
4028
  }
4010
4029
  // routeToolCall knows "reply"; not handling it means the world changed.
@@ -9224,9 +9243,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
9224
9243
  const lastActivity = inst.classic
9225
9244
  ? Math.max(persistedInboundAt ?? 0, readClassicLastActivityAt(this.dataDir, inst.name) ?? 0) || null
9226
9245
  : (persistedInboundAt ?? this.lastActivityMs(inst.name)) || null;
9227
- const backend = inst.classic
9228
- ? this.classicChannels?.getBackendByInstance(inst.name, this.fleetConfig?.defaults.backend) ?? "claude-code"
9229
- : config?.backend ?? "claude-code";
9246
+ const backend = this.backendNameForInstance(inst.name);
9247
+ const resolvedModel = this.resolveInstanceModel(inst.name);
9248
+ const effortStrategy = this.effortStrategyFor(inst.name);
9249
+ const resolvedEffort = this.resolveInstanceEffort(inst.name);
9230
9250
  // Find claimed tasks for this instance
9231
9251
  let currentTask = null;
9232
9252
  try {
@@ -9241,6 +9261,14 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
9241
9261
  ...inst,
9242
9262
  description: config?.description ?? ("classicName" in inst ? inst.classicName : null),
9243
9263
  backend,
9264
+ // Settings renders these runtime-effective values rather than the
9265
+ // sparse user-authored YAML. `auto` means the supported CLI is
9266
+ // using its own effort default; null is reserved for unsupported.
9267
+ model: resolvedModel.model,
9268
+ model_display: resolvedModel.display,
9269
+ model_source: resolvedModel.source,
9270
+ effort: effortStrategy === "unsupported" ? null : (resolvedEffort.effort ?? "auto"),
9271
+ effort_supported: effortStrategy !== "unsupported",
9244
9272
  tool_set: config?.tool_set ?? "full",
9245
9273
  general_topic: config?.general_topic ?? false,
9246
9274
  // User activity is persisted by the daemon, so both the board and