@songsid/agend 2.1.2-beta.15 → 2.1.2-beta.17
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/access-path.js +15 -6
- package/dist/access-path.js.map +1 -1
- package/dist/backend/claude-code.d.ts +37 -0
- package/dist/backend/claude-code.js +43 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +22 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.d.ts +23 -0
- package/dist/backend/grok.js +25 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +20 -0
- package/dist/backend/kiro.js +59 -0
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +27 -0
- package/dist/backend/types.js +1 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +15 -0
- package/dist/channel/adapters/discord.js +91 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +21 -0
- package/dist/channel/adapters/telegram.js +82 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +9 -1
- package/dist/channel/ipc-bridge.js +12 -3
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/ipc-timeouts.d.ts +40 -0
- package/dist/channel/ipc-timeouts.js +58 -0
- package/dist/channel/ipc-timeouts.js.map +1 -0
- package/dist/channel/mcp-server.js +25 -14
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +4 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/reconnect-backoff.d.ts +17 -0
- package/dist/channel/reconnect-backoff.js +21 -0
- package/dist/channel/reconnect-backoff.js.map +1 -0
- package/dist/channel/types.d.ts +29 -0
- package/dist/classic-channel-manager.js +1 -5
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +129 -50
- package/dist/cli.js.map +1 -1
- package/dist/completion.d.ts +27 -0
- package/dist/completion.js +121 -0
- package/dist/completion.js.map +1 -0
- package/dist/config-validator.js +9 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/cost-guard.d.ts +3 -1
- package/dist/cost-guard.js +3 -1
- package/dist/cost-guard.js.map +1 -1
- package/dist/daemon.d.ts +164 -5
- package/dist/daemon.js +711 -290
- package/dist/daemon.js.map +1 -1
- package/dist/event-log.d.ts +31 -0
- package/dist/event-log.js +96 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-manager.d.ts +188 -1
- package/dist/fleet-manager.js +713 -61
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +1 -1
- package/dist/hang-detector.d.ts +19 -13
- package/dist/hang-detector.js +19 -49
- package/dist/hang-detector.js.map +1 -1
- package/dist/instance-lifecycle.js +25 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +2 -1
- package/dist/locale.js.map +1 -1
- package/dist/mcp-liveness.d.ts +21 -0
- package/dist/mcp-liveness.js +27 -0
- package/dist/mcp-liveness.js.map +1 -0
- package/dist/outbound-handlers.d.ts +7 -0
- package/dist/outbound-handlers.js +79 -5
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +7 -2
- package/dist/outbound-schemas.js +13 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-write-lock.d.ts +48 -0
- package/dist/pane-write-lock.js +73 -0
- package/dist/pane-write-lock.js.map +1 -0
- package/dist/process-memory.d.ts +31 -0
- package/dist/process-memory.js +79 -0
- package/dist/process-memory.js.map +1 -0
- package/dist/quickstart.js +17 -16
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.js +3 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/sd-notify.d.ts +27 -0
- package/dist/sd-notify.js +33 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/secret-file.d.ts +33 -0
- package/dist/secret-file.js +36 -0
- package/dist/secret-file.js.map +1 -0
- package/dist/setup-wizard.js +9 -7
- package/dist/setup-wizard.js.map +1 -1
- package/dist/tmux-control.d.ts +58 -5
- package/dist/tmux-control.js +102 -14
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +31 -1
- package/dist/tmux-manager.js +40 -11
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +30 -11
- package/dist/topic-commands.js +164 -66
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/ui/view.html +123 -2
- package/dist/usage/format-rich.d.ts +11 -0
- package/dist/usage/format-rich.js +108 -0
- package/dist/usage/format-rich.js.map +1 -0
- package/dist/usage/providers.d.ts +50 -0
- package/dist/usage/providers.js +611 -0
- package/dist/usage/providers.js.map +1 -0
- package/dist/usage/usage-api.d.ts +50 -0
- package/dist/usage/usage-api.js +111 -0
- package/dist/usage/usage-api.js.map +1 -0
- package/dist/view-api.js +1 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.js +5 -2
- package/dist/web-api.js.map +1 -1
- package/package.json +4 -1
- package/dist/channel/tool-tracker.d.ts +0 -13
- package/dist/channel/tool-tracker.js +0 -58
- package/dist/channel/tool-tracker.js.map +0 -1
- package/dist/daemon-entry.d.ts +0 -1
- package/dist/daemon-entry.js +0 -30
- package/dist/daemon-entry.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { getAgendHome, getTmuxSocketName } from "./paths.js";
|
|
|
14
14
|
import { readClassicLastActivityAt } from "./classic-channel-manager.js";
|
|
15
15
|
import { hasPausedMarker } from "./pause-marker.js";
|
|
16
16
|
import { PIE_PERCENT_RE } from "./tui-glyphs.js";
|
|
17
|
+
import { COMPLETION_SHELLS, completionScript } from "./completion.js";
|
|
17
18
|
import { getUpdateSelector, lookupTargetVersion, reportUpdateRestart, shouldSkipUpdate, } from "./update-check.js";
|
|
18
19
|
/** Prefix tmux args with -L when socket isolation is active. */
|
|
19
20
|
function tmuxArgs(args) {
|
|
@@ -68,8 +69,14 @@ fleet
|
|
|
68
69
|
const fleet = loadFleetConfig(FLEET_CONFIG_PATH);
|
|
69
70
|
const port = fleet.health_port ?? 19280;
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
// Any HTTP answer means a fleet daemon is listening — including a 503,
|
|
73
|
+
// which /health now returns when the fleet is degraded (no adapter
|
|
74
|
+
// connected, an instance crashed). Checking `.ok` here would treat a
|
|
75
|
+
// degraded fleet as "not running" and start a SECOND fleet on the same
|
|
76
|
+
// data dir, whose startup cleanup kills the first one's tmux windows.
|
|
77
|
+
// Only a connection error means nothing is there (the catch below).
|
|
78
|
+
await fetch(`http://127.0.0.1:${port}/health`);
|
|
79
|
+
{
|
|
73
80
|
try {
|
|
74
81
|
const resp = await fetch(`http://127.0.0.1:${port}/api/instance/${encodeURIComponent(instance)}/start`, { method: "POST" });
|
|
75
82
|
const body = await resp.json();
|
|
@@ -92,6 +99,59 @@ fleet
|
|
|
92
99
|
}
|
|
93
100
|
const { FleetManager } = await import("./fleet-manager.js");
|
|
94
101
|
const fm = new FleetManager(DATA_DIR);
|
|
102
|
+
// Register crash/signal handlers BEFORE startAll(). Startup routinely takes
|
|
103
|
+
// minutes (sequential general spawn, staggered instances, warmup waits), and
|
|
104
|
+
// without these a Ctrl-C or `systemctl stop` in that window got Node's default
|
|
105
|
+
// behaviour: instant death with fleet.pid already written, databases unclosed,
|
|
106
|
+
// and every tmux window orphaned.
|
|
107
|
+
let stopping = false;
|
|
108
|
+
const shutdown = async () => {
|
|
109
|
+
if (stopping)
|
|
110
|
+
return; // SIGINT and SIGTERM share this, and crash paths call stopAll too
|
|
111
|
+
stopping = true;
|
|
112
|
+
console.log("\nStopping fleet...");
|
|
113
|
+
await fm.stopAll().catch(err => console.error("Shutdown error:", err));
|
|
114
|
+
process.exit(0);
|
|
115
|
+
};
|
|
116
|
+
process.on("SIGINT", shutdown);
|
|
117
|
+
process.on("SIGTERM", shutdown);
|
|
118
|
+
process.on("uncaughtException", (err) => {
|
|
119
|
+
// An uncaught exception leaves unknown state — still the one case worth
|
|
120
|
+
// tearing down for.
|
|
121
|
+
console.error("Uncaught exception:", err);
|
|
122
|
+
if (stopping)
|
|
123
|
+
return;
|
|
124
|
+
stopping = true;
|
|
125
|
+
fm.stopAll().catch(() => { }).finally(() => process.exit(1));
|
|
126
|
+
});
|
|
127
|
+
process.on("unhandledRejection", (err) => {
|
|
128
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
129
|
+
// 409 = another bot poller exists — the adapter retries, don't crash.
|
|
130
|
+
if (msg.includes("409") && msg.includes("getUpdates")) {
|
|
131
|
+
console.error("Bot polling conflict (409) — retrying...");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Do NOT take the fleet down. A rejected promise in one instance's tmux
|
|
135
|
+
// call, health tick, or model switch used to stop every other working
|
|
136
|
+
// instance and exit(1) — the widest blast radius in the process. The
|
|
137
|
+
// per-instance health check and crash recovery handle a genuinely broken
|
|
138
|
+
// instance on their own.
|
|
139
|
+
//
|
|
140
|
+
// Surviving quietly would be its own failure mode, so this reports to the
|
|
141
|
+
// General topic as well as the log: the operator must not have to read
|
|
142
|
+
// daemon.log to learn the fleet swallowed a fault. notifyFleetError()
|
|
143
|
+
// throttles repeats, since these usually arrive from a loop.
|
|
144
|
+
console.error("Unhandled rejection (fleet continues):", err);
|
|
145
|
+
if (err instanceof Error && err.stack)
|
|
146
|
+
console.error(err.stack);
|
|
147
|
+
try {
|
|
148
|
+
fm.notifyFleetError(`⚠️ Fleet caught an unhandled error and kept running:\n\`${msg}\`\nSee daemon.log for the stack trace.`);
|
|
149
|
+
}
|
|
150
|
+
catch (notifyErr) {
|
|
151
|
+
// Never let the reporting path itself become an unhandled rejection.
|
|
152
|
+
console.error("Failed to report unhandled rejection:", notifyErr);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
95
155
|
if (instance) {
|
|
96
156
|
const config = fm.loadConfig(FLEET_CONFIG_PATH);
|
|
97
157
|
const inst = config.instances[instance];
|
|
@@ -109,30 +169,6 @@ fleet
|
|
|
109
169
|
await fm.startAll(FLEET_CONFIG_PATH);
|
|
110
170
|
}
|
|
111
171
|
console.log("Fleet started");
|
|
112
|
-
// Keep process alive + clean shutdown on Ctrl+C
|
|
113
|
-
const shutdown = async () => {
|
|
114
|
-
console.log("\nStopping fleet...");
|
|
115
|
-
await fm.stopAll();
|
|
116
|
-
process.exit(0);
|
|
117
|
-
};
|
|
118
|
-
process.on("SIGINT", shutdown);
|
|
119
|
-
process.on("SIGTERM", shutdown);
|
|
120
|
-
process.on("uncaughtException", async (err) => {
|
|
121
|
-
console.error("Uncaught exception:", err);
|
|
122
|
-
await fm.stopAll().catch(() => { });
|
|
123
|
-
process.exit(1);
|
|
124
|
-
});
|
|
125
|
-
process.on("unhandledRejection", async (err) => {
|
|
126
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
-
// 409 = another bot poller exists — adapter handles retry, don't crash
|
|
128
|
-
if (msg.includes("409") && msg.includes("getUpdates")) {
|
|
129
|
-
console.error("Bot polling conflict (409) — retrying...");
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
console.error("Unhandled rejection:", err);
|
|
133
|
-
await fm.stopAll().catch(() => { });
|
|
134
|
-
process.exit(1);
|
|
135
|
-
});
|
|
136
172
|
});
|
|
137
173
|
fleet
|
|
138
174
|
.command("stop")
|
|
@@ -1835,28 +1871,7 @@ async function fuzzyMatch(query, names) {
|
|
|
1835
1871
|
return null;
|
|
1836
1872
|
}
|
|
1837
1873
|
async function resolveInstance(query, config) {
|
|
1838
|
-
const names =
|
|
1839
|
-
// Include classic instances from classicBot.yaml
|
|
1840
|
-
try {
|
|
1841
|
-
const classicPath = join(DATA_DIR, "classicBot.yaml");
|
|
1842
|
-
if (existsSync(classicPath)) {
|
|
1843
|
-
const yamlMod = (await import("js-yaml")).default;
|
|
1844
|
-
const classic = yamlMod.load(readFileSync(classicPath, "utf-8"));
|
|
1845
|
-
if (classic?.channels) {
|
|
1846
|
-
for (const [key, val] of Object.entries(classic.channels)) {
|
|
1847
|
-
// New format persists instanceName; old format keyed by channelId.
|
|
1848
|
-
if (val.instanceName) {
|
|
1849
|
-
names.push(val.instanceName);
|
|
1850
|
-
continue;
|
|
1851
|
-
}
|
|
1852
|
-
const channelId = val.channelId ?? key;
|
|
1853
|
-
const chName = (val.name ?? channelId).toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "project";
|
|
1854
|
-
names.push(`classic-${chName}-${channelId.slice(-4)}`);
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
catch { /* ignore */ }
|
|
1874
|
+
const names = await listInstanceNames(config);
|
|
1860
1875
|
const match = await fuzzyMatch(query, names);
|
|
1861
1876
|
if (!match) {
|
|
1862
1877
|
console.error(`No instance matching "${query}". Available: ${names.join(", ")}`);
|
|
@@ -1989,10 +2004,48 @@ const defaultParser = (output) => {
|
|
|
1989
2004
|
};
|
|
1990
2005
|
/** Optional backend-specific overrides; none needed — defaultParser covers all. */
|
|
1991
2006
|
const contextParsers = {};
|
|
2007
|
+
/**
|
|
2008
|
+
* Every instance name the CLI accepts: fleet.yaml instances plus the
|
|
2009
|
+
* dynamically-created ClassicBot ones. Shared by `resolveInstance` (fuzzy
|
|
2010
|
+
* matching for `attach`) and `ls --names-only` (shell completion), so the
|
|
2011
|
+
* candidates offered by tab-completion are exactly the ones `attach` resolves.
|
|
2012
|
+
*/
|
|
2013
|
+
async function listInstanceNames(config) {
|
|
2014
|
+
const names = Object.keys(config.instances);
|
|
2015
|
+
try {
|
|
2016
|
+
const classicPath = join(DATA_DIR, "classicBot.yaml");
|
|
2017
|
+
if (existsSync(classicPath)) {
|
|
2018
|
+
const yamlMod = (await import("js-yaml")).default;
|
|
2019
|
+
const classic = yamlMod.load(readFileSync(classicPath, "utf-8"));
|
|
2020
|
+
if (classic?.channels) {
|
|
2021
|
+
for (const [key, val] of Object.entries(classic.channels)) {
|
|
2022
|
+
// New format persists instanceName; old format keyed by channelId.
|
|
2023
|
+
if (val.instanceName) {
|
|
2024
|
+
names.push(val.instanceName);
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
const channelId = val.channelId ?? key;
|
|
2028
|
+
const chName = (val.name ?? channelId).toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "project";
|
|
2029
|
+
names.push(`classic-${chName}-${channelId.slice(-4)}`);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
catch { /* ignore */ }
|
|
2035
|
+
return names;
|
|
2036
|
+
}
|
|
1992
2037
|
async function lsAction(opts) {
|
|
1993
2038
|
const yaml = (await import("js-yaml")).default;
|
|
1994
2039
|
const config = yaml.load(readFileSync(FLEET_CONFIG_PATH, "utf-8"));
|
|
1995
2040
|
const names = Object.keys(config.instances);
|
|
2041
|
+
// --names-only feeds shell completion, so it must return before any tmux or
|
|
2042
|
+
// status work (a tab press should not wait on the fleet) and must print
|
|
2043
|
+
// nothing but names — a "no instances" notice would become a candidate.
|
|
2044
|
+
if (opts.namesOnly) {
|
|
2045
|
+
for (const n of await listInstanceNames(config))
|
|
2046
|
+
console.log(n);
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
1996
2049
|
// Load classic channels from classicBot.yaml (keyed by channelId)
|
|
1997
2050
|
const classicPath = join(DATA_DIR, "classicBot.yaml");
|
|
1998
2051
|
let classicConfig = null;
|
|
@@ -2278,12 +2331,38 @@ program
|
|
|
2278
2331
|
.command("ls")
|
|
2279
2332
|
.description("List all instances with status, backend, team, and last activity")
|
|
2280
2333
|
.option("--json", "Output as JSON")
|
|
2334
|
+
.option("--names-only", "Print instance names one per line (for shell completion)")
|
|
2281
2335
|
.action(async (opts) => {
|
|
2282
2336
|
await lsAction(opts);
|
|
2283
2337
|
});
|
|
2284
2338
|
program
|
|
2285
|
-
.command("
|
|
2286
|
-
.description("
|
|
2339
|
+
.command("completion")
|
|
2340
|
+
.description("Print a shell completion script (bash or zsh)")
|
|
2341
|
+
.argument("<shell>", `Shell to generate for (${COMPLETION_SHELLS.join(", ")})`)
|
|
2342
|
+
.addHelpText("after", `
|
|
2343
|
+
Install:
|
|
2344
|
+
bash echo 'eval "$(agend completion bash)"' >> ~/.bashrc
|
|
2345
|
+
zsh echo 'eval "$(agend completion zsh)"' >> ~/.zshrc
|
|
2346
|
+
|
|
2347
|
+
Then reload the shell. Tab-completes instance names for \`agend attach\` and
|
|
2348
|
+
\`agend fleet start|stop|restart\`, and subcommands elsewhere.`)
|
|
2349
|
+
.action((shell) => {
|
|
2350
|
+
if (!COMPLETION_SHELLS.includes(shell)) {
|
|
2351
|
+
console.error(`Unsupported shell: ${shell}. Supported: ${COMPLETION_SHELLS.join(", ")}`);
|
|
2352
|
+
process.exit(2);
|
|
2353
|
+
}
|
|
2354
|
+
// Subcommand names come from commander itself, so the script can't drift
|
|
2355
|
+
// out of sync with the CLI the way a hardcoded list would.
|
|
2356
|
+
const fleetCmd = program.commands.find(c => c.name() === "fleet");
|
|
2357
|
+
console.log(completionScript(shell, {
|
|
2358
|
+
topLevel: program.commands.map(c => c.name()),
|
|
2359
|
+
fleetSub: fleetCmd?.commands.map(c => c.name()) ?? [],
|
|
2360
|
+
instanceCommands: ["attach"],
|
|
2361
|
+
fleetInstanceCommands: ["start", "stop", "restart"],
|
|
2362
|
+
}));
|
|
2363
|
+
});
|
|
2364
|
+
program
|
|
2365
|
+
.command("health").description("Fleet health check — shows problems and diagnostics")
|
|
2287
2366
|
.option("--json", "Output as JSON")
|
|
2288
2367
|
.option("-q, --quiet", "One-line summary only")
|
|
2289
2368
|
.action(async (opts) => {
|