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

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 (41) hide show
  1. package/dist/backend/claude-code.d.ts +10 -0
  2. package/dist/backend/claude-code.js +56 -3
  3. package/dist/backend/claude-code.js.map +1 -1
  4. package/dist/backend/codex.js +1 -1
  5. package/dist/backend/codex.js.map +1 -1
  6. package/dist/backend/gemini-cli.js +2 -2
  7. package/dist/backend/gemini-cli.js.map +1 -1
  8. package/dist/backend/grok.js +2 -2
  9. package/dist/backend/grok.js.map +1 -1
  10. package/dist/backend/kiro.d.ts +1 -0
  11. package/dist/backend/kiro.js +10 -3
  12. package/dist/backend/kiro.js.map +1 -1
  13. package/dist/backend/opencode.js +2 -2
  14. package/dist/backend/opencode.js.map +1 -1
  15. package/dist/backend/types.d.ts +14 -0
  16. package/dist/backend/types.js +5 -1
  17. package/dist/backend/types.js.map +1 -1
  18. package/dist/channel/adapters/discord.d.ts +2 -0
  19. package/dist/channel/adapters/discord.js +6 -0
  20. package/dist/channel/adapters/discord.js.map +1 -1
  21. package/dist/channel/adapters/telegram.d.ts +6 -0
  22. package/dist/channel/adapters/telegram.js +10 -0
  23. package/dist/channel/adapters/telegram.js.map +1 -1
  24. package/dist/channel/types.d.ts +2 -0
  25. package/dist/daemon.d.ts +11 -0
  26. package/dist/daemon.js +130 -28
  27. package/dist/daemon.js.map +1 -1
  28. package/dist/fleet-manager.d.ts +4 -0
  29. package/dist/fleet-manager.js +100 -7
  30. package/dist/fleet-manager.js.map +1 -1
  31. package/dist/instance-lifecycle.d.ts +17 -0
  32. package/dist/instance-lifecycle.js +71 -0
  33. package/dist/instance-lifecycle.js.map +1 -1
  34. package/dist/locale.js +6 -0
  35. package/dist/locale.js.map +1 -1
  36. package/dist/topic-commands.js +5 -1
  37. package/dist/topic-commands.js.map +1 -1
  38. package/dist/usage/providers.d.ts +7 -0
  39. package/dist/usage/providers.js +18 -1
  40. package/dist/usage/providers.js.map +1 -1
  41. package/package.json +1 -1
