@songsid/agend 2.1.3-beta.6 → 2.1.3-beta.8

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.
package/dist/daemon.js CHANGED
@@ -614,12 +614,8 @@ export class Daemon extends EventEmitter {
614
614
  lastAdapterId;
615
615
  // Pending ack: react 🫡 on first transcript activity after receiving a message
616
616
  pendingAckMessage = null;
617
- // Tool status tracking for channel adapter
618
617
  /** Last activity published to the fleet manager; null when nothing is running. */
619
618
  currentActivity = null;
620
- toolStatusMessageId = null;
621
- toolStatusLines = [];
622
- toolStatusDebounce = null;
623
619
  // Session identity: map IPC socket → sessionName (from mcp_ready)
624
620
  socketSessionNames = new Map();
625
621
  // Crash recovery
@@ -940,10 +936,6 @@ export class Daemon extends EventEmitter {
940
936
  this.logger.error({ err: err.message }, "Wake failed for scheduled delivery");
941
937
  });
942
938
  }
943
- else if (msg.type === "fleet_tool_status_ack") {
944
- // Fleet manager sent us the messageId for our tool status message
945
- this.toolStatusMessageId = msg.messageId;
946
- }
947
939
  else if (msg.type === "query_instance_state") {
948
940
  const snapshot = this.getInstanceStateSnapshot();
949
941
  this.ipcServer?.send(socket, {
@@ -1779,10 +1771,6 @@ export class Daemon extends EventEmitter {
1779
1771
  async stop() {
1780
1772
  this.logger.info("Stopping daemon instance");
1781
1773
  this.freezeRuntimeMonitors();
1782
- if (this.toolStatusDebounce) {
1783
- clearTimeout(this.toolStatusDebounce);
1784
- this.toolStatusDebounce = null;
1785
- }
1786
1774
  this.pendingIpcRequests.clear();
1787
1775
  if (this.adapter)
1788
1776
  await this.adapter.stop();
@@ -2440,45 +2428,6 @@ export class Daemon extends EventEmitter {
2440
2428
  return ""; // skip channel tools
2441
2429
  return name;
2442
2430
  }
2443
- addToolStatus(name, input, state) {
2444
- const summary = this.summarizeTool(name, input);
2445
- if (!summary)
2446
- return; // skip empty (e.g., channel tools)
2447
- if (state === "running") {
2448
- this.toolStatusLines.push(`⏳ ${summary}`);
2449
- }
2450
- else {
2451
- // Mark the last matching tool as done
2452
- for (let i = this.toolStatusLines.length - 1; i >= 0; i--) {
2453
- if (this.toolStatusLines[i].includes(name) && this.toolStatusLines[i].startsWith("⏳")) {
2454
- this.toolStatusLines[i] = this.toolStatusLines[i].replace("⏳", "✅");
2455
- break;
2456
- }
2457
- }
2458
- }
2459
- this.debouncedSendToolStatus();
2460
- }
2461
- /** Debounce tool status updates to avoid channel rate limits */
2462
- debouncedSendToolStatus() {
2463
- if (this.toolStatusDebounce)
2464
- clearTimeout(this.toolStatusDebounce);
2465
- this.toolStatusDebounce = setTimeout(() => this.sendToolStatus(), 500);
2466
- }
2467
- sendToolStatus() {
2468
- const text = this.toolStatusLines.join("\n");
2469
- if (!text)
2470
- return;
2471
- this.ipcServer?.broadcast({
2472
- type: "fleet_tool_status",
2473
- instanceName: this.name,
2474
- text,
2475
- editMessageId: this.toolStatusMessageId,
2476
- });
2477
- }
2478
- /** Called by fleet manager when tool status message is sent (returns messageId) */
2479
- setToolStatusMessageId(messageId) {
2480
- this.toolStatusMessageId = messageId;
2481
- }
2482
2431
  /**
2483
2432
  * Push an inbound channel message to a specific MCP session.
2484
2433
  * If targetSession is provided, only send to the matching socket.
@@ -2656,6 +2605,16 @@ export class Daemon extends EventEmitter {
2656
2605
  * precisely so the lock's scope is visible at the call site rather than being
2657
2606
  * an invariant maintained by comments.
2658
2607
  */
2608
+ async sendDeliveryEnter(phase) {
2609
+ const sent = await this.tmux.sendSpecialKey("Enter");
2610
+ if (!sent) {
2611
+ this.logger.error({
2612
+ phase,
2613
+ tmuxError: this.tmux.getLastSendSpecialKeyError() ?? "unknown tmux send-keys failure",
2614
+ }, "tmux send-keys Enter failed during message delivery");
2615
+ }
2616
+ return sent;
2617
+ }
2659
2618
  async writeMessageToPane(formatted, initialWindowId, handingOffToNativeQueue, status) {
2660
2619
  let windowId = initialWindowId;
2661
2620
  // Bug A: paste with backoff. Transient failures are usually a stale window id
@@ -2693,7 +2652,11 @@ export class Daemon extends EventEmitter {
2693
2652
  settle = { settleMs: fallbackMs, observedPostPasteOutput: false, capHit: false, usedFallback: true };
2694
2653
  }
2695
2654
  let enterAt = Date.now();
2696
- await this.tmux.sendSpecialKey("Enter");
2655
+ if (!(await this.sendDeliveryEnter("initial-submit"))) {
2656
+ if (status)
2657
+ this.emit("message_failed", status); // ❌
2658
+ return false;
2659
+ }
2697
2660
  // Kiro's legacy TUI can swallow Enter while it is still processing a large
2698
2661
  // paste — not only during the post-ready redraw (#479): on slower hosts it
2699
2662
  // happens on ordinary deliveries too (v2.1.2 stable, WSL2 + tmux 3.4).
@@ -2709,10 +2672,12 @@ export class Daemon extends EventEmitter {
2709
2672
  let enterRetry = false;
2710
2673
  if (this.backend?.requiresDeliveryEnterRetry?.() === true) {
2711
2674
  await new Promise(r => setTimeout(r, 1_000));
2712
- enterAt = Date.now();
2713
- await this.tmux.sendSpecialKey("Enter");
2714
- enterRetry = true;
2715
- this.logger.debug("Sent defensive Enter retry (queue-less TUI can swallow the first)");
2675
+ const retryAt = Date.now();
2676
+ if (await this.sendDeliveryEnter("queue-less-defensive-retry")) {
2677
+ enterAt = retryAt;
2678
+ enterRetry = true;
2679
+ this.logger.debug("Sent defensive Enter retry (queue-less TUI can swallow the first)");
2680
+ }
2716
2681
  }
2717
2682
  this.logger.debug({
2718
2683
  observedPostPasteOutput: settle.observedPostPasteOutput,
@@ -2748,13 +2713,21 @@ export class Daemon extends EventEmitter {
2748
2713
  }
2749
2714
  await new Promise(r => setTimeout(r, NORMAL_ENTER_SETTLE_MS));
2750
2715
  const retryAt = Date.now();
2751
- await this.tmux.sendSpecialKey("Enter");
2716
+ if (!(await this.sendDeliveryEnter("native-queue-idle-redelivery"))) {
2717
+ if (status)
2718
+ this.emit("message_failed", status); // ❌
2719
+ return false;
2720
+ }
2752
2721
  if (windowId && this.controlClient) {
2753
2722
  let becameBusy = await this.confirmBusyAfterEnter(windowId, retryAt);
2754
2723
  if (!becameBusy) {
2755
2724
  this.logger.warn("No idle→busy after idle-gated redelivery — re-sending Enter once");
2756
2725
  const retry2At = Date.now();
2757
- await this.tmux.sendSpecialKey("Enter");
2726
+ if (!(await this.sendDeliveryEnter("native-queue-idle-redelivery-retry"))) {
2727
+ if (status)
2728
+ this.emit("message_failed", status); // ❌
2729
+ return false;
2730
+ }
2758
2731
  becameBusy = await this.confirmBusyAfterEnter(windowId, retry2At);
2759
2732
  }
2760
2733
  if (becameBusy) {
@@ -2778,7 +2751,11 @@ export class Daemon extends EventEmitter {
2778
2751
  if (!becameBusy) {
2779
2752
  this.logger.warn("No idle→busy transition after Enter — re-sending Enter once");
2780
2753
  const retryAt = Date.now();
2781
- await this.tmux.sendSpecialKey("Enter");
2754
+ if (!(await this.sendDeliveryEnter("idle-to-busy-retry"))) {
2755
+ if (status)
2756
+ this.emit("message_failed", status); // ❌
2757
+ return false;
2758
+ }
2782
2759
  becameBusy = await this.confirmBusyAfterEnter(windowId, retryAt);
2783
2760
  }
2784
2761
  if (becameBusy) {
@@ -2799,7 +2776,7 @@ export class Daemon extends EventEmitter {
2799
2776
  else {
2800
2777
  // No control client to observe output: fall back to the legacy double-Enter.
2801
2778
  await new Promise(r => setTimeout(r, 1000));
2802
- await this.tmux.sendSpecialKey("Enter");
2779
+ await this.sendDeliveryEnter("unobserved-defensive-retry");
2803
2780
  if (status)
2804
2781
  this.emit("message_confirmed", status); // ✅ (best-effort)
2805
2782
  }
@@ -3367,8 +3344,6 @@ export class Daemon extends EventEmitter {
3367
3344
  this.beginSpawn();
3368
3345
  let resumedSuccessfully = false;
3369
3346
  try {
3370
- this.toolStatusLines = [];
3371
- this.toolStatusMessageId = null;
3372
3347
  if (!this.backend) {
3373
3348
  throw new Error("No backend configured — cannot spawn CLI window");
3374
3349
  }