@songsid/agend 2.1.1-beta.11 → 2.1.1-beta.13
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/antigravity.js +5 -2
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +3 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +16 -0
- package/dist/backend/codex.js +248 -56
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +20 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.js +18 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +7 -0
- package/dist/backend/types.js +1 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +4 -5
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.js +50 -15
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +8 -0
- package/dist/channel/ipc-bridge.js +41 -14
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/mcp-server.js +25 -10
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +25 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.d.ts +1 -0
- package/dist/channel/message-queue.js +49 -17
- package/dist/channel/message-queue.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/cli.js +71 -26
- 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/daemon.d.ts +74 -8
- package/dist/daemon.js +254 -18
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +7 -1
- package/dist/fleet-manager.js +89 -16
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +22 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +14 -23
- package/dist/general-knowledge/skills/session-management/SKILL.md +15 -34
- package/dist/instance-lifecycle.d.ts +13 -1
- package/dist/instance-lifecycle.js +70 -3
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +5 -0
- package/dist/instructions.js +9 -11
- package/dist/instructions.js.map +1 -1
- package/dist/logger.js +14 -0
- package/dist/logger.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 +9 -0
- package/dist/outbound-handlers.js +46 -26
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/tmux-control.js +4 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +14 -0
- package/dist/tmux-manager.js +20 -6
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +22 -1
- package/dist/topic-commands.js +95 -57
- package/dist/topic-commands.js.map +1 -1
- package/dist/tui-glyphs.d.ts +25 -0
- package/dist/tui-glyphs.js +26 -0
- package/dist/tui-glyphs.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/ui/view.html +114 -2
- package/dist/usage/providers.d.ts +26 -0
- package/dist/usage/providers.js +577 -0
- package/dist/usage/providers.js.map +1 -0
- package/dist/usage/usage-api.d.ts +35 -0
- package/dist/usage/usage-api.js +52 -0
- package/dist/usage/usage-api.js.map +1 -0
- package/dist/view-api.d.ts +3 -0
- package/dist/view-api.js +10 -2
- package/dist/view-api.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -26,7 +26,7 @@ import { processAttachments } from "./channel/attachment-handler.js";
|
|
|
26
26
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
27
27
|
import { Scheduler } from "./scheduler/index.js";
|
|
28
28
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
29
|
-
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, SAVE_FILENAME_RE, SAVE_UNSUPPORTED_MSG } from "./topic-commands.js";
|
|
29
|
+
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, SAVE_FILENAME_RE, SAVE_UNSUPPORTED_MSG, resolveInstanceContext } from "./topic-commands.js";
|
|
30
30
|
import { DailySummary } from "./daily-summary.js";
|
|
31
31
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
32
32
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
@@ -38,6 +38,7 @@ import { StatuslineWatcher } from "./statusline-watcher.js";
|
|
|
38
38
|
import { outboundHandlers } from "./outbound-handlers.js";
|
|
39
39
|
import { handleWebRequest, broadcastSseEvent } from "./web-api.js";
|
|
40
40
|
import { handleViewRequest, isViewPath } from "./view-api.js";
|
|
41
|
+
import { handleUsageRequest, isUsagePath } from "./usage/usage-api.js";
|
|
41
42
|
import { handleSettingsRequest } from "./settings-api.js";
|
|
42
43
|
import { setLocale, detectLocale, t } from "./locale.js";
|
|
43
44
|
import { handleAgentRequest } from "./agent-endpoint.js";
|
|
@@ -163,6 +164,10 @@ export class FleetManager {
|
|
|
163
164
|
failoverActive = new Map(); // instance → current failover model
|
|
164
165
|
// IPC reconnect: tracks instances being intentionally stopped (skip reconnect)
|
|
165
166
|
ipcStoppingInstances = new Set();
|
|
167
|
+
/** Coalesce concurrent connection attempts for the same daemon socket. */
|
|
168
|
+
ipcConnectInFlight = new Map();
|
|
169
|
+
/** At most one reconnect/backoff loop may exist per instance. */
|
|
170
|
+
ipcReconnectInFlight = new Map();
|
|
166
171
|
// Adapter restart: prevents re-entrant restart attempts
|
|
167
172
|
adapterRestarting = new Set();
|
|
168
173
|
// Adapter isolation: track state per adapter for retry + visibility
|
|
@@ -651,7 +656,7 @@ export class FleetManager {
|
|
|
651
656
|
await new Promise(resolve => setTimeout(resolve, 250));
|
|
652
657
|
await this.startClassicInstance(instanceName, this.classicChannels.getBackendByInstance(instanceName, this.fleetConfig?.defaults?.backend), this.classicChannels.getPreTaskCommand(channel.channelId, channel.adapterId), this.classicChannels.getModel(channel.channelId, channel.adapterId, this.fleetConfig?.defaults?.model), this.classicChannels.getAutoPauseAfter(channel.channelId, channel.adapterId, this.fleetConfig?.defaults?.auto_pause_after));
|
|
653
658
|
}
|
|
654
|
-
async startInstance(name, config, topicMode) {
|
|
659
|
+
async startInstance(name, config, topicMode, kind = "fleet-topic") {
|
|
655
660
|
if (this.lifecycle.isPaused(name)) {
|
|
656
661
|
this.logger.info({ name }, "Persisted paused instance — skipping startup");
|
|
657
662
|
return;
|
|
@@ -667,7 +672,11 @@ export class FleetManager {
|
|
|
667
672
|
this.ensureGeneralInstructions(config.working_directory, config.backend);
|
|
668
673
|
}
|
|
669
674
|
this.instanceProcessStatus.delete(name);
|
|
670
|
-
await this.lifecycle.start(name, config, topicMode
|
|
675
|
+
await this.lifecycle.start(name, config, topicMode, {
|
|
676
|
+
kind,
|
|
677
|
+
backend: config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code",
|
|
678
|
+
model: this.resolveInstanceModel(name).display,
|
|
679
|
+
});
|
|
671
680
|
// Auto-connect IPC — daemon.start() ensures socket is ready before resolving
|
|
672
681
|
await this.connectIpcToInstance(name);
|
|
673
682
|
}
|
|
@@ -978,6 +987,11 @@ export class FleetManager {
|
|
|
978
987
|
// Rotate fleet.log daily too (besides the startup size check above), so a
|
|
979
988
|
// long-running fleet doesn't accumulate an unbounded log.
|
|
980
989
|
rotateLogIfNeeded(join(this.dataDir, "fleet.log"));
|
|
990
|
+
// Instance output.log is pipe-pane (TUI ANSI). Daemon health ticks also
|
|
991
|
+
// rotate it; this is the daily safety net for idle/stopped instances.
|
|
992
|
+
for (const name of Object.keys(this.fleetConfig?.instances ?? {})) {
|
|
993
|
+
rotateLogIfNeeded(join(this.dataDir, "instances", name, "output.log"));
|
|
994
|
+
}
|
|
981
995
|
}, () => {
|
|
982
996
|
const instances = Object.keys(this.fleetConfig?.instances ?? {});
|
|
983
997
|
const costMap = new Map();
|
|
@@ -1931,19 +1945,38 @@ export class FleetManager {
|
|
|
1931
1945
|
this.logger.info({ adapterId, type: channelConfig.type }, "Additional adapter started");
|
|
1932
1946
|
}
|
|
1933
1947
|
/** Connect IPC to a single instance with all handlers */
|
|
1934
|
-
|
|
1948
|
+
connectIpcToInstance(name) {
|
|
1949
|
+
const inFlight = this.ipcConnectInFlight.get(name);
|
|
1950
|
+
if (inFlight)
|
|
1951
|
+
return inFlight;
|
|
1952
|
+
const connection = this.connectIpcToInstanceInternal(name)
|
|
1953
|
+
.finally(() => {
|
|
1954
|
+
if (this.ipcConnectInFlight.get(name) === connection) {
|
|
1955
|
+
this.ipcConnectInFlight.delete(name);
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
this.ipcConnectInFlight.set(name, connection);
|
|
1959
|
+
return connection;
|
|
1960
|
+
}
|
|
1961
|
+
async connectIpcToInstanceInternal(name) {
|
|
1935
1962
|
// Close existing client to prevent socket leak on reconnect
|
|
1936
1963
|
const existing = this.instanceIpcClients.get(name);
|
|
1937
1964
|
if (existing) {
|
|
1938
|
-
|
|
1965
|
+
// Remove application listeners before destroying the socket. Even if a
|
|
1966
|
+
// future regression creates two clients, the replaced one cannot keep
|
|
1967
|
+
// handling fleet_outbound messages as an orphan.
|
|
1968
|
+
existing.removeAllListeners();
|
|
1939
1969
|
try {
|
|
1940
|
-
existing.close();
|
|
1970
|
+
await existing.close();
|
|
1941
1971
|
}
|
|
1942
1972
|
catch (err) {
|
|
1943
1973
|
this.logger.debug({ err, name }, "IPC client close failed (likely already closed)");
|
|
1944
1974
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1975
|
+
finally {
|
|
1976
|
+
if (this.instanceIpcClients.get(name) === existing) {
|
|
1977
|
+
this.instanceIpcClients.delete(name);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1947
1980
|
}
|
|
1948
1981
|
const sockPath = join(this.getInstanceDir(name), "channel.sock");
|
|
1949
1982
|
if (!existsSync(sockPath))
|
|
@@ -2021,6 +2054,10 @@ export class FleetManager {
|
|
|
2021
2054
|
}
|
|
2022
2055
|
// Auto-reconnect on disconnect (unless intentionally stopping)
|
|
2023
2056
|
ipc.on("disconnect", () => {
|
|
2057
|
+
// A delayed event from a replaced/stale client must never delete the
|
|
2058
|
+
// current connection or start another reconnect loop.
|
|
2059
|
+
if (this.instanceIpcClients.get(name) !== ipc)
|
|
2060
|
+
return;
|
|
2024
2061
|
this.instanceIpcClients.delete(name);
|
|
2025
2062
|
if (this.ipcStoppingInstances.has(name))
|
|
2026
2063
|
return;
|
|
@@ -2032,7 +2069,20 @@ export class FleetManager {
|
|
|
2032
2069
|
}
|
|
2033
2070
|
}
|
|
2034
2071
|
/** Attempt IPC reconnection with exponential backoff */
|
|
2035
|
-
|
|
2072
|
+
ipcReconnect(name) {
|
|
2073
|
+
const inFlight = this.ipcReconnectInFlight.get(name);
|
|
2074
|
+
if (inFlight)
|
|
2075
|
+
return inFlight;
|
|
2076
|
+
const reconnect = this.runIpcReconnect(name)
|
|
2077
|
+
.finally(() => {
|
|
2078
|
+
if (this.ipcReconnectInFlight.get(name) === reconnect) {
|
|
2079
|
+
this.ipcReconnectInFlight.delete(name);
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
this.ipcReconnectInFlight.set(name, reconnect);
|
|
2083
|
+
return reconnect;
|
|
2084
|
+
}
|
|
2085
|
+
async runIpcReconnect(name) {
|
|
2036
2086
|
for (let attempt = 1;; attempt++) {
|
|
2037
2087
|
if (this.ipcStoppingInstances.has(name) || !this.daemons.has(name))
|
|
2038
2088
|
return;
|
|
@@ -4771,7 +4821,7 @@ When users create specialized instances, suggest these configurations:
|
|
|
4771
4821
|
...(preTaskCommand ? { pre_task_command: preTaskCommand } : {}),
|
|
4772
4822
|
};
|
|
4773
4823
|
const topicMode = this.fleetConfig?.channel?.mode === "topic";
|
|
4774
|
-
await this.startInstance(instanceName, config, topicMode);
|
|
4824
|
+
await this.startInstance(instanceName, config, topicMode, "classic");
|
|
4775
4825
|
}
|
|
4776
4826
|
/** Handle /start slash command — register classic channel */
|
|
4777
4827
|
async handleClassicStart(channelId, channelName, userId, guildId, adapterId, backend) {
|
|
@@ -5372,6 +5422,10 @@ When users create specialized instances, suggest these configurations:
|
|
|
5372
5422
|
// /view routes accept the read-only view.token (or web.token) and do
|
|
5373
5423
|
// their own per-method auth in view-api.ts — skip the web-token gate.
|
|
5374
5424
|
}
|
|
5425
|
+
else if (isUsagePath(new URL(req.url ?? "/", `http://localhost:${port}`).pathname)) {
|
|
5426
|
+
// /api/ai-usage is read-only GET data for the /view Usage panel — open
|
|
5427
|
+
// like the other /view data routes (usage-api.ts rejects non-GET).
|
|
5428
|
+
}
|
|
5375
5429
|
else {
|
|
5376
5430
|
// All other endpoints require a valid token (query ?token= or X-Agend-Token header).
|
|
5377
5431
|
// /ui/* will also re-check in web-api.ts, which is harmless.
|
|
@@ -5400,20 +5454,22 @@ When users create specialized instances, suggest these configurations:
|
|
|
5400
5454
|
if (req.method === "GET" && req.url === "/status") {
|
|
5401
5455
|
const instances = Object.keys(this.fleetConfig?.instances ?? {}).map(name => {
|
|
5402
5456
|
const statusFile = join(this.getInstanceDir(name), "statusline.json");
|
|
5403
|
-
let context_pct = 0;
|
|
5404
5457
|
let cost = 0;
|
|
5405
5458
|
try {
|
|
5406
5459
|
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
5407
|
-
context_pct = data.context_window?.used_percentage ?? 0;
|
|
5408
5460
|
cost = data.cost?.total_cost_usd ?? 0;
|
|
5409
5461
|
}
|
|
5410
5462
|
catch (err) {
|
|
5411
5463
|
this.logger.debug({ err, name }, "statusline.json read failed (/status)");
|
|
5412
5464
|
}
|
|
5465
|
+
const backend = this.fleetConfig?.instances[name]?.backend
|
|
5466
|
+
?? this.fleetConfig?.defaults?.backend
|
|
5467
|
+
?? "claude-code";
|
|
5468
|
+
const { context } = resolveInstanceContext(this.dataDir, name, backend);
|
|
5413
5469
|
return {
|
|
5414
5470
|
name,
|
|
5415
5471
|
status: this.getInstanceStatus(name),
|
|
5416
|
-
context_pct,
|
|
5472
|
+
context_pct: context ?? 0,
|
|
5417
5473
|
cost,
|
|
5418
5474
|
};
|
|
5419
5475
|
});
|
|
@@ -5584,6 +5640,8 @@ When users create specialized instances, suggest these configurations:
|
|
|
5584
5640
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
5585
5641
|
if (handleViewRequest(req, res, url, this))
|
|
5586
5642
|
return;
|
|
5643
|
+
if (handleUsageRequest(req, res, url, this))
|
|
5644
|
+
return;
|
|
5587
5645
|
if (handleSettingsRequest(req, res, url, this))
|
|
5588
5646
|
return;
|
|
5589
5647
|
if (handleWebRequest(req, res, url, this))
|
|
@@ -5630,20 +5688,35 @@ When users create specialized instances, suggest these configurations:
|
|
|
5630
5688
|
this.logger.info({ url: `http://localhost:${port}/view?token=${this.viewToken}` }, "Web View available");
|
|
5631
5689
|
}
|
|
5632
5690
|
getUiStatus() {
|
|
5633
|
-
const
|
|
5691
|
+
const fleetNames = Object.keys(this.fleetConfig?.instances ?? {});
|
|
5692
|
+
// Classic rooms live only in classicBot.yaml — /api/profiles merges them into
|
|
5693
|
+
// the View roster, but previously getUiStatus skipped them so context_pct was
|
|
5694
|
+
// always 0 (live map miss → l?.context_pct ?? 0).
|
|
5695
|
+
const classicOnly = (this.classicChannels?.getAll() ?? [])
|
|
5696
|
+
.map(ch => ch.instanceName)
|
|
5697
|
+
.filter(name => !fleetNames.includes(name));
|
|
5698
|
+
const names = [...fleetNames, ...classicOnly];
|
|
5699
|
+
const instances = names.map(name => {
|
|
5634
5700
|
const statusFile = join(this.getInstanceDir(name), "statusline.json");
|
|
5635
|
-
let context_pct = 0;
|
|
5636
5701
|
let cost = 0;
|
|
5637
5702
|
let model = "";
|
|
5638
5703
|
try {
|
|
5639
5704
|
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
5640
|
-
context_pct = data.context_window?.used_percentage ?? 0;
|
|
5641
5705
|
cost = data.cost?.total_cost_usd ?? 0;
|
|
5642
5706
|
model = data.model?.display_name ?? "";
|
|
5643
5707
|
}
|
|
5644
5708
|
catch (err) {
|
|
5645
5709
|
this.logger.debug({ err, name }, "statusline.json read failed (getUiStatus)");
|
|
5646
5710
|
}
|
|
5711
|
+
// Align with /ctx: statusline for claude-code, pane scrape for kiro/grok/codex.
|
|
5712
|
+
const classic = classicOnly.includes(name);
|
|
5713
|
+
const backend = classic
|
|
5714
|
+
? this.classicChannels.getBackendByInstance(name, this.fleetConfig?.defaults?.backend)
|
|
5715
|
+
: (this.fleetConfig?.instances[name]?.backend
|
|
5716
|
+
?? this.fleetConfig?.defaults?.backend
|
|
5717
|
+
?? "claude-code");
|
|
5718
|
+
const { context } = resolveInstanceContext(this.dataDir, name, backend);
|
|
5719
|
+
const context_pct = context ?? 0;
|
|
5647
5720
|
return { name, status: this.getInstanceStatus(name), context_pct, cost, model };
|
|
5648
5721
|
});
|
|
5649
5722
|
return {
|