@adhdev/daemon-standalone 1.0.18-rc.2 → 1.0.18-rc.20
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/index.js +12457 -7337
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/public/assets/index-BeWlq-WP.js +121 -0
- package/public/assets/index-CXZe3Zxn.css +1 -0
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +117 -4
- package/vendor/mcp-server/index.js.map +1 -1
- package/vendor/mcp-server/package.json +1 -1
- package/vendor/session-host-daemon/index.d.mts +7 -0
- package/vendor/session-host-daemon/index.d.ts +7 -0
- package/vendor/session-host-daemon/index.js +117 -18
- package/vendor/session-host-daemon/index.js.map +1 -1
- package/vendor/session-host-daemon/index.mjs +118 -18
- package/vendor/session-host-daemon/index.mjs.map +1 -1
- package/public/assets/index-D8z4TEna.js +0 -121
- package/public/assets/index-DU4BBoUD.css +0 -1
|
@@ -25,6 +25,7 @@ import * as fs3 from "fs";
|
|
|
25
25
|
import * as net from "net";
|
|
26
26
|
import {
|
|
27
27
|
SessionHostRegistry,
|
|
28
|
+
classifyTermination,
|
|
28
29
|
createLineParser,
|
|
29
30
|
createResponseEnvelope,
|
|
30
31
|
getDefaultSessionHostEndpoint
|
|
@@ -268,13 +269,16 @@ var PtySessionRuntime = class {
|
|
|
268
269
|
this.respondToTerminalQueries(data);
|
|
269
270
|
this.onDataCallback(data);
|
|
270
271
|
});
|
|
271
|
-
this.ptyProcess.onExit(({ exitCode }) => {
|
|
272
|
+
this.ptyProcess.onExit(({ exitCode, signal }) => {
|
|
272
273
|
this.ptyProcess = null;
|
|
273
274
|
this.screenMirror?.dispose();
|
|
274
275
|
this.screenMirror = null;
|
|
275
276
|
this.pendingQueryScanTail = "";
|
|
276
277
|
this.terminalModeScanTail = "";
|
|
277
|
-
this.onExitCallback(
|
|
278
|
+
this.onExitCallback(
|
|
279
|
+
typeof exitCode === "number" ? exitCode : null,
|
|
280
|
+
typeof signal === "number" ? signal : null
|
|
281
|
+
);
|
|
278
282
|
});
|
|
279
283
|
return this.ptyProcess.pid;
|
|
280
284
|
}
|
|
@@ -386,10 +390,12 @@ import * as path from "path";
|
|
|
386
390
|
var SessionHostStorage = class {
|
|
387
391
|
rootDir;
|
|
388
392
|
runtimesDir;
|
|
393
|
+
tombstonesDir;
|
|
389
394
|
constructor(options = {}) {
|
|
390
395
|
const appName = options.appName || "adhdev";
|
|
391
396
|
this.rootDir = path.join(os2.homedir(), ".adhdev", "session-host", appName);
|
|
392
397
|
this.runtimesDir = path.join(this.rootDir, "runtimes");
|
|
398
|
+
this.tombstonesDir = path.join(this.rootDir, "tombstones");
|
|
393
399
|
}
|
|
394
400
|
loadAll() {
|
|
395
401
|
if (!fs2.existsSync(this.runtimesDir)) return [];
|
|
@@ -425,6 +431,52 @@ var SessionHostStorage = class {
|
|
|
425
431
|
} catch {
|
|
426
432
|
}
|
|
427
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Persist a compact termination tombstone. Kept in a separate directory from
|
|
436
|
+
* live runtimes so it survives the post-exit cleanup of the runtime file and
|
|
437
|
+
* remains inspectable for post-mortem diagnostics.
|
|
438
|
+
*/
|
|
439
|
+
saveTombstone(sessionId, termination) {
|
|
440
|
+
fs2.mkdirSync(this.tombstonesDir, { recursive: true });
|
|
441
|
+
const filePath = path.join(this.tombstonesDir, `${sessionId}.json`);
|
|
442
|
+
const payload = {
|
|
443
|
+
sessionId,
|
|
444
|
+
termination,
|
|
445
|
+
updatedAt: Date.now()
|
|
446
|
+
};
|
|
447
|
+
fs2.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
448
|
+
}
|
|
449
|
+
loadTombstone(sessionId) {
|
|
450
|
+
const filePath = path.join(this.tombstonesDir, `${sessionId}.json`);
|
|
451
|
+
try {
|
|
452
|
+
const parsed = JSON.parse(fs2.readFileSync(filePath, "utf8"));
|
|
453
|
+
return parsed?.sessionId ? parsed : null;
|
|
454
|
+
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
loadAllTombstones() {
|
|
459
|
+
if (!fs2.existsSync(this.tombstonesDir)) return [];
|
|
460
|
+
const entries = fs2.readdirSync(this.tombstonesDir, { withFileTypes: true });
|
|
461
|
+
const states = [];
|
|
462
|
+
for (const entry of entries) {
|
|
463
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
464
|
+
const fullPath = path.join(this.tombstonesDir, entry.name);
|
|
465
|
+
try {
|
|
466
|
+
const parsed = JSON.parse(fs2.readFileSync(fullPath, "utf8"));
|
|
467
|
+
if (parsed?.sessionId) states.push(parsed);
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return states.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
472
|
+
}
|
|
473
|
+
removeTombstone(sessionId) {
|
|
474
|
+
const filePath = path.join(this.tombstonesDir, `${sessionId}.json`);
|
|
475
|
+
try {
|
|
476
|
+
fs2.unlinkSync(filePath);
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
}
|
|
428
480
|
};
|
|
429
481
|
|
|
430
482
|
// src/session-diagnostics.ts
|
|
@@ -640,6 +692,9 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
640
692
|
recentTransitions = [];
|
|
641
693
|
exitWaiters = /* @__PURE__ */ new Map();
|
|
642
694
|
lastNoOutputInputWarnAt = /* @__PURE__ */ new Map();
|
|
695
|
+
// Records the most recent explicit stop/delete/restart/prune request per
|
|
696
|
+
// session so the termination diagnostic can attribute the exit to it.
|
|
697
|
+
stopRequests = /* @__PURE__ */ new Map();
|
|
643
698
|
constructor(options = {}) {
|
|
644
699
|
super();
|
|
645
700
|
this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || "adhdev");
|
|
@@ -837,6 +892,7 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
837
892
|
return { success: true, result: this.registry.getSession(request.payload.sessionId) };
|
|
838
893
|
}
|
|
839
894
|
case "stop_session": {
|
|
895
|
+
this.stopRequests.set(request.payload.sessionId, "stop");
|
|
840
896
|
this.registry.setLifecycle(request.payload.sessionId, "stopping");
|
|
841
897
|
this.persistNow(request.payload.sessionId);
|
|
842
898
|
this.requireRuntime(request.payload.sessionId).stop();
|
|
@@ -851,6 +907,7 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
851
907
|
if (!request.payload.force) {
|
|
852
908
|
return { success: false, error: `Session ${record.sessionId} is still running; pass force to stop and delete it` };
|
|
853
909
|
}
|
|
910
|
+
this.stopRequests.set(record.sessionId, "delete");
|
|
854
911
|
this.registry.setLifecycle(record.sessionId, "stopping");
|
|
855
912
|
this.persistNow(record.sessionId);
|
|
856
913
|
this.requireRuntime(record.sessionId).stop();
|
|
@@ -860,6 +917,8 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
860
917
|
}
|
|
861
918
|
this.registry.deleteSession(record.sessionId);
|
|
862
919
|
this.storage.remove(record.sessionId);
|
|
920
|
+
this.storage.removeTombstone(record.sessionId);
|
|
921
|
+
this.stopRequests.delete(record.sessionId);
|
|
863
922
|
this.emitEvent({ type: "session_deleted", sessionId: record.sessionId });
|
|
864
923
|
this.recordRuntimeTransition(record.sessionId, "delete_session", record.lifecycle, void 0, true);
|
|
865
924
|
return { success: true, result: { sessionId: record.sessionId, deleted: true } };
|
|
@@ -1167,6 +1226,7 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
1167
1226
|
throw new Error(`Unknown session: ${sessionId}`);
|
|
1168
1227
|
}
|
|
1169
1228
|
if (this.runtimes.has(sessionId)) {
|
|
1229
|
+
this.stopRequests.set(sessionId, "restart");
|
|
1170
1230
|
this.registry.setLifecycle(sessionId, "stopping");
|
|
1171
1231
|
this.persistNow(sessionId);
|
|
1172
1232
|
this.recordRuntimeTransition(sessionId, "restart_requested", "stopping", void 0, true);
|
|
@@ -1237,6 +1297,7 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
1237
1297
|
true
|
|
1238
1298
|
);
|
|
1239
1299
|
if (this.runtimes.has(record.sessionId)) {
|
|
1300
|
+
this.stopRequests.set(record.sessionId, "prune");
|
|
1240
1301
|
this.registry.setLifecycle(record.sessionId, "stopping");
|
|
1241
1302
|
this.persistNow(record.sessionId);
|
|
1242
1303
|
this.requireRuntime(record.sessionId).stop();
|
|
@@ -1246,6 +1307,8 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
1246
1307
|
}
|
|
1247
1308
|
this.registry.deleteSession(record.sessionId);
|
|
1248
1309
|
this.storage.remove(record.sessionId);
|
|
1310
|
+
this.storage.removeTombstone(record.sessionId);
|
|
1311
|
+
this.stopRequests.delete(record.sessionId);
|
|
1249
1312
|
}
|
|
1250
1313
|
startRuntime(record, payload, startEventType) {
|
|
1251
1314
|
const runtime = new PtySessionRuntime({
|
|
@@ -1256,22 +1319,7 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
1256
1319
|
this.schedulePersist(record.sessionId);
|
|
1257
1320
|
this.emitEvent({ type: "session_output", sessionId: record.sessionId, seq, data });
|
|
1258
1321
|
},
|
|
1259
|
-
onExit: (exitCode) =>
|
|
1260
|
-
this.registry.markStopped(record.sessionId, exitCode === 0 ? "stopped" : "failed");
|
|
1261
|
-
this.runtimes.delete(record.sessionId);
|
|
1262
|
-
this.resolveExitWaiters(record.sessionId, exitCode);
|
|
1263
|
-
this.persistNow(record.sessionId);
|
|
1264
|
-
this.emitEvent({ type: "session_exit", sessionId: record.sessionId, exitCode });
|
|
1265
|
-
this.recordRuntimeTransition(
|
|
1266
|
-
record.sessionId,
|
|
1267
|
-
"session_exit",
|
|
1268
|
-
exitCode === 0 ? "stopped" : "failed",
|
|
1269
|
-
void 0,
|
|
1270
|
-
exitCode === 0,
|
|
1271
|
-
exitCode === 0 ? void 0 : `exitCode=${exitCode}`
|
|
1272
|
-
);
|
|
1273
|
-
setTimeout(() => this.storage.remove(record.sessionId), 5e3);
|
|
1274
|
-
}
|
|
1322
|
+
onExit: (exitCode, signal) => this.handleRuntimeExit(record, exitCode, signal)
|
|
1275
1323
|
});
|
|
1276
1324
|
this.registry.setLifecycle(record.sessionId, "starting");
|
|
1277
1325
|
const pid = runtime.start();
|
|
@@ -1282,6 +1330,58 @@ var SessionHostServer = class extends EventEmitter {
|
|
|
1282
1330
|
this.recordRuntimeTransition(record.sessionId, startEventType, startedRecord.lifecycle, `pid=${pid}`, true);
|
|
1283
1331
|
return startedRecord;
|
|
1284
1332
|
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Handle a PTY termination: classify the (exitCode, signal) pair, stamp the
|
|
1335
|
+
* record + tombstone, emit exactly one structured diagnostic, and schedule
|
|
1336
|
+
* cleanup of the live persistence file (the tombstone is retained).
|
|
1337
|
+
*/
|
|
1338
|
+
handleRuntimeExit(record, exitCode, signal) {
|
|
1339
|
+
const priorRecord = this.registry.getSession(record.sessionId);
|
|
1340
|
+
const termination = classifyTermination({
|
|
1341
|
+
exitCode,
|
|
1342
|
+
signal,
|
|
1343
|
+
osPid: priorRecord?.osPid,
|
|
1344
|
+
previousLifecycle: priorRecord?.lifecycle,
|
|
1345
|
+
lastOutputAt: priorRecord?.lastActivityAt,
|
|
1346
|
+
requestedStop: this.stopRequests.get(record.sessionId),
|
|
1347
|
+
terminatedAt: Date.now()
|
|
1348
|
+
});
|
|
1349
|
+
this.stopRequests.delete(record.sessionId);
|
|
1350
|
+
this.registry.markStopped(record.sessionId, termination.lifecycle, termination);
|
|
1351
|
+
this.runtimes.delete(record.sessionId);
|
|
1352
|
+
this.resolveExitWaiters(record.sessionId, exitCode);
|
|
1353
|
+
this.persistNow(record.sessionId);
|
|
1354
|
+
this.storage.saveTombstone(record.sessionId, termination);
|
|
1355
|
+
this.emitEvent({ type: "session_exit", sessionId: record.sessionId, exitCode, signal, termination });
|
|
1356
|
+
const summary = `reason=${termination.reason} exitCode=${termination.exitCode === null ? "unknown" : termination.exitCode} signal=${termination.signal ?? "none"}`;
|
|
1357
|
+
this.recordHostLog(
|
|
1358
|
+
termination.lifecycle === "stopped" ? "info" : "warn",
|
|
1359
|
+
`session terminated: ${summary}`,
|
|
1360
|
+
record.sessionId,
|
|
1361
|
+
{
|
|
1362
|
+
runtimeKey: record.runtimeKey,
|
|
1363
|
+
providerType: record.providerType,
|
|
1364
|
+
osPid: termination.osPid,
|
|
1365
|
+
exitCode: termination.exitCode,
|
|
1366
|
+
signal: termination.signal,
|
|
1367
|
+
reason: termination.reason,
|
|
1368
|
+
previousLifecycle: termination.previousLifecycle,
|
|
1369
|
+
lifecycle: termination.lifecycle,
|
|
1370
|
+
lastOutputAt: termination.lastOutputAt,
|
|
1371
|
+
requestedStop: termination.requestedStop
|
|
1372
|
+
}
|
|
1373
|
+
);
|
|
1374
|
+
this.recordRuntimeTransition(
|
|
1375
|
+
record.sessionId,
|
|
1376
|
+
"session_exit",
|
|
1377
|
+
termination.lifecycle,
|
|
1378
|
+
summary,
|
|
1379
|
+
termination.lifecycle === "stopped",
|
|
1380
|
+
termination.lifecycle === "stopped" ? void 0 : summary
|
|
1381
|
+
);
|
|
1382
|
+
setTimeout(() => this.storage.remove(record.sessionId), 5e3).unref?.();
|
|
1383
|
+
return termination;
|
|
1384
|
+
}
|
|
1285
1385
|
};
|
|
1286
1386
|
|
|
1287
1387
|
// src/index.ts
|