@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.
- package/dist/backend/claude-code.d.ts +10 -0
- package/dist/backend/claude-code.js +56 -3
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +1 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.js +2 -2
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/grok.js +2 -2
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +1 -0
- package/dist/backend/kiro.js +10 -3
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode.js +2 -2
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +5 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +2 -0
- package/dist/channel/adapters/discord.js +6 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +10 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/daemon.d.ts +11 -0
- package/dist/daemon.js +130 -28
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +4 -0
- package/dist/fleet-manager.js +100 -7
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +17 -0
- package/dist/instance-lifecycle.js +71 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +6 -0
- package/dist/locale.js.map +1 -1
- package/dist/topic-commands.js +5 -1
- package/dist/topic-commands.js.map +1 -1
- package/dist/usage/providers.d.ts +7 -0
- package/dist/usage/providers.js +18 -1
- package/dist/usage/providers.js.map +1 -1
- 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
|
-
//
|
|
1846
|
-
//
|
|
1847
|
-
//
|
|
1848
|
-
if (
|
|
1849
|
-
this.
|
|
1850
|
-
|
|
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
|
-
|
|
1856
|
-
if
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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
|
-
|
|
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
|
|
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();
|