@songsid/agend 2.1.4-beta.11 → 2.1.4-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/classic-channel-manager.d.ts +6 -0
- package/dist/classic-channel-manager.js +28 -0
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +23 -12
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/fleet-manager.d.ts +9 -1
- package/dist/fleet-manager.js +99 -16
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-yaml-slim.d.ts +10 -0
- package/dist/fleet-yaml-slim.js +59 -0
- package/dist/fleet-yaml-slim.js.map +1 -0
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +2 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/service-installer.js +1 -0
- package/dist/service-installer.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -9,7 +9,7 @@ import { formatUpdateProgress } from "./update-progress.js";
|
|
|
9
9
|
import { sdNotify, sdNotifyBlocking } from "./sd-notify.js";
|
|
10
10
|
import { readFleetMemory } from "./process-memory.js";
|
|
11
11
|
import { ReplyDeduper } from "./reply-dedup.js";
|
|
12
|
-
import { isScalar, parseDocument } from "yaml";
|
|
12
|
+
import { isMap, isScalar, parseDocument } from "yaml";
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
15
15
|
/** Fallback access policy for a channel with no `access:` block — open (no gate). */
|
|
@@ -54,6 +54,7 @@ import { releaseProcessFleetLock } from "./fleet-lock.js";
|
|
|
54
54
|
import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
55
55
|
import { loadOrCreateWebToken, WEB_TOKEN_INVALID_MESSAGE } from "./web-auth.js";
|
|
56
56
|
import { RestartProgress } from "./restart-progress.js";
|
|
57
|
+
import { collectRedundantInstanceDefaultPaths } from "./fleet-yaml-slim.js";
|
|
57
58
|
import { getTmuxSession } from "./config.js";
|
|
58
59
|
export function resolveReplyThreadId(argsThreadId, instanceConfig) {
|
|
59
60
|
if (typeof argsThreadId === "string" && argsThreadId.length > 0) {
|
|
@@ -1453,6 +1454,7 @@ export class FleetManager {
|
|
|
1453
1454
|
// Rotate fleet.log if oversized (before any logging)
|
|
1454
1455
|
rotateLogIfNeeded(join(this.dataDir, "fleet.log"));
|
|
1455
1456
|
const fleet = this.loadConfig(configPath);
|
|
1457
|
+
this.slimFleetConfigAtStartup();
|
|
1456
1458
|
setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
|
|
1457
1459
|
const savedUpdateProgress = readUpdateProgress(this.dataDir);
|
|
1458
1460
|
const pendingUpdateProgress = savedUpdateProgress
|
|
@@ -3902,7 +3904,29 @@ export class FleetManager {
|
|
|
3902
3904
|
}
|
|
3903
3905
|
/** Resolve display name for an instance, fallback to instance name. */
|
|
3904
3906
|
resolveDisplayName(instanceName) {
|
|
3905
|
-
return this.fleetConfig?.instances[instanceName]?.display_name
|
|
3907
|
+
return this.fleetConfig?.instances[instanceName]?.display_name
|
|
3908
|
+
?? this.classicChannels?.getAll().find(ch => ch.instanceName === instanceName)?.displayName
|
|
3909
|
+
?? instanceName;
|
|
3910
|
+
}
|
|
3911
|
+
/** Persist identity to the instance's actual config store. Classic instances
|
|
3912
|
+
* are registry rows in classicBot.yaml, not fleet.yaml instance entries. */
|
|
3913
|
+
setInstanceDisplayName(instanceName, displayName) {
|
|
3914
|
+
const fleetInstance = this.fleetConfig?.instances[instanceName];
|
|
3915
|
+
if (fleetInstance) {
|
|
3916
|
+
fleetInstance.display_name = displayName;
|
|
3917
|
+
this.saveFleetConfig();
|
|
3918
|
+
return true;
|
|
3919
|
+
}
|
|
3920
|
+
return this.classicChannels?.setDisplayNameByInstance(instanceName, displayName) ?? false;
|
|
3921
|
+
}
|
|
3922
|
+
setInstanceDescription(instanceName, description) {
|
|
3923
|
+
const fleetInstance = this.fleetConfig?.instances[instanceName];
|
|
3924
|
+
if (fleetInstance) {
|
|
3925
|
+
fleetInstance.description = description;
|
|
3926
|
+
this.saveFleetConfig();
|
|
3927
|
+
return true;
|
|
3928
|
+
}
|
|
3929
|
+
return this.classicChannels?.setDescriptionByInstance(instanceName, description) ?? false;
|
|
3906
3930
|
}
|
|
3907
3931
|
handleSetDisplayName(instanceName, msg) {
|
|
3908
3932
|
const fleetRequestId = msg.fleetRequestId;
|
|
@@ -3915,8 +3939,10 @@ export class FleetManager {
|
|
|
3915
3939
|
ipc.send({ type: "fleet_display_name_response", fleetRequestId, error: "Name must be 1-30 characters" });
|
|
3916
3940
|
return;
|
|
3917
3941
|
}
|
|
3918
|
-
this.
|
|
3919
|
-
|
|
3942
|
+
if (!this.setInstanceDisplayName(instanceName, displayName)) {
|
|
3943
|
+
ipc.send({ type: "fleet_display_name_response", fleetRequestId, error: `Instance '${instanceName}' not found` });
|
|
3944
|
+
return;
|
|
3945
|
+
}
|
|
3920
3946
|
this.logger.info({ instanceName, displayName }, "Display name set");
|
|
3921
3947
|
ipc.send({ type: "fleet_display_name_response", fleetRequestId, result: { display_name: displayName } });
|
|
3922
3948
|
}
|
|
@@ -3931,8 +3957,10 @@ export class FleetManager {
|
|
|
3931
3957
|
ipc.send({ type: "fleet_description_response", fleetRequestId, error: "Description cannot be empty" });
|
|
3932
3958
|
return;
|
|
3933
3959
|
}
|
|
3934
|
-
this.
|
|
3935
|
-
|
|
3960
|
+
if (!this.setInstanceDescription(instanceName, description)) {
|
|
3961
|
+
ipc.send({ type: "fleet_description_response", fleetRequestId, error: `Instance '${instanceName}' not found` });
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3936
3964
|
this.logger.info({ instanceName, description: description.slice(0, 80) }, "Description set");
|
|
3937
3965
|
ipc.send({ type: "fleet_description_response", fleetRequestId, result: { description } });
|
|
3938
3966
|
}
|
|
@@ -4061,8 +4089,8 @@ export class FleetManager {
|
|
|
4061
4089
|
return { error: "Fleet config not available" };
|
|
4062
4090
|
if (!name || name.length > 30)
|
|
4063
4091
|
return { error: "Name must be 1-30 characters" };
|
|
4064
|
-
this.
|
|
4065
|
-
|
|
4092
|
+
if (!this.setInstanceDisplayName(instance, name))
|
|
4093
|
+
return { error: `Instance '${instance}' not found` };
|
|
4066
4094
|
return { display_name: name };
|
|
4067
4095
|
}
|
|
4068
4096
|
async handleSetDescriptionHttp(instance, description) {
|
|
@@ -4070,8 +4098,8 @@ export class FleetManager {
|
|
|
4070
4098
|
return { error: "Fleet config not available" };
|
|
4071
4099
|
if (!description)
|
|
4072
4100
|
return { error: "Description cannot be empty" };
|
|
4073
|
-
this.
|
|
4074
|
-
|
|
4101
|
+
if (!this.setInstanceDescription(instance, description))
|
|
4102
|
+
return { error: `Instance '${instance}' not found` };
|
|
4075
4103
|
return { description };
|
|
4076
4104
|
}
|
|
4077
4105
|
summarizeToolCall(tool, args) {
|
|
@@ -4203,7 +4231,8 @@ export class FleetManager {
|
|
|
4203
4231
|
}
|
|
4204
4232
|
/**
|
|
4205
4233
|
* Patch only values changed in the effective config into the original YAML
|
|
4206
|
-
* document. Unknown keys
|
|
4234
|
+
* document. Unknown keys and comments remain untouched; redundant
|
|
4235
|
+
* non-identity instance leaves are canonicalized to inheritance afterward.
|
|
4207
4236
|
*/
|
|
4208
4237
|
saveFleetConfig(explicitPatches = []) {
|
|
4209
4238
|
if (!this.fleetConfig || !this.configPath)
|
|
@@ -4219,9 +4248,9 @@ export class FleetManager {
|
|
|
4219
4248
|
}
|
|
4220
4249
|
this.rawFleetConfig = loadRawFleetConfig(this.configPath);
|
|
4221
4250
|
this.patchFleetDocument(this.rawFleetDocument, [], this.savedFleetConfigSnapshot, this.fleetConfig);
|
|
4222
|
-
// Settings edits are expressed against the raw config
|
|
4223
|
-
//
|
|
4224
|
-
//
|
|
4251
|
+
// Settings edits are expressed against the raw config, so apply them before
|
|
4252
|
+
// canonicalization. A non-identity value equal to its inherited default is
|
|
4253
|
+
// intentionally stored as inheritance rather than an explicit duplicate.
|
|
4225
4254
|
for (const patch of explicitPatches) {
|
|
4226
4255
|
if (patch.remove) {
|
|
4227
4256
|
// YAML's deleteIn throws when an inherited nested key has no raw parent
|
|
@@ -4236,7 +4265,24 @@ export class FleetManager {
|
|
|
4236
4265
|
this.patchFleetDocument(this.rawFleetDocument, patch.path, before, patch.value);
|
|
4237
4266
|
}
|
|
4238
4267
|
}
|
|
4268
|
+
const rawAfterPatches = this.rawFleetDocument.toJS();
|
|
4269
|
+
const redundantPaths = collectRedundantInstanceDefaultPaths(rawAfterPatches);
|
|
4270
|
+
for (const path of redundantPaths) {
|
|
4271
|
+
if (this.rawFleetDocument.hasIn(path))
|
|
4272
|
+
this.rawFleetDocument.deleteIn(path);
|
|
4273
|
+
// Avoid leaving empty operational maps such as `terminal: {}` while
|
|
4274
|
+
// preserving the instance mapping itself and all surrounding comments.
|
|
4275
|
+
for (let depth = path.length - 1; depth > 2; depth--) {
|
|
4276
|
+
const parentPath = path.slice(0, depth);
|
|
4277
|
+
const parent = this.rawFleetDocument.getIn(parentPath, true);
|
|
4278
|
+
if (!isMap(parent) || parent.items.length > 0)
|
|
4279
|
+
break;
|
|
4280
|
+
this.rawFleetDocument.deleteIn(parentPath);
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4239
4283
|
const output = String(this.rawFleetDocument);
|
|
4284
|
+
if (redundantPaths.length > 0)
|
|
4285
|
+
this.writeFleetConfigBackup(source);
|
|
4240
4286
|
const tempPath = `${this.configPath}.tmp-${process.pid}`;
|
|
4241
4287
|
writeFileSync(tempPath, output, "utf-8");
|
|
4242
4288
|
if (existsSync(this.configPath))
|
|
@@ -4244,7 +4290,36 @@ export class FleetManager {
|
|
|
4244
4290
|
renameSync(tempPath, this.configPath);
|
|
4245
4291
|
this.rawFleetConfig = loadRawFleetConfig(this.configPath);
|
|
4246
4292
|
this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
|
|
4247
|
-
this.logger.info({ path: this.configPath }, "Saved fleet config (lossless patch)");
|
|
4293
|
+
this.logger.info({ path: this.configPath, strippedDefaults: redundantPaths.length }, "Saved fleet config (lossless patch)");
|
|
4294
|
+
}
|
|
4295
|
+
/** One-time upgrade migration; invalid YAML is never rewritten. */
|
|
4296
|
+
slimFleetConfigAtStartup() {
|
|
4297
|
+
const redundantPaths = collectRedundantInstanceDefaultPaths(this.rawFleetConfig);
|
|
4298
|
+
if (redundantPaths.length === 0)
|
|
4299
|
+
return;
|
|
4300
|
+
const validation = validateFleetConfig(this.rawFleetConfig);
|
|
4301
|
+
if (!validation.valid) {
|
|
4302
|
+
this.logger.warn({ errors: validation.errors, redundantDefaults: redundantPaths.length }, "Skipping fleet.yaml default slimming because the raw config is invalid");
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4305
|
+
try {
|
|
4306
|
+
this.saveFleetConfig();
|
|
4307
|
+
this.logger.info({ strippedDefaults: redundantPaths.length, backup: `${this.configPath}.bak` }, "Slimmed redundant instance defaults in fleet.yaml");
|
|
4308
|
+
}
|
|
4309
|
+
catch (err) {
|
|
4310
|
+
// A migration must not turn a previously bootable fleet into an outage.
|
|
4311
|
+
this.logger.warn({ err }, "Could not slim fleet.yaml; continuing with the original config");
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
writeFleetConfigBackup(source) {
|
|
4315
|
+
if (!this.configPath)
|
|
4316
|
+
return;
|
|
4317
|
+
const backupPath = `${this.configPath}.bak`;
|
|
4318
|
+
const tempPath = `${backupPath}.tmp-${process.pid}`;
|
|
4319
|
+
writeFileSync(tempPath, source, "utf-8");
|
|
4320
|
+
if (existsSync(this.configPath))
|
|
4321
|
+
chmodSync(tempPath, statSync(this.configPath).mode);
|
|
4322
|
+
renameSync(tempPath, backupPath);
|
|
4248
4323
|
}
|
|
4249
4324
|
patchFleetDocument(document, path, before, after) {
|
|
4250
4325
|
if (Object.is(before, after))
|
|
@@ -4296,7 +4371,12 @@ export class FleetManager {
|
|
|
4296
4371
|
currentNode.value = after;
|
|
4297
4372
|
}
|
|
4298
4373
|
else {
|
|
4299
|
-
|
|
4374
|
+
// Use a YAML collection node for newly-added objects. Passing the raw
|
|
4375
|
+
// object creates a scalar wrapper: it serializes, but nested hasIn /
|
|
4376
|
+
// deleteIn cannot traverse it (notably when slimming create_instance).
|
|
4377
|
+
document.setIn(path, after !== null && typeof after === "object"
|
|
4378
|
+
? document.createNode(after)
|
|
4379
|
+
: after);
|
|
4300
4380
|
}
|
|
4301
4381
|
}
|
|
4302
4382
|
}
|
|
@@ -7078,6 +7158,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
7078
7158
|
return;
|
|
7079
7159
|
const workDir = join(getAgendHome(), "workspaces", instanceName);
|
|
7080
7160
|
ensureWorkspaceGit(workDir);
|
|
7161
|
+
const classicIdentity = this.classicChannels?.getAll().find(ch => ch.instanceName === instanceName);
|
|
7081
7162
|
const config = {
|
|
7082
7163
|
...DEFAULT_INSTANCE_CONFIG,
|
|
7083
7164
|
...this.fleetConfig?.defaults,
|
|
@@ -7085,6 +7166,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
7085
7166
|
lightweight: true,
|
|
7086
7167
|
...(backend ? { backend } : {}),
|
|
7087
7168
|
...(model ? { model } : {}),
|
|
7169
|
+
...(classicIdentity?.displayName ? { display_name: classicIdentity.displayName } : {}),
|
|
7170
|
+
...(classicIdentity?.description ? { description: classicIdentity.description } : {}),
|
|
7088
7171
|
...(autoPauseAfter !== undefined ? { auto_pause_after: autoPauseAfter } : {}),
|
|
7089
7172
|
...(preTaskCommand ? { pre_task_command: preTaskCommand } : {}),
|
|
7090
7173
|
};
|