@songsid/agend 2.1.0-beta.30 → 2.1.0-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/antigravity.js +6 -0
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/types.d.ts +1 -1
- package/dist/channel/adapters/discord.js +1 -1
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +42 -3
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +14 -2
- package/dist/fleet-manager.js +79 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +4 -0
- package/dist/instance-lifecycle.js +7 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +6 -2
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +21 -9
- package/dist/outbound-handlers.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -122,6 +122,8 @@ export class FleetManager {
|
|
|
122
122
|
lastActivity = new Map();
|
|
123
123
|
/** Latest pane-derived execution snapshot reported by each daemon. */
|
|
124
124
|
instanceStateCache = new Map();
|
|
125
|
+
/** CLI pane status overrides; daemon.pid alone only proves FleetManager lives. */
|
|
126
|
+
instanceProcessStatus = new Map();
|
|
125
127
|
/** Instances currently being auto-paused by warm_cap, so concurrent checks don't double-evict. */
|
|
126
128
|
warmCapEvicting = new Set();
|
|
127
129
|
/** Per-instance tail keeps cross-instance and scheduled deliveries FIFO. */
|
|
@@ -369,6 +371,9 @@ export class FleetManager {
|
|
|
369
371
|
getInstanceStatus(name) {
|
|
370
372
|
if (this.lifecycle.isPaused(name))
|
|
371
373
|
return "paused";
|
|
374
|
+
const processStatus = this.instanceProcessStatus.get(name);
|
|
375
|
+
if (processStatus)
|
|
376
|
+
return processStatus;
|
|
372
377
|
const pidPath = join(this.getInstanceDir(name), "daemon.pid");
|
|
373
378
|
if (!existsSync(pidPath))
|
|
374
379
|
return "stopped";
|
|
@@ -410,6 +415,20 @@ export class FleetManager {
|
|
|
410
415
|
if (state === "idle" && previous?.state !== "idle")
|
|
411
416
|
this.enforceWarmCap();
|
|
412
417
|
}
|
|
418
|
+
cacheInstanceProcessStatus(name, status) {
|
|
419
|
+
if (status === "running") {
|
|
420
|
+
this.instanceProcessStatus.delete(name);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (status !== "crashed" && status !== "stopped")
|
|
424
|
+
return;
|
|
425
|
+
this.instanceProcessStatus.set(name, status);
|
|
426
|
+
// Never display the last ready prompt as current execution state after its
|
|
427
|
+
// owning CLI process has exited.
|
|
428
|
+
this.instanceStateCache.delete(name);
|
|
429
|
+
for (const check of this.instanceIdleWaiters.get(name) ?? [])
|
|
430
|
+
check();
|
|
431
|
+
}
|
|
413
432
|
/**
|
|
414
433
|
* Fleet-wide warm cap: if more than `defaults.warm_cap` instances are running,
|
|
415
434
|
* auto-pause the least-recently-active idle instances until back at the cap.
|
|
@@ -586,6 +605,7 @@ export class FleetManager {
|
|
|
586
605
|
}
|
|
587
606
|
this.ensureGeneralInstructions(config.working_directory, config.backend);
|
|
588
607
|
}
|
|
608
|
+
this.instanceProcessStatus.delete(name);
|
|
589
609
|
await this.lifecycle.start(name, config, topicMode);
|
|
590
610
|
// Auto-connect IPC — daemon.start() ensures socket is ready before resolving
|
|
591
611
|
await this.connectIpcToInstance(name);
|
|
@@ -688,22 +708,53 @@ export class FleetManager {
|
|
|
688
708
|
async stopInstance(name) {
|
|
689
709
|
this.failoverActive.delete(name);
|
|
690
710
|
this.instanceStateCache.delete(name);
|
|
711
|
+
this.instanceProcessStatus.delete(name);
|
|
691
712
|
this.lastDeliveryAt.delete(name);
|
|
692
713
|
return this.lifecycle.stop(name);
|
|
693
714
|
}
|
|
694
715
|
/** Restart a single instance, reloading fleet.yaml first to pick up config changes. */
|
|
695
|
-
async restartSingleInstance(name) {
|
|
716
|
+
async restartSingleInstance(name, opts) {
|
|
696
717
|
if (this.configPath) {
|
|
697
718
|
this.loadConfig(this.configPath);
|
|
698
719
|
this.routing.rebuild(this.fleetConfig);
|
|
699
720
|
this.reregisterClassicChannels();
|
|
700
721
|
}
|
|
701
722
|
const config = this.fleetConfig?.instances[name];
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
723
|
+
if (config) {
|
|
724
|
+
await this.stopInstance(name);
|
|
725
|
+
if (opts?.freshStart)
|
|
726
|
+
this.writeFreshStartMarker(name);
|
|
727
|
+
const topicMode = this.fleetConfig?.channel?.mode === "topic";
|
|
728
|
+
await this.startInstance(name, config, topicMode ?? false);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// Classic instance fallback
|
|
732
|
+
const channelId = this.classicChannels?.getChannelIdByInstance(name);
|
|
733
|
+
if (channelId) {
|
|
734
|
+
const fleetBackend = this.fleetConfig?.defaults?.backend;
|
|
735
|
+
const adapterId = this.classicChannels.getAdapterIdByInstance(name);
|
|
736
|
+
await this.stopInstance(name);
|
|
737
|
+
await new Promise(r => setTimeout(r, 1000)); // let tmux clean up
|
|
738
|
+
if (opts?.freshStart)
|
|
739
|
+
this.writeFreshStartMarker(name);
|
|
740
|
+
await this.startClassicInstance(name, this.classicChannels.getBackendByInstance(name, fleetBackend), this.classicChannels.getPreTaskCommand(channelId, adapterId), this.classicChannels.getModel(channelId, adapterId, this.fleetConfig?.defaults?.model), this.classicChannels.getAutoPauseAfter(channelId, adapterId, this.fleetConfig?.defaults?.auto_pause_after));
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
throw new Error(`Instance not found: ${name}`);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Mark an instance so its next daemon start skips session resume. Written AFTER
|
|
747
|
+
* stop (survives the old daemon's cleanup) and BEFORE start so the respawn reads
|
|
748
|
+
* it — reuses the crash-state → resumeDisabled path from crash-loop recovery.
|
|
749
|
+
* One-shot: the daemon deletes it on startup.
|
|
750
|
+
*/
|
|
751
|
+
writeFreshStartMarker(name) {
|
|
752
|
+
try {
|
|
753
|
+
writeFileSync(join(this.getInstanceDir(name), "crash-state.json"), JSON.stringify({ resumeDisabled: true, reason: "pty_error_restart" }));
|
|
754
|
+
}
|
|
755
|
+
catch (err) {
|
|
756
|
+
this.logger.warn({ err, name }, "freshStart: failed to write crash-state marker");
|
|
757
|
+
}
|
|
707
758
|
}
|
|
708
759
|
/** Load .env file from data dir into process.env */
|
|
709
760
|
loadEnvFile() {
|
|
@@ -1328,7 +1379,7 @@ export class FleetManager {
|
|
|
1328
1379
|
// Handle classic bot slash commands (/start, /stop, /chat, /compact, /save, /load)
|
|
1329
1380
|
this.adapter.on("slash_command", safeHandler(async (data) => {
|
|
1330
1381
|
if (data.command === "start") {
|
|
1331
|
-
await this.handleClassicStartSlash(data, adapterId
|
|
1382
|
+
await this.handleClassicStartSlash(data, adapterId);
|
|
1332
1383
|
}
|
|
1333
1384
|
else if (data.command === "stop") {
|
|
1334
1385
|
const reply = await this.handleClassicStop(data.channelId, adapterId);
|
|
@@ -1611,7 +1662,7 @@ export class FleetManager {
|
|
|
1611
1662
|
// Slash commands: classic bot + admin commands
|
|
1612
1663
|
adapter.on("slash_command", safeHandler(async (data) => {
|
|
1613
1664
|
if (data.command === "start") {
|
|
1614
|
-
await this.handleClassicStartSlash(data, adapterId
|
|
1665
|
+
await this.handleClassicStartSlash(data, adapterId);
|
|
1615
1666
|
}
|
|
1616
1667
|
else if (data.command === "stop") {
|
|
1617
1668
|
const reply = await this.handleClassicStop(data.channelId, adapterId);
|
|
@@ -1876,8 +1927,14 @@ export class FleetManager {
|
|
|
1876
1927
|
else if (msg.type === "fleet_set_description") {
|
|
1877
1928
|
this.handleSetDescription(name, msg);
|
|
1878
1929
|
}
|
|
1930
|
+
else if (msg.type === "instance_process_state") {
|
|
1931
|
+
this.cacheInstanceProcessStatus(name, msg.status);
|
|
1932
|
+
}
|
|
1879
1933
|
else if (msg.type === "instance_state" || msg.type === "instance_state_response") {
|
|
1880
1934
|
this.cacheInstanceExecutionState(name, msg);
|
|
1935
|
+
if (msg.type === "instance_state_response") {
|
|
1936
|
+
this.cacheInstanceProcessStatus(name, msg.processStatus);
|
|
1937
|
+
}
|
|
1881
1938
|
}
|
|
1882
1939
|
}, this.logger, `ipc.message[${name}]`));
|
|
1883
1940
|
// Ask daemon for any sessions that registered before we connected
|
|
@@ -4139,11 +4196,16 @@ When users create specialized instances, suggest these configurations:
|
|
|
4139
4196
|
return undefined;
|
|
4140
4197
|
return t("classic.backend_not_installed", backend, installation.binary, installation.install);
|
|
4141
4198
|
}
|
|
4142
|
-
/** Handle Discord's
|
|
4143
|
-
async handleClassicStartSlash(data, adapterId
|
|
4199
|
+
/** Handle Discord's required static slash choice, warning before a likely startup failure. */
|
|
4200
|
+
async handleClassicStartSlash(data, adapterId) {
|
|
4144
4201
|
const requestedBackend = typeof data.options?.backend === "string" ? data.options.backend : undefined;
|
|
4145
4202
|
if (!requestedBackend) {
|
|
4146
|
-
|
|
4203
|
+
// beta.31 made this option required. Discord can briefly retain the old
|
|
4204
|
+
// command schema client-side, however, so stale clients may still submit
|
|
4205
|
+
// `/start` without it. Do not resurrect the legacy 60-second component
|
|
4206
|
+
// menu in that case: fail immediately and make the user invoke the newly
|
|
4207
|
+
// registered command, which guarantees an explicit backend choice.
|
|
4208
|
+
await data.respond(t("classic.backend_required"));
|
|
4147
4209
|
return;
|
|
4148
4210
|
}
|
|
4149
4211
|
const warning = this.getMissingBackendWarning(requestedBackend);
|
|
@@ -4180,7 +4242,12 @@ When users create specialized instances, suggest these configurations:
|
|
|
4180
4242
|
await data.respond(text);
|
|
4181
4243
|
};
|
|
4182
4244
|
const timer = setTimeout(() => {
|
|
4183
|
-
|
|
4245
|
+
// Timeout: cancel the selection — do NOT fall back to default.
|
|
4246
|
+
const p = this.pendingClassicStarts.get(nonce);
|
|
4247
|
+
if (p) {
|
|
4248
|
+
this.pendingClassicStarts.delete(nonce);
|
|
4249
|
+
p.complete(t("classic.selection_expired"), p.messageId).catch(() => { });
|
|
4250
|
+
}
|
|
4184
4251
|
}, CLASSIC_BACKEND_SELECTION_TIMEOUT_MS);
|
|
4185
4252
|
timer.unref?.();
|
|
4186
4253
|
const pending = {
|