@songsid/agend 2.0.8-beta.8 → 2.0.8

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.
Files changed (46) hide show
  1. package/README.md +8 -3
  2. package/dist/adapter-world.d.ts +1 -1
  3. package/dist/adapter-world.js +2 -2
  4. package/dist/adapter-world.js.map +1 -1
  5. package/dist/agent-endpoint.js +6 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/backend/codex.js +10 -3
  8. package/dist/backend/codex.js.map +1 -1
  9. package/dist/channel/adapters/discord.d.ts +4 -4
  10. package/dist/channel/adapters/discord.js +162 -111
  11. package/dist/channel/adapters/discord.js.map +1 -1
  12. package/dist/channel/adapters/telegram.d.ts +1 -1
  13. package/dist/channel/adapters/telegram.js +3 -1
  14. package/dist/channel/adapters/telegram.js.map +1 -1
  15. package/dist/channel/tool-router.js +4 -2
  16. package/dist/channel/tool-router.js.map +1 -1
  17. package/dist/channel/tool-tracker.js +2 -2
  18. package/dist/channel/tool-tracker.js.map +1 -1
  19. package/dist/channel/types.d.ts +14 -6
  20. package/dist/cli.js +150 -95
  21. package/dist/cli.js.map +1 -1
  22. package/dist/daemon.d.ts +25 -1
  23. package/dist/daemon.js +149 -43
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/fleet-manager.d.ts +54 -5
  26. package/dist/fleet-manager.js +423 -170
  27. package/dist/fleet-manager.js.map +1 -1
  28. package/dist/instance-lifecycle.js +9 -0
  29. package/dist/instance-lifecycle.js.map +1 -1
  30. package/dist/logger.d.ts +9 -1
  31. package/dist/logger.js +17 -7
  32. package/dist/logger.js.map +1 -1
  33. package/dist/outbound-handlers.d.ts +3 -0
  34. package/dist/outbound-handlers.js +17 -0
  35. package/dist/outbound-handlers.js.map +1 -1
  36. package/dist/outbound-schemas.d.ts +1 -1
  37. package/dist/tmux-control.d.ts +10 -0
  38. package/dist/tmux-control.js +29 -0
  39. package/dist/tmux-control.js.map +1 -1
  40. package/dist/tmux-manager.d.ts +6 -0
  41. package/dist/tmux-manager.js +17 -0
  42. package/dist/tmux-manager.js.map +1 -1
  43. package/dist/topic-commands.d.ts +21 -0
  44. package/dist/topic-commands.js +73 -6
  45. package/dist/topic-commands.js.map +1 -1
  46. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -131,12 +131,40 @@ fleet
131
131
  .argument("[instance]", "Specific instance to stop")