package/dist/daemon.js CHANGED
@@ -776,6 +776,8 @@ export class Daemon extends EventEmitter {
776
776
  authFailureUnresolved = false;
777
777
  /** One login-screen auth report per spawn — the screen persists across polls. */
778
778
  loginScreenReported = false;
779
+ /** model_error seen mid-turn: notify only if it survives to the idle screen. */
780
+ pendingModelErrorKey = null;
779
781
  /**
780
782
  * Let the next pause() proceed from a stuck pane. Set only for the auth-deferred
781
783
  * pause — see the pausePending consumption site for why waiting for idle there
@@ -1035,7 +1037,7 @@ export class Daemon extends EventEmitter {
1035
1037
  }
1036
1038
  else if (msg.type === "raw_paste") {
1037
1039
  // Paste raw text directly to CLI without [user:] wrapping.
1038
- this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch));
1040
+ this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch), msg.confirm_clear === true);
1039
1041
  }
1040
1042
  else if (msg.type === "config_update") {
1041
1043
  this.applyConfigUpdate(msg.config);
@@ -1815,6 +1817,19 @@ export class Daemon extends EventEmitter {
1815
1817
  this.logger.warn({ errorType: this.lastDetectedErrorType }, "PTY error recovery deadline expired — resuming error scan");
1816
1818
  this.clearErrorRecoveryGate();
1817
1819
  }
1820
+ // A model_error deferred mid-turn: the turn is over (or the pane left
1821
+ // "working"), so decide now — still on screen means the CLI gave up and
1822
+ // the user must act; gone means a retry got past it and nobody needs to know.
1823
+ if (this.pendingModelErrorKey && this.instanceState !== "working") {
1824
+ const key = this.pendingModelErrorKey;
1825
+ this.pendingModelErrorKey = null;
1826
+ const ep = patterns.find(candidate => Daemon.errorPatternKey(candidate) === key);
1827
+ if (ep && ep.pattern.test(pane)) {
1828
+ this.emitErrorPattern(ep, key, pane, now);
1829
+ return;
1830
+ }
1831
+ this.logger.debug({ errorKey: key }, "Deferred model_error cleared before idle — dropped");
1832
+ }
1818
1833
  // State: monitoring — count-based new-error detection over the full pane.
1819
1834
  for (const ep of patterns) {
1820
1835
  const key = Daemon.errorPatternKey(ep);
@@ -1842,30 +1857,49 @@ export class Daemon extends EventEmitter {
1842
1857
  continue;
1843
1858
  }
1844
1859
  this.lastErrorCount.set(key, count);
1845
- // skipRecoveryWait: this error self-recovers (e.g. a timeout — Kiro is
1846
- // back at its prompt immediately). Keep monitoring so the next occurrence
1847
- // can fire without waiting for a possibly startup-only ready pattern.
1848
- if (!ep.skipRecoveryWait) {
1849
- this.errorWaitingForRecovery = true;
1850
- this.errorDetectedAt = now;
1851
- this.errorRecoveryDeadlineAt = now + Daemon.ERROR_RECOVERY_TIMEOUT_MS;
1852
- this.activeErrorPatternKey = key;
1853
- this.lastDetectedErrorType = ep.type;
1860
+ // Kiro's `auto` model picks a concrete model per turn and reroutes around
1861
+ // a temporarily unavailable one on its own — telling the user to run
1862
+ // /model would ask them to fix something the CLI is already handling.
1863
+ if (ep.type === "model_error" && ep.action === "notify" && this.config.model === "auto") {
1864
+ this.logger.debug({ errorType: ep.type }, "model_error suppressed — model 'auto' reroutes on its own");
1865
+ continue;
1854
1866
  }
1855
- this.lastErrorNotifiedAt.set(key, now);
1856
- if (ep.action === "failover")
1857
- this.lastFailoverAt = now;
1858
- const message = this.resolveErrorMessage(pane, ep);
1859
- // Remember an auth failure for as long as it is unresolved. An auth-broken
1860
- // CLI cannot finish the turn it is holding, so every other detector in this
1861
- // scan would otherwise keep reporting the same dead instance forever.
1862
- if (ep.type === "auth_error")
1863
- this.authFailureUnresolved = true;
1864
- this.logger.warn({ errorType: ep.type, action: ep.action }, `PTY error detected: ${message}`);
1865
- this.emit("pty_error", { name: this.name, ...ep, message });
1867
+ // A mid-turn model error is often a transient the CLI retries past. Hold
1868
+ // the notification until the turn ends and notify only if the error is
1869
+ // still on the idle screen (see the deferred check above).
1870
+ if (ep.type === "model_error" && ep.action === "notify" && this.instanceState === "working") {
1871
+ this.pendingModelErrorKey = key;
1872
+ this.logger.debug({ errorType: ep.type }, "model_error deferred — instance is mid-turn, re-checking at idle");
1873
+ continue;
1874
+ }
1875
+ this.emitErrorPattern(ep, key, pane, now);
1866
1876
  break; // Only handle first unsuppressed new error per scan
1867
1877
  }
1868
1878
  }
1879
+ /** Arm the recovery gates and emit one detected error pattern. */
1880
+ emitErrorPattern(ep, key, pane, now) {
1881
+ // skipRecoveryWait: this error self-recovers (e.g. a timeout — Kiro is
1882
+ // back at its prompt immediately). Keep monitoring so the next occurrence
1883
+ // can fire without waiting for a possibly startup-only ready pattern.
1884
+ if (!ep.skipRecoveryWait) {
1885
+ this.errorWaitingForRecovery = true;
1886
+ this.errorDetectedAt = now;
1887
+ this.errorRecoveryDeadlineAt = now + Daemon.ERROR_RECOVERY_TIMEOUT_MS;
1888
+ this.activeErrorPatternKey = key;
1889
+ this.lastDetectedErrorType = ep.type;
1890
+ }
1891
+ this.lastErrorNotifiedAt.set(key, now);
1892
+ if (ep.action === "failover")
1893
+ this.lastFailoverAt = now;
1894
+ const message = this.resolveErrorMessage(pane, ep);
1895
+ // Remember an auth failure for as long as it is unresolved. An auth-broken
1896
+ // CLI cannot finish the turn it is holding, so every other detector in this
1897
+ // scan would otherwise keep reporting the same dead instance forever.
1898
+ if (ep.type === "auth_error")
1899
+ this.authFailureUnresolved = true;
1900
+ this.logger.warn({ errorType: ep.type, action: ep.action }, `PTY error detected: ${message}`);
1901
+ this.emit("pty_error", { name: this.name, ...ep, message });
1902
+ }
1869
1903
  /**
1870
1904
  * Notification text for a detected pattern. `formatMessage` patterns build it
1871
1905
  * from the match so the user gets the specifics (e.g. which period and what
@@ -1927,7 +1961,7 @@ export class Daemon extends EventEmitter {
1927
1961
  isDeliveryEpochCurrent(epoch) {
1928
1962
  return epoch === this.deliveryEpoch;
1929
1963
  }
1930
- queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch) {
1964
+ queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch, confirmClear = false) {
1931
1965
  if (!this.tmux || !this.isDeliveryEpochCurrent(deliveryEpoch))
1932
1966
  return;
1933
1967
  this.pasteLock = this.pasteLock.then(async () => {
@@ -1939,11 +1973,62 @@ export class Daemon extends EventEmitter {
1939
1973
  if (!this.isDeliveryEpochCurrent(deliveryEpoch))
1940
1974
  return;
1941
1975
  await this.deliverMessage(rawText, undefined, { deliveryEpoch });
1976
+ if (confirmClear)
1977
+ await this.confirmBackendClearDialog();
1942
1978
  this.logger.debug({ text: rawText.slice(0, 100) }, "Raw paste delivered");
1943
1979
  }).catch(err => {
1944
1980
  this.logger.warn({ err: err.message }, "raw_paste delivery error");
1945
1981
  });
1946
1982
  }
1983
+ /**
1984
+ * Confirm a backend's terminal-level /clear prompt after the channel-level
1985
+ * admin confirmation has already succeeded. Poll instead of pasting `/clear\ny`
1986
+ * as one buffer: Kiro versions that clear immediately interpret that queued
1987
+ * `y` as a brand-new user message.
1988
+ */
1989
+ async confirmBackendClearDialog() {
1990
+ const dialog = this.backend?.getClearConfirmationDialog?.();
1991
+ if (!dialog || !this.tmux)
1992
+ return;
1993
+ const matches = (pane) => {
1994
+ dialog.pattern.lastIndex = 0;
1995
+ return dialog.pattern.test(pane);
1996
+ };
1997
+ const deadline = Date.now() + 3_000;
1998
+ while (Date.now() <= deadline) {
1999
+ const pane = await this.tmux.capturePane().catch(() => "");
2000
+ if (matches(pane)) {
2001
+ const sent = await this.paneWriteLock.run(async () => {
2002
+ // Re-check under the write lock so a prompt that disappeared between
2003
+ // capture and lock acquisition never receives a stray literal `y`.
2004
+ const currentPane = await this.tmux.capturePane().catch(() => "");
2005
+ if (!matches(currentPane))
2006
+ return false;
2007
+ const specialKeys = new Set(["Up", "Down", "Enter", "Escape", "Right", "Left"]);
2008
+ for (const key of dialog.keys) {
2009
+ const ok = specialKeys.has(key)
2010
+ ? await this.tmux.sendSpecialKey(key)
2011
+ : await this.tmux.sendKeys(key);
2012
+ if (!ok)
2013
+ return false;
2014
+ await new Promise(resolve => setTimeout(resolve, 100));
2015
+ }
2016
+ return true;
2017
+ });
2018
+ if (sent) {
2019
+ this.logger.info({ dialog: dialog.description }, "Auto-confirmed backend clear dialog");
2020
+ }
2021
+ else {
2022
+ this.logger.warn({ dialog: dialog.description }, "Backend clear dialog disappeared or confirmation keys failed");
2023
+ }
2024
+ return;
2025
+ }
2026
+ await new Promise(resolve => setTimeout(resolve, 100));
2027
+ }
2028
+ // Recent Kiro releases may clear immediately without a second prompt. That
2029
+ // is a successful no-op here, not an error.
2030
+ this.logger.debug({ dialog: dialog.description }, "Backend clear dialog did not appear");
2031
+ }
1947
2032
  /** Send the backend-specific graceful quit command/key sequence. */
1948
2033
  async sendQuitSequence() {
1949
2034
  if (!this.tmux || !this.backend)
@@ -1951,18 +2036,20 @@ export class Daemon extends EventEmitter {
1951
2036
  const quitCmd = this.backend.getQuitCommand();
1952
2037
  const quitKey = this.backend.getQuitKey?.();
1953
2038
  if (quitCmd) {
1954
- await this.tmux.sendKeys(quitCmd);
2039
+ if (!await this.tmux.sendKeys(quitCmd))
2040
+ return false;
1955
2041
  // Delay before Enter to prevent tmux server races when instances stop in
1956
2042
  // parallel (same pattern as pasteText).
1957
2043
  await new Promise(r => setTimeout(r, 150));
1958
- await this.tmux.sendSpecialKey("Enter");
1959
- return true;
2044
+ return this.tmux.sendSpecialKey("Enter");
1960
2045
  }
1961
2046
  if (!quitKey)
1962
2047
  return false;
1963
2048
  const presses = Math.max(1, Math.floor(this.backend.getQuitKeyPresses?.() ?? 1));
1964
2049
  for (let i = 0; i < presses; i++) {
1965
- await this.tmux.sendSpecialKey(quitKey);
2050
+ const sent = await this.tmux.sendSpecialKey(quitKey);
2051
+ if (!sent)
2052
+ return false;
1966
2053
  if (i + 1 < presses)
1967
2054
  await new Promise(r => setTimeout(r, 250));
1968
2055
  }
@@ -1995,8 +2082,23 @@ export class Daemon extends EventEmitter {
1995
2082
  }
1996
2083
  }
1997
2084
  }
1998
- if (!killed)
1999
- this.logger.warn("CLI did not exit gracefully within 3s, force killing window");
2085
+ if (!killed) {
2086
+ this.logger.warn({ quitSent }, "CLI did not exit gracefully within 3s — falling back to SIGTERM");
2087
+ await this.killProcessTree("SIGTERM");
2088
+ for (let i = 0; i < 5; i++) {
2089
+ await new Promise(r => setTimeout(r, 200));
2090
+ const status = await this.tmux.getPaneStatus();
2091
+ if (!status || !status.alive) {
2092
+ killed = true;
2093
+ break;
2094
+ }
2095
+ }
2096
+ }
2097
+ if (!killed) {
2098
+ this.logger.warn("CLI process tree survived SIGTERM — falling back to SIGKILL");
2099
+ await this.killProcessTree("SIGKILL");
2100
+ await new Promise(r => setTimeout(r, 200));
2101
+ }
2000
2102
  // Always kill window — remain-on-exit keeps dead panes around after CLI exits
2001
2103
  const stoppedWindowId = this.tmux.getWindowId();
2002
2104
  await this.tmux.killWindow();