@songsid/agend 2.1.0-beta.5 → 2.1.0-beta.7

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.
@@ -93,6 +93,8 @@ export class FleetManager {
93
93
  // Topic icon + auto-archive state
94
94
  topicIcons = {};
95
95
  lastActivity = new Map();
96
+ /** Latest pane-derived execution snapshot reported by each daemon. */
97
+ instanceStateCache = new Map();
96
98
  lastInboundUser = new Map(); // instanceName → last username
97
99
  // Active "🛑 Cancel" buttons, tracked per button (keyed by messageId) rather
98
100
  // than one-per-instance. A button is retired (deleted, with bounded retry) on
@@ -160,6 +162,7 @@ export class FleetManager {
160
162
  const instances = Object.keys(this.fleetConfig?.instances ?? {}).map(name => ({
161
163
  name,
162
164
  status: this.getInstanceStatus(name),
165
+ state: this.getInstanceExecutionState(name),
163
166
  ipc: this.instanceIpcClients.has(name),
164
167
  costCents: this.costGuard?.getDailyCostCents(name) ?? 0,
165
168
  rateLimits: this.statuslineWatcher.getRateLimits(name) ?? null,
@@ -290,6 +293,25 @@ export class FleetManager {
290
293
  return "crashed";
291
294
  }
292
295
  }
296
+ getInstanceExecutionState(name) {
297
+ if (this.lifecycle.isPaused(name))
298
+ return null;
299
+ return this.instanceStateCache.get(name)?.state ?? null;
300
+ }
301
+ cacheInstanceExecutionState(name, msg) {
302
+ const state = msg.state;
303
+ if (state !== "idle" && state !== "working" && state !== "stuck")
304
+ return;
305
+ const previous = this.instanceStateCache.get(name);
306
+ const now = Date.now();
307
+ const numberOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
308
+ this.instanceStateCache.set(name, {
309
+ state,
310
+ unchangedForMs: numberOr(msg.unchangedForMs, previous?.unchangedForMs ?? 0),
311
+ observedAt: numberOr(msg.observedAt, now),
312
+ stateChangedAt: numberOr(msg.stateChangedAt, previous?.state === state ? previous.stateChangedAt : now),
313
+ });
314
+ }
293
315
  /** Single delivery facade: wake an auto-paused CLI before sending to daemon IPC. */
294
316
  async deliverToInstance(instanceName, payload) {
295
317
  if (this.lifecycle.isPaused(instanceName)) {
@@ -367,6 +389,7 @@ export class FleetManager {
367
389
  }
368
390
  async stopInstance(name) {
369
391
  this.failoverActive.delete(name);
392
+ this.instanceStateCache.delete(name);
370
393
  return this.lifecycle.stop(name);
371
394
  }
372
395
  /** Restart a single instance, reloading fleet.yaml first to pick up config changes. */
@@ -1532,10 +1555,16 @@ export class FleetManager {
1532
1555
  else if (msg.type === "fleet_set_description") {
1533
1556
  this.handleSetDescription(name, msg);
1534
1557
  }
1558
+ else if (msg.type === "instance_state" || msg.type === "instance_state_response") {
1559
+ this.cacheInstanceExecutionState(name, msg);
1560
+ }
1535
1561
  }, this.logger, `ipc.message[${name}]`));
1536
1562
  // Ask daemon for any sessions that registered before we connected
1537
1563
  // (fixes race condition where mcp_ready was broadcast before fleet manager connected)
1538
1564
  ipc.send({ type: "query_sessions" });
1565
+ // The initial state transition may have happened before FleetManager
1566
+ // connected, so seed the cache instead of waiting for another transition.
1567
+ ipc.send({ type: "query_instance_state", requestId: `fleet-state-${Date.now()}` });
1539
1568
  this.logger.debug({ name }, "Connected to instance IPC");
1540
1569
  if (!this.statuslineWatcher.has(name)) {
1541
1570
  this.statuslineWatcher.watch(name);
@@ -4349,6 +4378,7 @@ When users create specialized instances, suggest these configurations:
4349
4378
  lastActivity: this.lastActivityMs(inst.name) || null,
4350
4379
  currentTask,
4351
4380
  idle: this.getInstanceIdle(inst.name),
4381
+ state: this.getInstanceExecutionState(inst.name),
4352
4382
  };
4353
4383
  });
4354
4384
  res.setHeader("Access-Control-Allow-Origin", "*");