@songsid/agend 2.1.1 → 2.1.2-beta.10

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 (52) hide show
  1. package/dist/backend/antigravity.js +4 -2
  2. package/dist/backend/antigravity.js.map +1 -1
  3. package/dist/backend/codex.d.ts +16 -0
  4. package/dist/backend/codex.js +226 -56
  5. package/dist/backend/codex.js.map +1 -1
  6. package/dist/backend/grok.js +20 -0
  7. package/dist/backend/grok.js.map +1 -1
  8. package/dist/backend/kiro.js +6 -2
  9. package/dist/backend/kiro.js.map +1 -1
  10. package/dist/backend/types.d.ts +7 -0
  11. package/dist/backend/types.js.map +1 -1
  12. package/dist/channel/adapters/discord.js +4 -5
  13. package/dist/channel/adapters/discord.js.map +1 -1
  14. package/dist/channel/adapters/telegram.js +50 -15
  15. package/dist/channel/adapters/telegram.js.map +1 -1
  16. package/dist/channel/ipc-bridge.d.ts +8 -0
  17. package/dist/channel/ipc-bridge.js +41 -14
  18. package/dist/channel/ipc-bridge.js.map +1 -1
  19. package/dist/channel/mcp-server.js +5 -0
  20. package/dist/channel/mcp-server.js.map +1 -1
  21. package/dist/channel/message-queue.d.ts +1 -0
  22. package/dist/channel/message-queue.js +49 -17
  23. package/dist/channel/message-queue.js.map +1 -1
  24. package/dist/cli.js +3 -2
  25. package/dist/cli.js.map +1 -1
  26. package/dist/daemon.d.ts +11 -8
  27. package/dist/daemon.js +56 -16
  28. package/dist/daemon.js.map +1 -1
  29. package/dist/fleet-manager.d.ts +7 -1
  30. package/dist/fleet-manager.js +58 -9
  31. package/dist/fleet-manager.js.map +1 -1
  32. package/dist/instance-lifecycle.d.ts +2 -1
  33. package/dist/instance-lifecycle.js +6 -2
  34. package/dist/instance-lifecycle.js.map +1 -1
  35. package/dist/instructions.d.ts +5 -0
  36. package/dist/instructions.js +9 -11
  37. package/dist/instructions.js.map +1 -1
  38. package/dist/logger.js +14 -0
  39. package/dist/logger.js.map +1 -1
  40. package/dist/outbound-handlers.d.ts +9 -0
  41. package/dist/outbound-handlers.js +46 -26
  42. package/dist/outbound-handlers.js.map +1 -1
  43. package/dist/topic-commands.d.ts +1 -1
  44. package/dist/topic-commands.js +11 -2
  45. package/dist/topic-commands.js.map +1 -1
  46. package/dist/tui-glyphs.d.ts +21 -0
  47. package/dist/tui-glyphs.js +22 -0
  48. package/dist/tui-glyphs.js.map +1 -0
  49. package/dist/view-api.d.ts +3 -0
  50. package/dist/view-api.js +6 -2
  51. package/dist/view-api.js.map +1 -1
  52. package/package.json +1 -1
