@adhdev/daemon-standalone 1.0.18-rc.2 → 1.0.18-rc.3
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 +500 -148
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/public/assets/{index-D8z4TEna.js → index-Be0j0_PU.js} +50 -50
- package/public/assets/index-CQVaCclg.css +1 -0
- package/public/index.html +2 -2
- 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-DU4BBoUD.css +0 -1
package/public/index.html
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
<meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
|
|
8
8
|
<link rel="icon" href="/otter-logo.png" />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-Be0j0_PU.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-DyCWA2YZ.js">
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CQVaCclg.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
|
|
@@ -21,6 +21,7 @@ declare class SessionHostServer extends EventEmitter {
|
|
|
21
21
|
private recentTransitions;
|
|
22
22
|
private exitWaiters;
|
|
23
23
|
private lastNoOutputInputWarnAt;
|
|
24
|
+
private stopRequests;
|
|
24
25
|
constructor(options?: SessionHostServerOptions);
|
|
25
26
|
start(): Promise<void>;
|
|
26
27
|
stop(): Promise<void>;
|
|
@@ -47,6 +48,12 @@ declare class SessionHostServer extends EventEmitter {
|
|
|
47
48
|
private restorePersistedRuntimes;
|
|
48
49
|
private pruneDuplicateRuntime;
|
|
49
50
|
private startRuntime;
|
|
51
|
+
/**
|
|
52
|
+
* Handle a PTY termination: classify the (exitCode, signal) pair, stamp the
|
|
53
|
+
* record + tombstone, emit exactly one structured diagnostic, and schedule
|
|
54
|
+
* cleanup of the live persistence file (the tombstone is retained).
|
|
55
|
+
*/
|
|
56
|
+
private handleRuntimeExit;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export { SessionHostServer, type SessionHostServerOptions };
|
|
@@ -21,6 +21,7 @@ declare class SessionHostServer extends EventEmitter {
|
|
|
21
21
|
private recentTransitions;
|
|
22
22
|
private exitWaiters;
|
|
23
23
|
private lastNoOutputInputWarnAt;
|
|
24
|
+
private stopRequests;
|
|
24
25
|
constructor(options?: SessionHostServerOptions);
|
|
25
26
|
start(): Promise<void>;
|
|
26
27
|
stop(): Promise<void>;
|
|
@@ -47,6 +48,12 @@ declare class SessionHostServer extends EventEmitter {
|
|
|
47
48
|
private restorePersistedRuntimes;
|
|
48
49
|
private pruneDuplicateRuntime;
|
|
49
50
|
private startRuntime;
|
|
51
|
+
/**
|
|
52
|
+
* Handle a PTY termination: classify the (exitCode, signal) pair, stamp the
|
|
53
|
+
* record + tombstone, emit exactly one structured diagnostic, and schedule
|
|
54
|
+
* cleanup of the live persistence file (the tombstone is retained).
|
|
55
|
+
*/
|
|
56
|
+
private handleRuntimeExit;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export { SessionHostServer, type SessionHostServerOptions };
|
|
@@ -279,13 +279,16 @@ var PtySessionRuntime = class {
|
|
|
279
279
|
this.respondToTerminalQueries(data);
|
|
280
280
|
this.onDataCallback(data);
|
|
281
281
|
});
|
|
282
|
-
this.ptyProcess.onExit(({ exitCode }) => {
|
|
282
|
+
this.ptyProcess.onExit(({ exitCode, signal }) => {
|
|
283
283
|
this.ptyProcess = null;
|
|
284
284
|
this.screenMirror?.dispose();
|
|
285
285
|
this.screenMirror = null;
|
|
286
286
|
this.pendingQueryScanTail = "";
|
|
287
287
|
this.terminalModeScanTail = "";
|
|
288
|
-
this.onExitCallback(
|
|
288
|
+
this.onExitCallback(
|
|
289
|
+
typeof exitCode === "number" ? exitCode : null,
|
|
290
|
+
typeof signal === "number" ? signal : null
|
|
291
|
+
);
|
|
289
292
|
});
|
|
290
293
|
return this.ptyProcess.pid;
|
|
291
294
|
}
|
|
@@ -397,10 +400,12 @@ var path = __toESM(require("path"));
|
|
|
397
400
|
var SessionHostStorage = class {
|
|
398
401
|
rootDir;
|
|
399
402
|
runtimesDir;
|
|
403
|
+
tombstonesDir;
|
|
400
404
|
constructor(options = {}) {
|
|
401
405
|
const appName = options.appName || "adhdev";
|
|
402
406
|
this.rootDir = path.join(os2.homedir(), ".adhdev", "session-host", appName);
|
|
403
407
|
this.runtimesDir = path.join(this.rootDir, "runtimes");
|
|
408
|
+
this.tombstonesDir = path.join(this.rootDir, "tombstones");
|
|
404
409
|
}
|
|
405
410
|
loadAll() {
|
|
406
411
|
if (!fs2.existsSync(this.runtimesDir)) return [];
|
|
@@ -436,6 +441,52 @@ var SessionHostStorage = class {
|
|
|
436
441
|
} catch {
|
|
437
442
|
}
|
|
438
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Persist a compact termination tombstone. Kept in a separate directory from
|
|
446
|
+
* live runtimes so it survives the post-exit cleanup of the runtime file and
|
|
447
|
+
* remains inspectable for post-mortem diagnostics.
|
|
448
|
+
*/
|
|
449
|
+
saveTombstone(sessionId, termination) {
|
|
450
|
+
fs2.mkdirSync(this.tombstonesDir, { recursive: true });
|
|
451
|
+
const filePath = path.join(this.tombstonesDir, `${sessionId}.json`);
|
|
452
|
+
const payload = {
|
|
453
|
+
sessionId,
|
|
454
|
+
termination,
|
|
455
|
+
updatedAt: Date.now()
|
|
456
|
+
};
|
|
457
|
+
fs2.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
458
|
+
}
|
|
459
|
+
loadTombstone(sessionId) {
|
|
460
|
+
const filePath = path.join(this.tombstonesDir, `${sessionId}.json`);
|
|
461
|
+
try {
|
|
462
|
+
const parsed = JSON.parse(fs2.readFileSync(filePath, "utf8"));
|
|
463
|
+
return parsed?.sessionId ? parsed : null;
|
|
464
|
+
} catch {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
loadAllTombstones() {
|
|
469
|
+
if (!fs2.existsSync(this.tombstonesDir)) return [];
|
|
470
|
+
const entries = fs2.readdirSync(this.tombstonesDir, { withFileTypes: true });
|
|
471
|
+
const states = [];
|
|
472
|
+
for (const entry of entries) {
|
|
473
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
474
|
+
const fullPath = path.join(this.tombstonesDir, entry.name);
|
|
475
|
+
try {
|
|
476
|
+
const parsed = JSON.parse(fs2.readFileSync(fullPath, "utf8"));
|
|
477
|
+
if (parsed?.sessionId) states.push(parsed);
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return states.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
482
|
+
}
|
|
483
|
+
removeTombstone(sessionId) {
|
|
484
|
+
const filePath = path.join(this.tombstonesDir, `${sessionId}.json`);
|
|
485
|
+
try {
|
|
486
|
+
fs2.unlinkSync(filePath);
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
439
490
|
};
|
|
440
491
|
|
|
441
492
|
// src/session-diagnostics.ts
|
|
@@ -646,6 +697,9 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
646
697
|
recentTransitions = [];
|
|
647
698
|
exitWaiters = /* @__PURE__ */ new Map();
|
|
648
699
|
lastNoOutputInputWarnAt = /* @__PURE__ */ new Map();
|
|
700
|
+
// Records the most recent explicit stop/delete/restart/prune request per
|
|
701
|
+
// session so the termination diagnostic can attribute the exit to it.
|
|
702
|
+
stopRequests = /* @__PURE__ */ new Map();
|
|
649
703
|
constructor(options = {}) {
|
|
650
704
|
super();
|
|
651
705
|
this.endpoint = options.endpoint || (0, import_session_host_core4.getDefaultSessionHostEndpoint)(options.appName || "adhdev");
|
|
@@ -843,6 +897,7 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
843
897
|
return { success: true, result: this.registry.getSession(request.payload.sessionId) };
|
|
844
898
|
}
|
|
845
899
|
case "stop_session": {
|
|
900
|
+
this.stopRequests.set(request.payload.sessionId, "stop");
|
|
846
901
|
this.registry.setLifecycle(request.payload.sessionId, "stopping");
|
|
847
902
|
this.persistNow(request.payload.sessionId);
|
|
848
903
|
this.requireRuntime(request.payload.sessionId).stop();
|
|
@@ -857,6 +912,7 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
857
912
|
if (!request.payload.force) {
|
|
858
913
|
return { success: false, error: `Session ${record.sessionId} is still running; pass force to stop and delete it` };
|
|
859
914
|
}
|
|
915
|
+
this.stopRequests.set(record.sessionId, "delete");
|
|
860
916
|
this.registry.setLifecycle(record.sessionId, "stopping");
|
|
861
917
|
this.persistNow(record.sessionId);
|
|
862
918
|
this.requireRuntime(record.sessionId).stop();
|
|
@@ -866,6 +922,8 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
866
922
|
}
|
|
867
923
|
this.registry.deleteSession(record.sessionId);
|
|
868
924
|
this.storage.remove(record.sessionId);
|
|
925
|
+
this.storage.removeTombstone(record.sessionId);
|
|
926
|
+
this.stopRequests.delete(record.sessionId);
|
|
869
927
|
this.emitEvent({ type: "session_deleted", sessionId: record.sessionId });
|
|
870
928
|
this.recordRuntimeTransition(record.sessionId, "delete_session", record.lifecycle, void 0, true);
|
|
871
929
|
return { success: true, result: { sessionId: record.sessionId, deleted: true } };
|
|
@@ -1173,6 +1231,7 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
1173
1231
|
throw new Error(`Unknown session: ${sessionId}`);
|
|
1174
1232
|
}
|
|
1175
1233
|
if (this.runtimes.has(sessionId)) {
|
|
1234
|
+
this.stopRequests.set(sessionId, "restart");
|
|
1176
1235
|
this.registry.setLifecycle(sessionId, "stopping");
|
|
1177
1236
|
this.persistNow(sessionId);
|
|
1178
1237
|
this.recordRuntimeTransition(sessionId, "restart_requested", "stopping", void 0, true);
|
|
@@ -1243,6 +1302,7 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
1243
1302
|
true
|
|
1244
1303
|
);
|
|
1245
1304
|
if (this.runtimes.has(record.sessionId)) {
|
|
1305
|
+
this.stopRequests.set(record.sessionId, "prune");
|
|
1246
1306
|
this.registry.setLifecycle(record.sessionId, "stopping");
|
|
1247
1307
|
this.persistNow(record.sessionId);
|
|
1248
1308
|
this.requireRuntime(record.sessionId).stop();
|
|
@@ -1252,6 +1312,8 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
1252
1312
|
}
|
|
1253
1313
|
this.registry.deleteSession(record.sessionId);
|
|
1254
1314
|
this.storage.remove(record.sessionId);
|
|
1315
|
+
this.storage.removeTombstone(record.sessionId);
|
|
1316
|
+
this.stopRequests.delete(record.sessionId);
|
|
1255
1317
|
}
|
|
1256
1318
|
startRuntime(record, payload, startEventType) {
|
|
1257
1319
|
const runtime = new PtySessionRuntime({
|
|
@@ -1262,22 +1324,7 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
1262
1324
|
this.schedulePersist(record.sessionId);
|
|
1263
1325
|
this.emitEvent({ type: "session_output", sessionId: record.sessionId, seq, data });
|
|
1264
1326
|
},
|
|
1265
|
-
onExit: (exitCode) =>
|
|
1266
|
-
this.registry.markStopped(record.sessionId, exitCode === 0 ? "stopped" : "failed");
|
|
1267
|
-
this.runtimes.delete(record.sessionId);
|
|
1268
|
-
this.resolveExitWaiters(record.sessionId, exitCode);
|
|
1269
|
-
this.persistNow(record.sessionId);
|
|
1270
|
-
this.emitEvent({ type: "session_exit", sessionId: record.sessionId, exitCode });
|
|
1271
|
-
this.recordRuntimeTransition(
|
|
1272
|
-
record.sessionId,
|
|
1273
|
-
"session_exit",
|
|
1274
|
-
exitCode === 0 ? "stopped" : "failed",
|
|
1275
|
-
void 0,
|
|
1276
|
-
exitCode === 0,
|
|
1277
|
-
exitCode === 0 ? void 0 : `exitCode=${exitCode}`
|
|
1278
|
-
);
|
|
1279
|
-
setTimeout(() => this.storage.remove(record.sessionId), 5e3);
|
|
1280
|
-
}
|
|
1327
|
+
onExit: (exitCode, signal) => this.handleRuntimeExit(record, exitCode, signal)
|
|
1281
1328
|
});
|
|
1282
1329
|
this.registry.setLifecycle(record.sessionId, "starting");
|
|
1283
1330
|
const pid = runtime.start();
|
|
@@ -1288,6 +1335,58 @@ var SessionHostServer = class extends import_events.EventEmitter {
|
|
|
1288
1335
|
this.recordRuntimeTransition(record.sessionId, startEventType, startedRecord.lifecycle, `pid=${pid}`, true);
|
|
1289
1336
|
return startedRecord;
|
|
1290
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Handle a PTY termination: classify the (exitCode, signal) pair, stamp the
|
|
1340
|
+
* record + tombstone, emit exactly one structured diagnostic, and schedule
|
|
1341
|
+
* cleanup of the live persistence file (the tombstone is retained).
|
|
1342
|
+
*/
|
|
1343
|
+
handleRuntimeExit(record, exitCode, signal) {
|
|
1344
|
+
const priorRecord = this.registry.getSession(record.sessionId);
|
|
1345
|
+
const termination = (0, import_session_host_core4.classifyTermination)({
|
|
1346
|
+
exitCode,
|
|
1347
|
+
signal,
|
|
1348
|
+
osPid: priorRecord?.osPid,
|
|
1349
|
+
previousLifecycle: priorRecord?.lifecycle,
|
|
1350
|
+
lastOutputAt: priorRecord?.lastActivityAt,
|
|
1351
|
+
requestedStop: this.stopRequests.get(record.sessionId),
|
|
1352
|
+
terminatedAt: Date.now()
|
|
1353
|
+
});
|
|
1354
|
+
this.stopRequests.delete(record.sessionId);
|
|
1355
|
+
this.registry.markStopped(record.sessionId, termination.lifecycle, termination);
|
|
1356
|
+
this.runtimes.delete(record.sessionId);
|
|
1357
|
+
this.resolveExitWaiters(record.sessionId, exitCode);
|
|
1358
|
+
this.persistNow(record.sessionId);
|
|
1359
|
+
this.storage.saveTombstone(record.sessionId, termination);
|
|
1360
|
+
this.emitEvent({ type: "session_exit", sessionId: record.sessionId, exitCode, signal, termination });
|
|
1361
|
+
const summary = `reason=${termination.reason} exitCode=${termination.exitCode === null ? "unknown" : termination.exitCode} signal=${termination.signal ?? "none"}`;
|
|
1362
|
+
this.recordHostLog(
|
|
1363
|
+
termination.lifecycle === "stopped" ? "info" : "warn",
|
|
1364
|
+
`session terminated: ${summary}`,
|
|
1365
|
+
record.sessionId,
|
|
1366
|
+
{
|
|
1367
|
+
runtimeKey: record.runtimeKey,
|
|
1368
|
+
providerType: record.providerType,
|
|
1369
|
+
osPid: termination.osPid,
|
|
1370
|
+
exitCode: termination.exitCode,
|
|
1371
|
+
signal: termination.signal,
|
|
1372
|
+
reason: termination.reason,
|
|
1373
|
+
previousLifecycle: termination.previousLifecycle,
|
|
1374
|
+
lifecycle: termination.lifecycle,
|
|
1375
|
+
lastOutputAt: termination.lastOutputAt,
|
|
1376
|
+
requestedStop: termination.requestedStop
|
|
1377
|
+
}
|
|
1378
|
+
);
|
|
1379
|
+
this.recordRuntimeTransition(
|
|
1380
|
+
record.sessionId,
|
|
1381
|
+
"session_exit",
|
|
1382
|
+
termination.lifecycle,
|
|
1383
|
+
summary,
|
|
1384
|
+
termination.lifecycle === "stopped",
|
|
1385
|
+
termination.lifecycle === "stopped" ? void 0 : summary
|
|
1386
|
+
);
|
|
1387
|
+
setTimeout(() => this.storage.remove(record.sessionId), 5e3).unref?.();
|
|
1388
|
+
return termination;
|
|
1389
|
+
}
|
|
1291
1390
|
};
|
|
1292
1391
|
|
|
1293
1392
|
// src/index.ts
|