132
132
  .action(async (instance) => {
133
133
  if (instance) {
134
- const { FleetManager } = await import("./fleet-manager.js");
135
- const fm = new FleetManager(DATA_DIR);
136
- await fm.stopInstance(instance);
137
- console.log("Stopped");
134
+ // Stop a single instance via the RUNNING fleet's HTTP API. Do NOT spawn a
135
+ // detached FleetManager and call stopInstance(): its in-memory daemons map
136
+ // is empty, so lifecycle.stop falls back to reading the instance's
137
+ // daemon.pid — which is the shared fleet process pid (daemons run
138
+ // in-process) — and SIGTERMs the whole fleet.
139
+ const { loadFleetConfig } = await import("./config.js");
140
+ const fleet = loadFleetConfig(FLEET_CONFIG_PATH);
141
+ const port = fleet.health_port ?? 19280;
142
+ let token = "";
143
+ try {
144
+ token = readFileSync(join(DATA_DIR, "web.token"), "utf-8").trim();
145
+ }
146
+ catch { /* fleet may not be running */ }
147
+ try {
148
+ const resp = await fetch(`http://127.0.0.1:${port}/stop/${encodeURIComponent(instance)}`, {
149
+ method: "POST",
150
+ headers: token ? { "X-Agend-Token": token } : {},
151
+ });
152
+ const body = await resp.json().catch(() => ({}));
153
+ if (resp.ok) {
154
+ console.log(`Instance "${instance}" stopped`);
155
+ }
156
+ else {
157
+ console.error(`Stop failed: ${body.error ?? resp.statusText}`);
158
+ process.exit(1);
159
+ }
160
+ }
161
+ catch {
162
+ console.error(`Cannot connect to fleet (port ${port}). Is the fleet running?`);
163
+ process.exit(1);
164
+ }
165
+ return;
138
166
  }
139
- else {
167
+ {
140
168
  const pidPath = join(DATA_DIR, "fleet.pid");
141
169
  if (!existsSync(pidPath)) {
142
170
  console.error("Fleet is not running (no PID file found)");
@@ -185,8 +213,16 @@ fleet
185
213
  const { loadFleetConfig } = await import("./config.js");
186
214
  const fleet = loadFleetConfig(FLEET_CONFIG_PATH);
187
215
  const port = fleet.health_port ?? 19280;
216
+ let token = "";
217
+ try {
218
+ token = readFileSync(join(DATA_DIR, "web.token"), "utf-8").trim();
219
+ }
220
+ catch { /* fleet may not be running */ }
188
221
  try {
189
- const resp = await fetch(`http://127.0.0.1:${port}/restart/${encodeURIComponent(instance)}`, { method: "POST" });
222
+ const resp = await fetch(`http://127.0.0.1:${port}/restart/${encodeURIComponent(instance)}`, {
223
+ method: "POST",
224
+ headers: token ? { "X-Agend-Token": token } : {},
225
+ });
190
226
  const body = await resp.json();
191
227
  if (resp.ok) {
192
228
  console.log(`Instance "${instance}" restarted (immediate)`);
@@ -1046,8 +1082,7 @@ program
1046
1082
  .option("--version <ver>", "Specific version to install")
1047
1083
  .option("--beta", "Install beta version")
1048
1084
  .action(async (opts) => {
1049
- const { installService, activateService, detectPlatform } = await import("./service-installer.js");
1050
- const { spawnSync, spawn: spawnAsync } = await import("node:child_process");
1085
+ const { spawnSync } = await import("node:child_process");
1051
1086
  const tag = opts.version ? opts.version : (opts.beta ? "beta" : "latest");
1052
1087
  const pkg = `@songsid/agend@${tag}`;
1053
1088
  console.log(`\n Updating AgEnD to ${tag}...\n`);
@@ -1139,7 +1174,6 @@ program
1139
1174
  const newVersion = (verifyResult.stdout ?? "").trim();
1140
1175
  console.log(` ✓ Installed: ${newVersion}`);
1141
1176
  // ── Update service file ──
1142
- const plat = detectPlatform();
1143
1177
  if (agendPath) {
1144
1178
  try {
1145
1179
  // Use the NEW binary to install service (old binary's templates may be deleted)
@@ -1156,69 +1190,19 @@ program
1156
1190
  }
1157
1191
  }
1158
1192
  // ── Restart fleet ──
1159
- const pidPath = join(DATA_DIR, "fleet.pid");
1160
- if (!existsSync(pidPath)) {
1161
- console.log("\n Fleet not running. Start with: agend start\n");
1162
- return;
1163
- }
1164
- // Kill old fleet process first to prevent duplicate
1165
- const oldPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
1166
- if (oldPid) {
1167
- try {
1168
- process.kill(oldPid, "SIGTERM");
1169
- }
1170
- catch { /* already gone */ }
1171
- // Wait up to 10s for old process to exit
1172
- for (let i = 0; i < 20; i++) {
1173
- try {
1174
- process.kill(oldPid, 0);
1175
- }
1176
- catch {
1177
- break;
1178
- }
1179
- spawnSync("sleep", ["0.5"]);
1180
- }
1181
- // Force kill if still alive
1182
- try {
1183
- process.kill(oldPid, "SIGKILL");
1184
- }
1185
- catch { /* already gone */ }
1186
- try {
1187
- unlinkSync(pidPath);
1188
- }
1189
- catch { /* best effort */ }
1190
- }
1193
+ // Run the restart through the NEWLY-INSTALLED binary (agendPath), not inline.
1194
+ // The inline restart would execute the OLD binary's code — exactly the logic
1195
+ // that may be missing or buggy on the version being upgraded from. `agend
1196
+ // restart` (new binary) does the 4-environment service detection (system
1197
+ // systemd → user systemd → launchd → detached pid).
1191
1198
  console.log(" Restarting fleet...");
1192
- if (plat === "macos") {
1193
- const uid = process.getuid?.() ?? 501;
1194
- const label = "com.agend.fleet";
1195
- try {
1196
- execSync(`launchctl kickstart -k gui/${uid}/${label}`, { stdio: "inherit", timeout: 15000 });
1197
- console.log(" ✓ Service restarted\n");
1198
- return;
1199
- }
1200
- catch { /* fall through to manual restart */ }
1199
+ const restartResult = spawnSync(agendPath, ["restart"], { encoding: "utf-8", timeout: 30000, stdio: "inherit" });
1200
+ if (restartResult.status === 0) {
1201
+ console.log(" ✓ Service restarted");
1201
1202
  }
1202
1203
  else {
1203
- try {
1204
- execSync("systemctl --user daemon-reload", { stdio: "pipe", timeout: 5000 });
1205
- // Reset failed state in case the kill above left systemd confused
1206
- try {
1207
- execSync("systemctl --user reset-failed com.agend.fleet", { stdio: "pipe", timeout: 5000 });
1208
- }
1209
- catch { }
1210
- execSync("systemctl --user start com.agend.fleet", { stdio: "inherit", timeout: 15000 });
1211
- console.log(" ✓ Service restarted\n");
1212
- return;
1213
- }
1214
- catch { /* fall through */ }
1204
+ console.log(" ⚠ Auto-restart failed. Run: agend start");
1215
1205
  }
1216
- // Fallback: start directly using new binary
1217
- const child = spawnAsync(agendPath, ["fleet", "start"], {
1218
- detached: true, stdio: "ignore",
1219
- });
1220
- child.unref();
1221
- console.log(" ✓ Fleet restarting in background\n");
1222
1206
  });
1223
1207
  program
1224
1208
  .command("reload")
@@ -1354,43 +1338,114 @@ program
1354
1338
  });
1355
1339
  program
1356
1340
  .command("restart")
1357
- .description("Restart the AgEnD service")
1341
+ .description("Restart the AgEnD service (auto-detects systemd/launchd/detached)")
1358
1342
  .action(async () => {
1359
- const { getServicePath, stopService, startService } = await import("./service-installer.js");
1360
- if (!getServicePath()) {
1361
- console.log("No service installed. Run: agend install");
1362
- return;
1363
- }
1343
+ // Try each runtime environment in order and stop at the first that succeeds.
1344
+ // Don't gate on fleet.pid: a fleet under systemd/launchd writes its pid in the
1345
+ // service's own HOME, which may differ from what this command resolves (sudo,
1346
+ // AGEND_HOME) — an absent pid file does NOT mean it's stopped. Ask the service
1347
+ // manager first. This is the canonical restart the `update` flow spawns.
1348
+ const { detectPlatform } = await import("./service-installer.js");
1349
+ const { spawn, spawnSync } = await import("node:child_process");
1350
+ const plat = detectPlatform();
1364
1351
  const pidPath = join(DATA_DIR, "fleet.pid");
1365
- let oldPid = null;
1366
- try {
1367
- oldPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
1368
- }
1369
- catch { }
1370
- stopService();
1371
- // Wait for old process to exit (up to 30s)
1372
- const deadline = Date.now() + 30_000;
1373
- while (Date.now() < deadline) {
1374
- // Check if process is still alive
1352
+ const isActive = (cmd) => {
1353
+ try {
1354
+ return spawnSync("sh", ["-c", cmd], { encoding: "utf-8", timeout: 5000 }).stdout?.trim() === "active";
1355
+ }
1356
+ catch {
1357
+ return false;
1358
+ }
1359
+ };
1360
+ const run = (cmd) => {
1361
+ try {
1362
+ execSync(cmd, { stdio: "inherit", timeout: 15000 });
1363
+ return true;
1364
+ }
1365
+ catch {
1366
+ return false;
1367
+ }
1368
+ };
1369
+ // 1. System-level systemd service "agend"
1370
+ if (plat !== "macos" && isActive("systemctl is-active agend 2>/dev/null")) {
1371
+ run("systemctl daemon-reload");
1372
+ if (run("systemctl restart agend")) {
1373
+ console.log("Service restarted (system).");
1374
+ return;
1375
+ }
1376
+ }
1377
+ // 2. User-level systemd service "com.agend.fleet"
1378
+ if (plat !== "macos" && isActive("systemctl --user is-active com.agend.fleet 2>/dev/null")) {
1379
+ run("systemctl --user daemon-reload");
1380
+ try {
1381
+ execSync("systemctl --user reset-failed com.agend.fleet", { stdio: "pipe", timeout: 5000 });
1382
+ }
1383
+ catch { /* best effort */ }
1384
+ if (run("systemctl --user restart com.agend.fleet")) {
1385
+ console.log("Service restarted (user).");
1386
+ return;
1387
+ }
1388
+ }
1389
+ // 3. launchd (macOS)
1390
+ if (plat === "macos") {
1391
+ const uid = process.getuid?.() ?? 501;
1392
+ const label = "com.agend.fleet";
1393
+ try {
1394
+ // print succeeds only if the service is loaded in launchd
1395
+ execSync(`launchctl print gui/${uid}/${label}`, { stdio: "pipe", timeout: 5000 });
1396
+ if (run(`launchctl kickstart -k gui/${uid}/${label}`)) {
1397
+ console.log("Service restarted.");
1398
+ return;
1399
+ }
1400
+ }
1401
+ catch { /* not loaded — fall through */ }
1402
+ }
1403
+ // 4. Detached process tracked by fleet.pid (no service manager)
1404
+ if (existsSync(pidPath)) {
1405
+ const oldPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
1406
+ let alive = false;
1375
1407
  if (oldPid) {
1376
1408
  try {
1377
1409
  process.kill(oldPid, 0);
1410
+ alive = true;
1378
1411
  }
1379
- catch {
1380
- break;
1412
+ catch { /* dead or not ours */ }
1413
+ }
1414
+ if (alive) {
1415
+ try {
1416
+ process.kill(oldPid, "SIGTERM");
1417
+ }
1418
+ catch { /* already gone */ }
1419
+ for (let i = 0; i < 20; i++) {
1420
+ try {
1421
+ process.kill(oldPid, 0);
1422
+ }
1423
+ catch {
1424
+ break;
1425
+ }
1426
+ spawnSync("sleep", ["0.5"]);
1381
1427
  }
1428
+ try {
1429
+ process.kill(oldPid, "SIGKILL");
1430
+ }
1431
+ catch { /* already gone */ }
1432
+ try {
1433
+ unlinkSync(pidPath);
1434
+ }
1435
+ catch { /* best effort */ }
1436
+ const child = spawn("sh", ["-c", "agend fleet start"], { detached: true, stdio: "ignore" });
1437
+ child.unref();
1438
+ console.log("Fleet restarting in background.");
1439
+ return;
1382
1440
  }
1383
- else if (!existsSync(pidPath)) {
1384
- break;
1441
+ // Stale pid file (process gone) — clean it up before declaring stopped.
1442
+ try {
1443
+ unlinkSync(pidPath);
1385
1444
  }
1386
- await new Promise(r => setTimeout(r, 500));
1387
- }
1388
- if (startService()) {
1389
- console.log("Service restarted.");
1390
- }
1391
- else {
1392
- console.log("Failed to restart service.");
1445
+ catch { /* best effort */ }
1393
1446
  }
1447
+ // 5. Nothing running anywhere
1448
+ console.log("Fleet not running. Start with: agend start");
1394
1449
  });
1395
1450
  program
1396
1451
  .command("quickstart")