package/dist/daemon.js CHANGED
@@ -3,6 +3,7 @@ import { mkdirSync, writeFileSync, readFileSync, existsSync, unlinkSync, rmSync,
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { createHash, randomBytes } from "node:crypto";
5
5
  import { EventEmitter } from "node:events";
6
+ import { rotateLogIfNeeded } from "./logger.js";
6
7
  import { clearPausedMarker, writePausedMarker } from "./pause-marker.js";
7
8
  import { TmuxManager, resolveTmuxLogicalSize } from "./tmux-manager.js";
8
9
  import { TranscriptMonitor } from "./transcript-monitor.js";
@@ -244,6 +245,7 @@ export class Daemon extends EventEmitter {
244
245
  topicMode;
245
246
  backend;
246
247
  controlClient;
248
+ runtimeIdentity;
247
249
  logger;
248
250
  tmuxSessionName;
249
251
  tmux = null;
@@ -371,7 +373,7 @@ export class Daemon extends EventEmitter {
371
373
  this.errorRecoveryDeadlineAt = 0;
372
374
  this.activeErrorPatternKey = null;
373
375
  }
374
- constructor(name, config, instanceDir, topicMode = false, backend, controlClient, rootLogger) {
376
+ constructor(name, config, instanceDir, topicMode = false, backend, controlClient, rootLogger, runtimeIdentity) {
375
377
  super();
376
378
  this.name = name;
377
379
  this.config = config;
@@ -379,8 +381,14 @@ export class Daemon extends EventEmitter {
379
381
  this.topicMode = topicMode;
380
382
  this.backend = backend;
381
383
  this.controlClient = controlClient;
384
+ this.runtimeIdentity = runtimeIdentity;
382
385
  if (!rootLogger)
383
386
  throw new Error("Daemon requires a shared root logger");
387
+ this.runtimeIdentity ??= {
388
+ kind: "fleet-topic",
389
+ backend: config.backend ?? backend?.binaryName ?? "unknown",
390
+ model: config.model ?? "default",
391
+ };
384
392
  this.logger = rootLogger.child({ instance: name }, { level: config.log_level });
385
393
  this.tmuxSessionName = getTmuxSession();
386
394
  this.messageBus = new MessageBus();
@@ -612,8 +620,12 @@ export class Daemon extends EventEmitter {
612
620
  catch { /* non-fatal */ }
613
621
  })();
614
622
  if (!this.config.lightweight) {
615
- // 3. Pipe-pane for prompt detection
623
+ // 3. Pipe-pane for prompt detection. Rotate first so a ballooned log from a
624
+ // previous stuck splash (hundreds of MB of ANSI frames) is truncated before
625
+ // we attach — pipe-pane uses `cat >>` on the same inode, so copytruncate
626
+ // keeps the writer attached after size resets.
616
627
  const outputLog = join(this.instanceDir, "output.log");
628
+ rotateLogIfNeeded(outputLog);
617
629
  await this.tmux.pipeOutput(outputLog).catch(() => { });
618
630
  // 4. Transcript monitor
619
631
  this.transcriptMonitor = new TranscriptMonitor(this.instanceDir, this.logger);
@@ -716,6 +728,11 @@ export class Daemon extends EventEmitter {
716
728
  return;
717
729
  }
718
730
  if (paneStatus?.alive) {
731
+ // Instance output.log is fed by tmux pipe-pane and was previously never
732
+ // rotated (only fleet.log / daemon.log were). Cap growth every tick.
733
+ if (!this.config.lightweight) {
734
+ rotateLogIfNeeded(join(this.instanceDir, "output.log"));
735
+ }
719
736
  scheduleNext();
720
737
  return;
721
738
  }
@@ -1755,18 +1772,19 @@ export class Daemon extends EventEmitter {
1755
1772
  }
1756
1773
  /**
1757
1774
  * Deliver a single message and drive its status reactions:
1758
- * ⏳ message_queued — CLI busy; queued, waiting for idle
1775
+ * ⏳ message_queued — CLI busy; queued locally or by the backend
1759
1776
  * 👀 message_delivered — pasted + Enter sent; agent now has it
1760
- * ✅ message_confirmed — idle→busy transition observed; agent is processing
1777
+ * ✅ message_confirmed — processing observed, or native queue handoff succeeded
1761
1778
  * ❌ message_failed — tmux window gone, paste retries exhausted
1762
1779
  * Returns true once the message is in the CLI, false only on real delivery failure.
1763
1780
  *
1764
1781
  * Bug A (silent message loss): paste failures retry with backoff (window recovery)
1765
1782
  * and emit `message_failed` if all attempts fail.
1766
- * Busy handling (UX): we never force-paste into a busy CLI and never give up on a
1767
- * busy one — we show ⏳ and wait for idle indefinitely (a genuinely hung CLI is the
1768
- * hang detector's job; a user who can't wait presses Cancel → Escape → idle). The
1769
- * pasteLock is serial, so later messages naturally queue behind this wait.
1783
+ * Busy handling (UX): backends without a native input queue show and wait for
1784
+ * idle indefinitely (a genuinely hung CLI is the hang detector's job). Backends
1785
+ * that explicitly support queued input receive one complete paste+Enter transaction
1786
+ * immediately and own the application-level queue themselves. The pasteLock remains
1787
+ * serial in both cases so separate PTY writes can never overlap.
1770
1788
  */
1771
1789
  async deliverMessage(formatted, status) {
1772
1790
  // Sanitize unclosed code fences — they cause CLI to wait for closure on Enter
@@ -1776,12 +1794,22 @@ export class Daemon extends EventEmitter {
1776
1794
  formatted = formatted.replace(/```/g, "");
1777
1795
  }
1778
1796
  let windowId = this.getWindowId();
1779
- // If the CLI is busy, show ⏳ and wait for it to go idle — no timeout, no force.
1797
+ const supportsQueuedInput = this.backend?.supportsQueuedInput?.() === true;
1798
+ let handingOffToNativeQueue = false;
1799
+ // If the CLI is busy, either hand the complete submission to its native input
1800
+ // queue or wait for idle. Native queue support is an explicit backend capability:
1801
+ // normal Enter input has steering/interrupt semantics in several other CLIs.
1780
1802
  if (windowId && this.controlClient && !this.controlClient.isIdle(windowId)) {
1781
1803
  if (status)
1782
1804
  this.emit("message_queued", status);
1783
- this.logger.debug("CLI busy — queuing message until idle");
1784
- await this.controlClient.waitUntilIdle(windowId);
1805
+ if (supportsQueuedInput) {
1806
+ handingOffToNativeQueue = true;
1807
+ this.logger.debug("CLI busy — handing message to backend-native input queue");
1808
+ }
1809
+ else {
1810
+ this.logger.debug("CLI busy — queuing message until idle");
1811
+ await this.controlClient.waitUntilIdle(windowId);
1812
+ }
1785
1813
  }
1786
1814
  // Bug A: paste with backoff. Transient failures are usually a stale window id
1787
1815
  // after a crash/respawn — recover by name and retry (max 3 attempts, 2s apart).
@@ -1807,10 +1835,15 @@ export class Daemon extends EventEmitter {
1807
1835
  await this.tmux.sendSpecialKey("Enter");
1808
1836
  if (status)
1809
1837
  this.emit("message_delivered", status); // 👀
1810
- // Confirm the CLI accepted the message by transitioning idle→busy (new output
1811
- // after Enter). If still idle after ~2s the Enter was likely swallowed while
1812
- // the TUI was redrawing re-send Enter once and re-check.
1813
- if (windowId && this.controlClient) {
1838
+ // A busy queue-capable CLI does not necessarily emit output when it accepts
1839
+ // queued input. Treat the successful paste+Enter as the handoff confirmation;
1840
+ // probing for idle→busy would time out and a second bare Enter could mutate
1841
+ // the queue/TUI state. Idle submissions retain the swallowed-Enter safeguard.
1842
+ if (handingOffToNativeQueue) {
1843
+ if (status)
1844
+ this.emit("message_confirmed", status); // ✅ native queue accepted
1845
+ }
1846
+ else if (windowId && this.controlClient) {
1814
1847
  let becameBusy = await this.confirmBusyAfterEnter(windowId, enterAt);
1815
1848
  if (!becameBusy) {
1816
1849
  this.logger.warn("No idle→busy transition after Enter — re-sending Enter once");
@@ -2124,6 +2157,9 @@ export class Daemon extends EventEmitter {
2124
2157
  AGEND_SOCKET_PATH: sockPath,
2125
2158
  AGEND_INSTANCE_NAME: this.name,
2126
2159
  AGEND_WORKING_DIR: this.config.working_directory,
2160
+ AGEND_INSTANCE_KIND: this.runtimeIdentity?.kind ?? "fleet-topic",
2161
+ AGEND_BACKEND: this.runtimeIdentity?.backend ?? this.config.backend ?? this.backend?.binaryName ?? "unknown",
2162
+ AGEND_MODEL: this.runtimeIdentity?.model ?? this.config.model ?? "default",
2127
2163
  };
2128
2164
  if (this.config.tool_set)
2129
2165
  mcpEnv.AGEND_TOOL_SET = this.config.tool_set;
@@ -2159,6 +2195,7 @@ export class Daemon extends EventEmitter {
2159
2195
  instructions = buildFleetInstructions({
2160
2196
  instanceName: this.name,
2161
2197
  workingDirectory: this.config.working_directory,
2198
+ runtimeIdentity: this.runtimeIdentity,
2162
2199
  displayName: this.config.display_name,
2163
2200
  description: this.config.description,
2164
2201
  customPrompt: resolvedCustomPrompt,
@@ -2171,6 +2208,7 @@ export class Daemon extends EventEmitter {
2171
2208
  instructions = buildFleetInstructions({
2172
2209
  instanceName: this.name,
2173
2210
  workingDirectory: this.config.working_directory,
2211
+ runtimeIdentity: this.runtimeIdentity,
2174
2212
  displayName: this.config.display_name,
2175
2213
  description: this.config.description,
2176
2214
  customPrompt: resolvedCustomPrompt,
@@ -2366,7 +2404,9 @@ export class Daemon extends EventEmitter {
2366
2404
  this.logger.warn({ err }, "Failed to set remain-on-exit — exit codes will not be captured");
2367
2405
  });
2368
2406
  if (reuseWindow && !this.config.lightweight) {
2369
- await this.tmux.pipeOutput(join(this.instanceDir, "output.log")).catch(err => {
2407
+ const outputLog = join(this.instanceDir, "output.log");
2408
+ rotateLogIfNeeded(outputLog);
2409
+ await this.tmux.pipeOutput(outputLog).catch(err => {
2370
2410
  this.logger.warn({ err }, "Failed to restore pipe-pane after wake");
2371
2411
  });
2372
2412
  }