@songsid/agend 2.1.8-beta.1 → 2.1.8-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 (46) hide show
  1. package/dist/agent-cli.js +9 -0
  2. package/dist/agent-cli.js.map +1 -1
  3. package/dist/agent-endpoint.d.ts +3 -1
  4. package/dist/agent-endpoint.js +7 -1
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/backend/cli-env-probe-worker.d.ts +1 -0
  7. package/dist/backend/cli-env-probe-worker.js +25 -0
  8. package/dist/backend/cli-env-probe-worker.js.map +1 -0
  9. package/dist/backend/codex.js +15 -1
  10. package/dist/backend/codex.js.map +1 -1
  11. package/dist/backend/muse.js +10 -1
  12. package/dist/backend/muse.js.map +1 -1
  13. package/dist/backend/types.d.ts +12 -1
  14. package/dist/backend/types.js +14 -3
  15. package/dist/backend/types.js.map +1 -1
  16. package/dist/channel/adapters/discord.d.ts +16 -4
  17. package/dist/channel/adapters/discord.js +20 -5
  18. package/dist/channel/adapters/discord.js.map +1 -1
  19. package/dist/channel/mcp-tools.js +4 -0
  20. package/dist/channel/mcp-tools.js.map +1 -1
  21. package/dist/daemon.d.ts +31 -0
  22. package/dist/daemon.js +107 -25
  23. package/dist/daemon.js.map +1 -1
  24. package/dist/fleet-context.d.ts +5 -0
  25. package/dist/fleet-context.js.map +1 -1
  26. package/dist/fleet-manager.d.ts +47 -11
  27. package/dist/fleet-manager.js +322 -61
  28. package/dist/fleet-manager.js.map +1 -1
  29. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +45 -0
  30. package/dist/locale.js +18 -0
  31. package/dist/locale.js.map +1 -1
  32. package/dist/outbound-schemas.d.ts +18 -4
  33. package/dist/outbound-schemas.js +8 -0
  34. package/dist/outbound-schemas.js.map +1 -1
  35. package/dist/settings-api.d.ts +6 -8
  36. package/dist/settings-api.js +9 -4
  37. package/dist/settings-api.js.map +1 -1
  38. package/dist/status-emojis.d.ts +19 -0
  39. package/dist/status-emojis.js.map +1 -1
  40. package/dist/tool-permissions.js +6 -2
  41. package/dist/tool-permissions.js.map +1 -1
  42. package/dist/topic-commands.d.ts +9 -0
  43. package/dist/topic-commands.js +41 -2
  44. package/dist/topic-commands.js.map +1 -1
  45. package/dist/ui/settings.html +23 -11
  46. package/package.json +1 -1
package/dist/daemon.js CHANGED
@@ -906,6 +906,11 @@ export class BlockingProcessDetector {
906
906
  this.notified = false;
907
907
  }
908
908
  }
