@songsid/agend 2.1.2-beta.43 → 2.1.2-beta.44
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 +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +4 -0
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +7 -1
- package/dist/fleet-manager.js +65 -3
- package/dist/fleet-manager.js.map +1 -1
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +60 -9
- package/dist/outbound-handlers.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -588,7 +588,25 @@ export class FleetManager {
|
|
|
588
588
|
getInstanceStatus(name) {
|
|
589
589
|
if (this.lifecycle.isPaused(name))
|
|
590
590
|
return "paused";
|
|
591
|
+
const daemon = this.lifecycle.daemons.get(name);
|
|
591
592
|
const processStatus = this.instanceProcessStatus.get(name);
|
|
593
|
+
// IPC can be disconnected during a respawn, so the event which announces
|
|
594
|
+
// the new live pane may be missed. The in-process daemon is authoritative
|
|
595
|
+
// in that case; do not leave a stale `crashed` cache masking an instance
|
|
596
|
+
// that has already recovered and can answer messages.
|
|
597
|
+
const daemonStatus = daemon?.getProcessStatus?.();
|
|
598
|
+
if (daemonStatus === "running") {
|
|
599
|
+
if (processStatus)
|
|
600
|
+
this.instanceProcessStatus.delete(name);
|
|
601
|
+
// A recovered in-process daemon is also the authority for clearing a
|
|
602
|
+
// marker left by a crash-loop/reconnect race. This keeps standalone
|
|
603
|
+
// `agend ls` from seeing the old marker after the next API outage.
|
|
604
|
+
try {
|
|
605
|
+
unlinkSync(join(this.getInstanceDir(name), "crash-state.json"));
|
|
606
|
+
}
|
|
607
|
+
catch { /* absent */ }
|
|
608
|
+
return "running";
|
|
609
|
+
}
|
|
592
610
|
if (processStatus)
|
|
593
611
|
return processStatus;
|
|
594
612
|
const pidPath = join(this.getInstanceDir(name), "daemon.pid");
|
|
@@ -670,6 +688,13 @@ export class FleetManager {
|
|
|
670
688
|
cacheInstanceProcessStatus(name, status) {
|
|
671
689
|
if (status === "running") {
|
|
672
690
|
this.instanceProcessStatus.delete(name);
|
|
691
|
+
// A prior crash-loop marker is one-shot. Successful respawn is the
|
|
692
|
+
// authoritative recovery signal even when the marker outlived an IPC
|
|
693
|
+
// disconnect and was not consumed by a new Daemon constructor.
|
|
694
|
+
try {
|
|
695
|
+
unlinkSync(join(this.getInstanceDir(name), "crash-state.json"));
|
|
696
|
+
}
|
|
697
|
+
catch { /* absent */ }
|
|
673
698
|
return;
|
|
674
699
|
}
|
|
675
700
|
if (status !== "crashed" && status !== "stopped")
|
|
@@ -901,11 +926,41 @@ export class FleetManager {
|
|
|
901
926
|
await new Promise(resolve => setTimeout(resolve, 250));
|
|
902
927
|
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));
|
|
903
928
|
}
|
|
904
|
-
async startInstance(name, config, topicMode, kind = "fleet-topic"
|
|
929
|
+
async startInstance(name, config, topicMode, kind = "fleet-topic",
|
|
930
|
+
/**
|
|
931
|
+
* Explicit starts (CLI/API) may resume a paused or failed daemon. Startup
|
|
932
|
+
* and reconcile calls leave this false so a persisted pause remains paused
|
|
933
|
+
* across a fleet restart.
|
|
934
|
+
*/
|
|
935
|
+
resumePaused = false) {
|
|
936
|
+
if (resumePaused && this.lifecycle.isPaused(name)) {
|
|
937
|
+
await this.lifecycle.wake(name, 30_000);
|
|
938
|
+
// A successful wake clears the persisted pause marker and produces a
|
|
939
|
+
// fresh instance_state snapshot. Drop any stale process error left by a
|
|
940
|
+
// pre-pause crash so /api/fleet and `agend ls` converge on running.
|
|
941
|
+
this.instanceProcessStatus.delete(name);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
905
944
|
if (this.lifecycle.isPaused(name)) {
|
|
906
945
|
this.logger.info({ name }, "Persisted paused instance — skipping startup");
|
|
907
946
|
return;
|
|
908
947
|
}
|
|
948
|
+
if (this.lifecycle.daemons.has(name)) {
|
|
949
|
+
// A crash-loop daemon remains in the lifecycle map so its health monitor
|
|
950
|
+
// can expose the failure. The old start path treated that object as
|
|
951
|
+
// already running and merely deleted the process-status cache, leaving a
|
|
952
|
+
// dead pane (and crash marker) behind. An explicit start is a recovery
|
|
953
|
+
// request: tear down the failed daemon and build a fresh one.
|
|
954
|
+
if (resumePaused) {
|
|
955
|
+
const status = this.getInstanceStatus(name);
|
|
956
|
+
if (status === "crashed" || status === "stopped") {
|
|
957
|
+
await this.restartSingleInstance(name);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
this.logger.info({ name }, "Instance already running, skipping");
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
909
964
|
if (config.general_topic) {
|
|
910
965
|
// antigravity (agy) does not read MCP instructions — fleet context and
|
|
911
966
|
// routing instructions are not injected, so it cannot act as a dispatcher.
|
|
@@ -916,12 +971,19 @@ export class FleetManager {
|
|
|
916
971
|
}
|
|
917
972
|
this.ensureGeneralInstructions(config.working_directory, config.backend);
|
|
918
973
|
}
|
|
919
|
-
this.instanceProcessStatus.delete(name);
|
|
920
974
|
await this.lifecycle.start(name, config, topicMode, {
|
|
921
975
|
kind,
|
|
922
976
|
backend: config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code",
|
|
923
977
|
model: this.resolveInstanceModel(name).display,
|
|
924
978
|
});
|
|
979
|
+
// Only clear a stale process status after a real start succeeded. Clearing
|
|
980
|
+
// it before lifecycle.start() can turn a crash-loop daemon's dead pane into
|
|
981
|
+
// a falsely running instance when lifecycle.start() returns early.
|
|
982
|
+
this.instanceProcessStatus.delete(name);
|
|
983
|
+
try {
|
|
984
|
+
unlinkSync(join(this.getInstanceDir(name), "crash-state.json"));
|
|
985
|
+
}
|
|
986
|
+
catch { /* consumed or absent */ }
|
|
925
987
|
// Auto-connect IPC — daemon.start() ensures socket is ready before resolving
|
|
926
988
|
await this.connectIpcToInstance(name);
|
|
927
989
|
}
|
|
@@ -6862,7 +6924,7 @@ When users create specialized instances, suggest these configurations:
|
|
|
6862
6924
|
(async () => {
|
|
6863
6925
|
try {
|
|
6864
6926
|
const topicMode = this.fleetConfig?.channel?.mode === "topic";
|
|
6865
|
-
await this.startInstance(name, config, topicMode ?? false);
|
|
6927
|
+
await this.startInstance(name, config, topicMode ?? false, "fleet-topic", true);
|
|
6866
6928
|
this.emitSseEvent("status", this.getUiStatus());
|
|
6867
6929
|
res.writeHead(200);
|
|
6868
6930
|
res.end(JSON.stringify({ ok: true }));
|