@songsid/agend 2.1.4-beta.52 → 2.1.4-beta.53
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/cli.js +2 -40
- package/dist/cli.js.map +1 -1
- package/dist/fleet-context.d.ts +14 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-manager.js +116 -70
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +10 -0
- package/dist/locale.js.map +1 -1
- package/dist/process-memory.d.ts +6 -0
- package/dist/process-memory.js +45 -0
- package/dist/process-memory.js.map +1 -1
- package/dist/topic-commands.d.ts +6 -2
- package/dist/topic-commands.js +39 -7
- package/dist/topic-commands.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync } from "node:fs";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { freemem } from "node:os";
|
|
4
|
+
import { freemem, totalmem } from "node:os";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { join, dirname, basename } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -567,7 +567,8 @@ export class FleetManager {
|
|
|
567
567
|
getSysInfo() {
|
|
568
568
|
const mem = process.memoryUsage();
|
|
569
569
|
const toMB = (b) => Math.round(b / 1024 / 1024 * 10) / 10;
|
|
570
|
-
|
|
570
|
+
// Fleet instances (fleet.yaml)
|
|
571
|
+
const fleetInstances = Object.keys(this.fleetConfig?.instances ?? {}).map(name => ({
|
|
571
572
|
name,
|
|
572
573
|
status: this.getInstanceStatus(name),
|
|
573
574
|
state: this.getInstanceExecutionState(name),
|
|
@@ -575,12 +576,46 @@ export class FleetManager {
|
|
|
575
576
|
costCents: this.costGuard?.getDailyCostCents(name) ?? 0,
|
|
576
577
|
rateLimits: this.statuslineWatcher.getRateLimits(name) ?? null,
|
|
577
578
|
}));
|
|
579
|
+
// Classic instances (classicBot.yaml) — dedupe against fleet
|
|
580
|
+
const fleetNames = new Set(fleetInstances.map(i => i.name));
|
|
581
|
+
const classicInstances = (this.classicChannels?.getAll() ?? [])
|
|
582
|
+
.filter(ch => !fleetNames.has(ch.instanceName))
|
|
583
|
+
.map(ch => ({
|
|
584
|
+
name: ch.instanceName,
|
|
585
|
+
status: this.getInstanceStatus(ch.instanceName),
|
|
586
|
+
state: this.getInstanceExecutionState(ch.instanceName),
|
|
587
|
+
ipc: this.instanceIpcClients.has(ch.instanceName),
|
|
588
|
+
costCents: this.costGuard?.getDailyCostCents(ch.instanceName) ?? 0,
|
|
589
|
+
rateLimits: this.statuslineWatcher.getRateLimits(ch.instanceName) ?? null,
|
|
590
|
+
}));
|
|
591
|
+
// Combined roster (matches /api/fleet and agend ls)
|
|
592
|
+
const allInstances = [...fleetInstances, ...classicInstances];
|
|
593
|
+
// Fleet summary counts (fleet + Classic combined)
|
|
594
|
+
const running_count = allInstances.filter(i => i.status === "running").length;
|
|
595
|
+
const paused_count = allInstances.filter(i => i.status === "paused").length;
|
|
596
|
+
// System memory (GB, 1 decimal)
|
|
597
|
+
const totalGB = totalmem() / (1024 ** 3);
|
|
598
|
+
const usedGB = (totalmem() - freemem()) / (1024 ** 3);
|
|
599
|
+
const system_mem_gb = {
|
|
600
|
+
used: Math.round(usedGB * 10) / 10,
|
|
601
|
+
total: Math.round(totalGB * 10) / 10,
|
|
602
|
+
};
|
|
603
|
+
// Fleet memory: O(1) cgroup read (includes entire service tree: fleet + CLIs + MCP servers)
|
|
604
|
+
// This avoids blocking the event loop with per-instance tree scans.
|
|
605
|
+
const fleetMem = readFleetMemory();
|
|
606
|
+
const fleet_mem_mb = fleetMem.cgroupAnonBytes != null
|
|
607
|
+
? Math.round(fleetMem.cgroupAnonBytes / (1024 * 1024) * 10) / 10
|
|
608
|
+
: null;
|
|
578
609
|
return {
|
|
579
610
|
uptime_seconds: Math.floor((Date.now() - this.startedAt) / 1000),
|
|
580
611
|
memory_mb: { rss: toMB(mem.rss), heapUsed: toMB(mem.heapUsed), heapTotal: toMB(mem.heapTotal) },
|
|
581
|
-
instances,
|
|
612
|
+
instances: fleetInstances, // SysInfo.instances is fleet-only; /api/fleet enriches with Classic
|
|
582
613
|
fleet_cost_cents: this.costGuard?.getFleetTotalCents() ?? 0,
|
|
583
614
|
fleet_cost_limit_cents: this.costGuard?.getLimitCents() ?? 0,
|
|
615
|
+
running_count,
|
|
616
|
+
paused_count,
|
|
617
|
+
fleet_mem_mb,
|
|
618
|
+
system_mem_gb,
|
|
584
619
|
};
|
|
585
620
|
}
|
|
586
621
|
/** Load fleet.yaml and build routing table */
|
|
@@ -2926,7 +2961,8 @@ export class FleetManager {
|
|
|
2926
2961
|
await data.respond(text);
|
|
2927
2962
|
}
|
|
2928
2963
|
else if (data.command === "sysinfo") {
|
|
2929
|
-
|
|
2964
|
+
// Slash commands are Discord-only; use plain lines (no markdown table)
|
|
2965
|
+
await data.respond(this.topicCommands.getSysInfoText({ platform: "discord" }));
|
|
2930
2966
|
}
|
|
2931
2967
|
else if (data.command === "dashboard") {
|
|
2932
2968
|
// Reply is ephemeral (adapter defers non-chat commands ephemerally), so
|
|
@@ -3272,7 +3308,8 @@ export class FleetManager {
|
|
|
3272
3308
|
await data.respond(text);
|
|
3273
3309
|
}
|
|
3274
3310
|
else if (data.command === "sysinfo") {
|
|
3275
|
-
|
|
3311
|
+
// Slash commands are Discord-only; use plain lines (no markdown table)
|
|
3312
|
+
await data.respond(this.topicCommands.getSysInfoText({ platform: "discord" }));
|
|
3276
3313
|
}
|
|
3277
3314
|
else if (data.command === "dashboard") {
|
|
3278
3315
|
// Reply is ephemeral (adapter defers non-chat commands ephemerally), so
|
|
@@ -9802,72 +9839,81 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
9802
9839
|
}
|
|
9803
9840
|
// Fleet API (enriched for agent board)
|
|
9804
9841
|
if (req.method === "GET" && req.url === "/api/fleet") {
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
const
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9842
|
+
try {
|
|
9843
|
+
const sysInfo = this.getSysInfo();
|
|
9844
|
+
const fleetInstances = sysInfo.instances.map(inst => ({ ...inst, classic: false }));
|
|
9845
|
+
const fleetNames = new Set(fleetInstances.map(inst => inst.name));
|
|
9846
|
+
const classicInstances = (this.classicChannels?.getAll() ?? [])
|
|
9847
|
+
.filter(channel => !fleetNames.has(channel.instanceName))
|
|
9848
|
+
.map(channel => ({
|
|
9849
|
+
name: channel.instanceName,
|
|
9850
|
+
status: this.getInstanceStatus(channel.instanceName),
|
|
9851
|
+
state: this.getInstanceExecutionState(channel.instanceName),
|
|
9852
|
+
ipc: this.instanceIpcClients.has(channel.instanceName),
|
|
9853
|
+
costCents: this.costGuard?.getDailyCostCents(channel.instanceName) ?? 0,
|
|
9854
|
+
rateLimits: this.statuslineWatcher.getRateLimits(channel.instanceName) ?? null,
|
|
9855
|
+
classic: true,
|
|
9856
|
+
classicName: channel.name,
|
|
9857
|
+
channelId: channel.channelId,
|
|
9858
|
+
adapterId: channel.adapterId ?? null,
|
|
9859
|
+
}));
|
|
9860
|
+
const enriched = [...fleetInstances, ...classicInstances].map(inst => {
|
|
9861
|
+
const config = this.fleetConfig?.instances[inst.name];
|
|
9862
|
+
const persistedInboundAt = readLastInboundAt(this.getInstanceDir(inst.name));
|
|
9863
|
+
const lastActivity = inst.classic
|
|
9864
|
+
? Math.max(persistedInboundAt ?? 0, readClassicLastActivityAt(this.dataDir, inst.name) ?? 0) || null
|
|
9865
|
+
: (persistedInboundAt ?? this.lastActivityMs(inst.name)) || null;
|
|
9866
|
+
const backend = this.backendNameForInstance(inst.name);
|
|
9867
|
+
const resolvedModel = this.resolveInstanceModel(inst.name);
|
|
9868
|
+
const effortStrategy = this.effortStrategyFor(inst.name);
|
|
9869
|
+
const resolvedEffort = this.resolveInstanceEffort(inst.name);
|
|
9870
|
+
// Find claimed tasks for this instance
|
|
9871
|
+
let currentTask = null;
|
|
9872
|
+
try {
|
|
9873
|
+
const tasks = this.scheduler?.db.listTasks({ assignee: inst.name, status: "claimed" });
|
|
9874
|
+
if (tasks?.length)
|
|
9875
|
+
currentTask = tasks[0].title;
|
|
9876
|
+
}
|
|
9877
|
+
catch (err) {
|
|
9878
|
+
this.logger.debug({ err, name: inst.name }, "Scheduler listTasks failed (/api/fleet)");
|
|
9879
|
+
}
|
|
9880
|
+
return {
|
|
9881
|
+
...inst,
|
|
9882
|
+
description: config?.description ?? ("classicName" in inst ? inst.classicName : null),
|
|
9883
|
+
backend,
|
|
9884
|
+
// Settings renders these runtime-effective values rather than the
|
|
9885
|
+
// sparse user-authored YAML. `auto` means the supported CLI is
|
|
9886
|
+
// using its own effort default; null is reserved for unsupported.
|
|
9887
|
+
model: resolvedModel.model,
|
|
9888
|
+
model_display: resolvedModel.display,
|
|
9889
|
+
model_source: resolvedModel.source,
|
|
9890
|
+
effort: effortStrategy === "unsupported" ? null : (resolvedEffort.effort ?? "auto"),
|
|
9891
|
+
effort_supported: effortStrategy !== "unsupported",
|
|
9892
|
+
tool_set: config?.tool_set ?? "full",
|
|
9893
|
+
general_topic: config?.general_topic ?? false,
|
|
9894
|
+
// User activity is persisted by the daemon, so both the board and
|
|
9895
|
+
// auto-pause retain an accurate age across fleet restarts.
|
|
9896
|
+
lastActivity,
|
|
9897
|
+
currentTask,
|
|
9898
|
+
idle: this.getInstanceIdle(inst.name),
|
|
9899
|
+
state: this.getInstanceExecutionState(inst.name),
|
|
9900
|
+
};
|
|
9901
|
+
});
|
|
9902
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
9903
|
+
res.writeHead(200);
|
|
9904
|
+
res.end(JSON.stringify({
|
|
9905
|
+
...sysInfo,
|
|
9906
|
+
version: this.currentVersion,
|
|
9907
|
+
instances: enriched,
|
|
9908
|
+
}));
|
|
9909
|
+
}
|
|
9910
|
+
catch (err) {
|
|
9911
|
+
this.logger.error({ err }, "/api/fleet failed");
|
|
9912
|
+
if (!res.headersSent) {
|
|
9913
|
+
res.writeHead(500);
|
|
9914
|
+
res.end("Internal Server Error");
|
|
9841
9915
|
}
|
|
9842
|
-
|
|
9843
|
-
...inst,
|
|
9844
|
-
description: config?.description ?? ("classicName" in inst ? inst.classicName : null),
|
|
9845
|
-
backend,
|
|
9846
|
-
// Settings renders these runtime-effective values rather than the
|
|
9847
|
-
// sparse user-authored YAML. `auto` means the supported CLI is
|
|
9848
|
-
// using its own effort default; null is reserved for unsupported.
|
|
9849
|
-
model: resolvedModel.model,
|
|
9850
|
-
model_display: resolvedModel.display,
|
|
9851
|
-
model_source: resolvedModel.source,
|
|
9852
|
-
effort: effortStrategy === "unsupported" ? null : (resolvedEffort.effort ?? "auto"),
|
|
9853
|
-
effort_supported: effortStrategy !== "unsupported",
|
|
9854
|
-
tool_set: config?.tool_set ?? "full",
|
|
9855
|
-
general_topic: config?.general_topic ?? false,
|
|
9856
|
-
// User activity is persisted by the daemon, so both the board and
|
|
9857
|
-
// auto-pause retain an accurate age across fleet restarts.
|
|
9858
|
-
lastActivity,
|
|
9859
|
-
currentTask,
|
|
9860
|
-
idle: this.getInstanceIdle(inst.name),
|
|
9861
|
-
state: this.getInstanceExecutionState(inst.name),
|
|
9862
|
-
};
|
|
9863
|
-
});
|
|
9864
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
9865
|
-
res.writeHead(200);
|
|
9866
|
-
res.end(JSON.stringify({
|
|
9867
|
-
...sysInfo,
|
|
9868
|
-
version: this.currentVersion,
|
|
9869
|
-
instances: enriched,
|
|
9870
|
-
}));
|
|
9916
|
+
}
|
|
9871
9917
|
return;
|
|
9872
9918
|
}
|
|
9873
9919
|
// Activity API
|