909
+ /** Response types the fleet sends back for tool calls the daemon forwarded. */
910
+ const FLEET_RESPONSE_TYPES = new Set([
911
+ "fleet_schedule_response", "fleet_outbound_response", "fleet_decision_response", "fleet_task_response",
912
+ "fleet_display_name_response", "fleet_description_response", "fleet_persona_emoji_response",
913
+ ]);
909
914
  export class Daemon extends EventEmitter {
910
915
  name;
911
916
  config;
@@ -1506,21 +1511,7 @@ export class Daemon extends EventEmitter {
1506
1511
  await this.ipcServer.listen();
1507
1512
  ipcListening = true;
1508
1513
  // Permanent IPC dispatcher: routes responses to pending requests by type+id key
1509
- this.ipcServer.on("message", (msg) => {
1510
- const type = msg.type;
1511
- if (!type)
1512
- return;
1513
- // Build lookup key matching the pattern used when registering
1514
- let key;
1515
- if ((type === "fleet_schedule_response" || type === "fleet_outbound_response" || type === "fleet_decision_response" || type === "fleet_task_response" || type === "fleet_display_name_response" || type === "fleet_description_response") && msg.fleetRequestId) {
1516
- key = String(msg.fleetRequestId);
1517
- }
1518
- if (key && this.pendingIpcRequests.has(key)) {
1519
- const handler = this.pendingIpcRequests.get(key);
1520
- this.pendingIpcRequests.delete(key);
1521
- handler(msg);
1522
- }
1523
- });
1514
+ this.ipcServer.on("message", (msg) => this.routeFleetResponse(msg));
1524
1515
  // IPC message relay: when daemon wants to push a channel message to Claude,
1525
1516
  // it broadcasts to all IPC clients (the MCP server is one of them).
1526
1517
  // When MCP server sends a tool_call, daemon handles it via the messageBus.
@@ -5045,10 +5036,13 @@ export class Daemon extends EventEmitter {
5045
5036
  // The pane text comes back with the verdict: a transient that is still
5046
5037
  // repainting is still working, and that is the only thing separating a
5047
5038
  // slow resume from a wedged one.
5039
+ if (transient)
5040
+ this.resetFooterFallback();
5048
5041
  return transient ? { state: "active", transient, pane } : { state: "clear" };
5049
5042
  }
5050
5043
  catch (err) {
5051
5044
  this.logger.debug({ err }, "capture-pane failed during the input-transient probe — pane state unknown");
5045
+ this.resetFooterFallback();
5052
5046
  return { state: "unknown" };
5053
5047
  }
5054
5048
  }
@@ -5259,13 +5253,77 @@ export class Daemon extends EventEmitter {
5259
5253
  return !check;
5260
5254
  try {
5261
5255
  const mode = await this.tmux.getPaneInputMode?.();
5262
- if (mode !== "raw")
5256
+ if (mode !== "raw") {
5257
+ this.resetFooterFallback();
5263
5258
  return false;
5264
- return check.call(this.backend, await this.tmux.capturePane());
5259
+ }
5260
+ return this.deliveryInputReadyPane(await this.tmux.capturePane());
5265
5261
  }
5266
5262
  catch {
5263
+ this.resetFooterFallback();
5264
+ return false;
5265
+ }
5266
+ }
5267
+ /** Pane text of the current footer-fallback candidate, and since when it has been unchanged. */
5268
+ footerFallbackPaneKey = null;
5269
+ footerFallbackStableSince = 0;
5270
+ /** The launch the candidate was seen in: a new spawn or launch attempt starts over. */
5271
+ footerFallbackEpoch = null;
5272
+ /**
5273
+ * Forget the footer-fallback candidate. Called whenever the pane is seen
5274
+ * outside the candidate state (busy, a dialog, a transient, unreadable) and
5275
+ * on every spawn: "stable for UNKNOWN_LAYOUT_STABLE_MS" must mean this screen,
5276
+ * continuously, not time accumulated across an interruption.
5277
+ */
5278
+ resetFooterFallback() {
5279
+ this.footerFallbackPaneKey = null;
5280
+ this.footerFallbackEpoch = null;
5281
+ }
5282
+ footerFallbackWarnedGeneration = null;
5283
+ /**
5284
+ * The input-row proof used before a paste (#931/#947), with one fallback
5285
+ * (#1031). Codex sometimes paints an idle composer without its Context
5286
+ * status item even though tui.status_line asks for it: after four fleet
5287
+ * restarts, sol's resumed session showed only `⚠ 2 warnings · f2 to view`.
5288
+ * The proof needs a recognised footer, so the first delivery after each
5289
+ * restart waited 30 minutes, failed, and waited again, for seven hours.
5290
+ *
5291
+ * When the footer is the only thing missing, the #978 structural evidence
5292
+ * stands in for it, all of it required: the backend's own stable-unknown
5293
+ * idle check (an empty live composer; no busy row, esc-to-interrupt row,
5294
+ * queued ↳ input or known picker), no input transient such as the resume
5295
+ * load, and the same screen for UNKNOWN_LAYOUT_STABLE_MS. The raw-tty
5296
+ * requirement stays with the caller.
5297
+ */
5298
+ deliveryInputReadyPane(pane) {
5299
+ const check = this.backend?.isDeliveryInputReadyPane;
5300
+ if (!check)
5301
+ return true;
5302
+ if (check.call(this.backend, pane)) {
5303
+ this.footerFallbackPaneKey = null;
5304
+ return true;
5305
+ }
5306
+ const stableUnknown = this.backend?.isStableUnknownLayoutIdlePane;
5307
+ if (!stableUnknown?.call(this.backend, pane) || this.inputTransientInPane(pane)) {
5308
+ this.footerFallbackPaneKey = null;
5309
+ return false;
5310
+ }
5311
+ const key = pane.replace(/⋆/gu, "").replace(/[ \t]+$/gmu, "");
5312
+ const epoch = `${this.spawnGeneration}:${this.launchAttempt}`;
5313
+ const now = Date.now();
5314
+ if (key !== this.footerFallbackPaneKey || epoch !== this.footerFallbackEpoch) {
5315
+ this.footerFallbackPaneKey = key;
5316
+ this.footerFallbackEpoch = epoch;
5317
+ this.footerFallbackStableSince = now;
5318
+ return false;
5319
+ }
5320
+ if (now - this.footerFallbackStableSince < UNKNOWN_LAYOUT_STABLE_MS)
5267
5321
  return false;
5322
+ if (this.footerFallbackWarnedGeneration !== this.spawnGeneration) {
5323
+ this.footerFallbackWarnedGeneration = this.spawnGeneration;
5324
+ this.logger.warn({ backend: this.backend?.binaryName, stableForMs: now - this.footerFallbackStableSince }, "Idle footer not recognised — delivering on the #978 fallback (stable empty composer, no busy/queued/dialog, raw tty, no transient)");
5268
5325
  }
5326
+ return true;
5269
5327
  }
5270
5328
  /**
5271
5329
  * Why a pane is not deliverable matters. "busy" may be handed to a native
@@ -5275,16 +5333,26 @@ export class Daemon extends EventEmitter {
5275
5333
  * pane proves nothing. The silence gate comes first and costs no capture.
5276
5334
  */
5277
5335
  async paneReadinessForDelivery(windowId) {
5278
- if (!this.isPaneIdleForDelivery(windowId))
5336
+ // Each early return below is the pane seen outside the footer-fallback
5337
+ // candidate state, and never reaches deliveryInputReadyPane: reset here.
5338
+ if (!this.isPaneIdleForDelivery(windowId)) {
5339
+ this.resetFooterFallback();
5279
5340
  return "busy";
5341
+ }
5280
5342
  const probe = await this.probeBlockingDialog();
5281
- if (probe.state !== "clear")
5343
+ if (probe.state !== "clear") {
5344
+ this.resetFooterFallback();
5282
5345
  return probe.state;
5346
+ }
5283
5347
  const transient = await this.probeInputTransient();
5284
- if (transient.state === "active")
5348
+ if (transient.state === "active") {
5349
+ this.resetFooterFallback();
5285
5350
  return "transient";
5286
- if (transient.state === "unknown")
5351
+ }
5352
+ if (transient.state === "unknown") {
5353
+ this.resetFooterFallback();
5287
5354
  return "unknown";
5355
+ }
5288
5356
  if (this.needsStartupInputProof()) {
5289
5357
  return await this.hasPositiveDeliveryInput() ? "ready" : "transient";
5290
5358
  }
@@ -5461,7 +5529,7 @@ export class Daemon extends EventEmitter {
5461
5529
  return "busy";
5462
5530
  if (this.backend?.getBusyPattern?.()?.test(pane))
5463
5531
  return "busy";
5464
- if (this.backend?.isDeliveryInputReadyPane && !this.backend.isDeliveryInputReadyPane(pane))
5532
+ if (!this.deliveryInputReadyPane(pane))
5465
5533
  return "busy";
5466
5534
  if (strandedAgendMessageInInput(pane, prompt))
5467
5535
  return "stranded";
@@ -6501,6 +6569,18 @@ export class Daemon extends EventEmitter {
6501
6569
  }
6502
6570
  return undefined;
6503
6571
  }
6572
+ /** The fleet's answers to forwarded tool calls: settle the pending request they name. */
6573
+ routeFleetResponse(msg) {
6574
+ const type = msg.type;
6575
+ if (!type || !FLEET_RESPONSE_TYPES.has(type) || !msg.fleetRequestId)
6576
+ return;
6577
+ const key = String(msg.fleetRequestId);
6578
+ const handler = this.pendingIpcRequests.get(key);
6579
+ if (!handler)
6580
+ return;
6581
+ this.pendingIpcRequests.delete(key);
6582
+ handler(msg);
6583
+ }
6504
6584
  /**
6505
6585
  * Handle a tool call from the MCP server (forwarded by Claude).
6506
6586
  * Routes to the channel adapter via MessageBus.
@@ -6549,9 +6629,11 @@ export class Daemon extends EventEmitter {
6549
6629
  this.handleReleaseRepo(args, respond);
6550
6630
  return;
6551
6631
  }
6552
- if (tool === "set_display_name" || tool === "set_description") {
6553
- const type = tool === "set_display_name" ? "fleet_set_display_name" : "fleet_set_description";
6554
- const fleetReqId = `${tool === "set_display_name" ? "dn" : "desc"}_${++this.fleetRequestSeq}_${requestId}`;
6632
+ if (tool === "set_display_name" || tool === "set_description" || tool === "list_emojis" || tool === "set_persona_emoji") {
6633
+ const type = tool === "set_display_name" ? "fleet_set_display_name"
6634
+ : tool === "set_description" ? "fleet_set_description"
6635
+ : tool === "list_emojis" ? "fleet_list_emojis" : "fleet_set_persona_emoji";
6636
+ const fleetReqId = `${tool === "set_display_name" ? "dn" : tool === "set_description" ? "desc" : "emoji"}_${++this.fleetRequestSeq}_${requestId}`;
6555
6637
  const timeout = setTimeout(() => {
6556
6638
  this.pendingIpcRequests.delete(fleetReqId);
6557
6639
  respond(null, `${tool} timed out`);