@adhdev/daemon-standalone 0.9.82-rc.99 → 1.0.1
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 +86588 -43350
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
- package/public/assets/index-EYZP2Wbo.js +121 -0
- package/public/assets/index-p-ehiI_L.css +1 -0
- package/public/assets/{terminal-D46M5EWH.js → terminal-DiXj2eIR.js} +21 -21
- package/public/assets/vendor-DyCWA2YZ.js +2776 -0
- package/public/index.html +3 -3
- package/public/snake.html +229 -0
- package/vendor/mcp-server/index.js +6539 -2572
- package/vendor/mcp-server/index.js.map +1 -1
- package/vendor/mcp-server/package.json +3 -2
- package/vendor/session-host-daemon/index.d.mts +0 -10
- package/vendor/session-host-daemon/index.d.ts +0 -10
- package/vendor/session-host-daemon/index.js +305 -196
- package/vendor/session-host-daemon/index.js.map +1 -1
- package/vendor/session-host-daemon/index.mjs +299 -188
- package/vendor/session-host-daemon/index.mjs.map +1 -1
- package/public/assets/index-Bso1b8Lh.css +0 -1
- package/public/assets/index-DaIkPFUd.js +0 -99
- package/public/assets/vendor-CgiI0UIA.js +0 -2745
|
@@ -24,13 +24,10 @@ import { EventEmitter } from "events";
|
|
|
24
24
|
import * as fs3 from "fs";
|
|
25
25
|
import * as net from "net";
|
|
26
26
|
import {
|
|
27
|
-
SESSION_HOST_SUPPORTED_REQUEST_TYPES,
|
|
28
27
|
SessionHostRegistry,
|
|
29
28
|
createLineParser,
|
|
30
29
|
createResponseEnvelope,
|
|
31
|
-
getDefaultSessionHostEndpoint
|
|
32
|
-
resolveSessionHostCols as resolveSessionHostCols2,
|
|
33
|
-
resolveSessionHostRows as resolveSessionHostRows2
|
|
30
|
+
getDefaultSessionHostEndpoint
|
|
34
31
|
} from "@adhdev/session-host-core";
|
|
35
32
|
|
|
36
33
|
// src/runtime.ts
|
|
@@ -74,7 +71,8 @@ function formatXtermViewportPlain(terminal, rows) {
|
|
|
74
71
|
const lines = [];
|
|
75
72
|
for (let i = start; i < end; i++) {
|
|
76
73
|
const line = buffer.getLine(i);
|
|
77
|
-
|
|
74
|
+
const raw = line ? line.translateToString(false) : "";
|
|
75
|
+
lines.push(raw.replace(/\s+$/, ""));
|
|
78
76
|
}
|
|
79
77
|
let first = 0;
|
|
80
78
|
let last = lines.length;
|
|
@@ -137,6 +135,9 @@ function createXtermMirror(options) {
|
|
|
137
135
|
if (serializer) return serializeXtermViewport(terminal, serializer, currentRows);
|
|
138
136
|
return formatXtermViewportPlain(terminal, currentRows);
|
|
139
137
|
},
|
|
138
|
+
formatPlainText() {
|
|
139
|
+
return formatXtermViewportPlain(terminal, currentRows).replace(/\r\n/g, "\n");
|
|
140
|
+
},
|
|
140
141
|
getCursorPosition() {
|
|
141
142
|
const buffer = terminal.buffer.active;
|
|
142
143
|
return {
|
|
@@ -169,6 +170,9 @@ function normalizeGhosttyBinding(mod) {
|
|
|
169
170
|
formatVT() {
|
|
170
171
|
return viewportSnapshot.formatVT();
|
|
171
172
|
},
|
|
173
|
+
formatPlainText() {
|
|
174
|
+
return viewportSnapshot.formatPlainText();
|
|
175
|
+
},
|
|
172
176
|
getCursorPosition() {
|
|
173
177
|
if (typeof handle.getCursorPosition === "function") return handle.getCursorPosition();
|
|
174
178
|
return viewportSnapshot.getCursorPosition();
|
|
@@ -217,6 +221,10 @@ var PtySessionRuntime = class {
|
|
|
217
221
|
ptyProcess = null;
|
|
218
222
|
screenMirror = null;
|
|
219
223
|
pendingQueryScanTail = "";
|
|
224
|
+
terminalModeScanTail = "";
|
|
225
|
+
altScreen = false;
|
|
226
|
+
pasteMode = false;
|
|
227
|
+
scrollRegion;
|
|
220
228
|
onDataCallback;
|
|
221
229
|
onExitCallback;
|
|
222
230
|
constructor(options) {
|
|
@@ -224,6 +232,7 @@ var PtySessionRuntime = class {
|
|
|
224
232
|
this.payload = options.payload;
|
|
225
233
|
this.cols = resolveSessionHostCols(options.payload.cols);
|
|
226
234
|
this.rows = resolveSessionHostRows(options.payload.rows);
|
|
235
|
+
this.scrollRegion = { top: 0, bot: this.rows - 1 };
|
|
227
236
|
this.onDataCallback = options.onData;
|
|
228
237
|
this.onExitCallback = options.onExit;
|
|
229
238
|
}
|
|
@@ -255,6 +264,7 @@ var PtySessionRuntime = class {
|
|
|
255
264
|
});
|
|
256
265
|
this.ptyProcess.onData((data) => {
|
|
257
266
|
this.screenMirror?.write(data);
|
|
267
|
+
this.trackTerminalModes(data);
|
|
258
268
|
this.respondToTerminalQueries(data);
|
|
259
269
|
this.onDataCallback(data);
|
|
260
270
|
});
|
|
@@ -263,6 +273,7 @@ var PtySessionRuntime = class {
|
|
|
263
273
|
this.screenMirror?.dispose();
|
|
264
274
|
this.screenMirror = null;
|
|
265
275
|
this.pendingQueryScanTail = "";
|
|
276
|
+
this.terminalModeScanTail = "";
|
|
266
277
|
this.onExitCallback(exitCode ?? null);
|
|
267
278
|
});
|
|
268
279
|
return this.ptyProcess.pid;
|
|
@@ -274,6 +285,12 @@ var PtySessionRuntime = class {
|
|
|
274
285
|
resize(cols, rows) {
|
|
275
286
|
if (!this.ptyProcess) throw new Error(`Session not running: ${this.sessionId}`);
|
|
276
287
|
this.ptyProcess.resize(cols, rows);
|
|
288
|
+
this.cols = Math.max(1, cols | 0);
|
|
289
|
+
this.rows = Math.max(1, rows | 0);
|
|
290
|
+
this.scrollRegion = {
|
|
291
|
+
top: Math.min(this.scrollRegion.top, this.rows - 1),
|
|
292
|
+
bot: Math.min(Math.max(this.scrollRegion.top, this.scrollRegion.bot), this.rows - 1)
|
|
293
|
+
};
|
|
277
294
|
this.screenMirror?.resize(cols, rows);
|
|
278
295
|
}
|
|
279
296
|
stop() {
|
|
@@ -297,6 +314,55 @@ var PtySessionRuntime = class {
|
|
|
297
314
|
getSnapshotText() {
|
|
298
315
|
return this.screenMirror?.formatVT() || "";
|
|
299
316
|
}
|
|
317
|
+
getTerminalSnapshot() {
|
|
318
|
+
if (!this.ptyProcess || !this.screenMirror) {
|
|
319
|
+
throw new Error(`Session not running: ${this.sessionId}`);
|
|
320
|
+
}
|
|
321
|
+
const cursor = this.screenMirror.getCursorPosition();
|
|
322
|
+
return {
|
|
323
|
+
text: this.screenMirror.formatPlainText(),
|
|
324
|
+
state: {
|
|
325
|
+
cursor: {
|
|
326
|
+
row: Math.max(0, cursor.row | 0),
|
|
327
|
+
col: Math.max(0, cursor.col | 0)
|
|
328
|
+
},
|
|
329
|
+
altScreen: this.altScreen,
|
|
330
|
+
pasteMode: this.pasteMode,
|
|
331
|
+
rawMode: true,
|
|
332
|
+
scrollRegion: { ...this.scrollRegion },
|
|
333
|
+
cols: this.cols,
|
|
334
|
+
rows: this.rows
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
trackTerminalModes(data) {
|
|
339
|
+
if (!data) return;
|
|
340
|
+
const combined = this.terminalModeScanTail + data;
|
|
341
|
+
const privateMode = /\x1b\[\?([0-9;]*)([hl])/g;
|
|
342
|
+
let privateMatch;
|
|
343
|
+
while ((privateMatch = privateMode.exec(combined)) !== null) {
|
|
344
|
+
const enabled = privateMatch[2] === "h";
|
|
345
|
+
for (const mode of privateMatch[1].split(";")) {
|
|
346
|
+
if (mode === "47" || mode === "1047" || mode === "1049") this.altScreen = enabled;
|
|
347
|
+
if (mode === "2004") this.pasteMode = enabled;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const scrollRegion = /\x1b\[(\d*)(?:;(\d*))?r/g;
|
|
351
|
+
let scrollMatch;
|
|
352
|
+
while ((scrollMatch = scrollRegion.exec(combined)) !== null) {
|
|
353
|
+
const top = scrollMatch[1] ? Number.parseInt(scrollMatch[1], 10) - 1 : 0;
|
|
354
|
+
const bot = scrollMatch[2] ? Number.parseInt(scrollMatch[2], 10) - 1 : this.rows - 1;
|
|
355
|
+
this.scrollRegion = {
|
|
356
|
+
top: Math.max(0, Math.min(this.rows - 1, top)),
|
|
357
|
+
bot: Math.max(0, Math.min(this.rows - 1, bot))
|
|
358
|
+
};
|
|
359
|
+
if (this.scrollRegion.bot < this.scrollRegion.top) {
|
|
360
|
+
this.scrollRegion = { top: 0, bot: this.rows - 1 };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const lastEscape = combined.lastIndexOf("\x1B");
|
|
364
|
+
this.terminalModeScanTail = lastEscape >= 0 && combined.length - lastEscape < 64 ? combined.slice(lastEscape) : "";
|
|
365
|
+
}
|
|
300
366
|
respondToTerminalQueries(data) {
|
|
301
367
|
if (!this.ptyProcess || !this.screenMirror || !data) return;
|
|
302
368
|
const combined = this.pendingQueryScanTail + data;
|
|
@@ -361,9 +427,203 @@ var SessionHostStorage = class {
|
|
|
361
427
|
}
|
|
362
428
|
};
|
|
363
429
|
|
|
430
|
+
// src/session-diagnostics.ts
|
|
431
|
+
import {
|
|
432
|
+
SESSION_HOST_SUPPORTED_REQUEST_TYPES
|
|
433
|
+
} from "@adhdev/session-host-core";
|
|
434
|
+
var MAX_RECENT_DIAGNOSTICS = 200;
|
|
435
|
+
function pushRecent(bucket, entry, max = MAX_RECENT_DIAGNOSTICS) {
|
|
436
|
+
bucket.push(entry);
|
|
437
|
+
if (bucket.length > max) {
|
|
438
|
+
bucket.splice(0, bucket.length - max);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function getSessionHostRecoveryLabel(record) {
|
|
442
|
+
const recoveryState = typeof record.meta?.runtimeRecoveryState === "string" ? String(record.meta.runtimeRecoveryState).trim() : "";
|
|
443
|
+
if (!recoveryState) return null;
|
|
444
|
+
if (recoveryState === "auto_resumed") return "restored after restart";
|
|
445
|
+
if (recoveryState === "resume_failed") return "restore failed";
|
|
446
|
+
if (recoveryState === "host_restart_interrupted") return "host restart interrupted";
|
|
447
|
+
if (recoveryState === "orphan_snapshot") return "snapshot recovered";
|
|
448
|
+
return recoveryState.replace(/_/g, " ");
|
|
449
|
+
}
|
|
450
|
+
function getSessionSurfaceKind(record) {
|
|
451
|
+
if (["starting", "running", "stopping", "interrupted"].includes(record.lifecycle)) {
|
|
452
|
+
return "live_runtime";
|
|
453
|
+
}
|
|
454
|
+
if ((record.lifecycle === "stopped" || record.lifecycle === "failed") && (record.meta?.restoredFromStorage === true || getSessionHostRecoveryLabel(record))) {
|
|
455
|
+
return "recovery_snapshot";
|
|
456
|
+
}
|
|
457
|
+
return "inactive_record";
|
|
458
|
+
}
|
|
459
|
+
function annotateSessionSurface(record) {
|
|
460
|
+
return {
|
|
461
|
+
...record,
|
|
462
|
+
surfaceKind: getSessionSurfaceKind(record)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function sanitizeDiagnosticsRecord(record) {
|
|
466
|
+
return {
|
|
467
|
+
...record,
|
|
468
|
+
launchCommand: {
|
|
469
|
+
command: record.launchCommand.command,
|
|
470
|
+
args: Array.isArray(record.launchCommand.args) ? [...record.launchCommand.args] : []
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function buildHostDiagnostics(params) {
|
|
475
|
+
const limit = Math.max(1, Math.min(200, Number(params.payload?.limit) || 50));
|
|
476
|
+
const allSessions = params.payload?.includeSessions === false ? void 0 : params.sessions.map((record) => annotateSessionSurface(record)).map((record) => sanitizeDiagnosticsRecord(record));
|
|
477
|
+
const liveRuntimes = allSessions?.filter((record) => record.surfaceKind === "live_runtime");
|
|
478
|
+
const recoverySnapshots = allSessions?.filter((record) => record.surfaceKind === "recovery_snapshot").slice(0, limit);
|
|
479
|
+
const inactiveRecords = allSessions?.filter((record) => record.surfaceKind === "inactive_record").slice(0, limit);
|
|
480
|
+
const sessions = allSessions ? [
|
|
481
|
+
...liveRuntimes || [],
|
|
482
|
+
...recoverySnapshots || [],
|
|
483
|
+
...inactiveRecords || []
|
|
484
|
+
] : void 0;
|
|
485
|
+
return {
|
|
486
|
+
hostStartedAt: params.hostStartedAt,
|
|
487
|
+
endpoint: params.endpointPath,
|
|
488
|
+
runtimeCount: params.runtimeCount,
|
|
489
|
+
supportedRequestTypes: [...SESSION_HOST_SUPPORTED_REQUEST_TYPES],
|
|
490
|
+
sessions,
|
|
491
|
+
liveRuntimes,
|
|
492
|
+
recoverySnapshots,
|
|
493
|
+
inactiveRecords,
|
|
494
|
+
recentLogs: params.recentLogs.slice(-limit),
|
|
495
|
+
recentRequests: params.recentRequests.slice(-limit),
|
|
496
|
+
recentTransitions: params.recentTransitions.slice(-limit)
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/session-lifecycle.ts
|
|
501
|
+
import {
|
|
502
|
+
resolveSessionHostCols as resolveSessionHostCols2,
|
|
503
|
+
resolveSessionHostRows as resolveSessionHostRows2
|
|
504
|
+
} from "@adhdev/session-host-core";
|
|
505
|
+
function compareDuplicateCandidates(a, b) {
|
|
506
|
+
const score = (record) => {
|
|
507
|
+
const lifecycleScore = record.lifecycle === "running" ? 4 : record.lifecycle === "starting" ? 3 : record.lifecycle === "stopping" ? 2 : record.lifecycle === "interrupted" ? 1 : 0;
|
|
508
|
+
return [
|
|
509
|
+
lifecycleScore,
|
|
510
|
+
record.writeOwner ? 1 : 0,
|
|
511
|
+
Array.isArray(record.attachedClients) ? record.attachedClients.length : 0,
|
|
512
|
+
record.lastActivityAt || 0,
|
|
513
|
+
record.startedAt || 0,
|
|
514
|
+
record.createdAt || 0
|
|
515
|
+
];
|
|
516
|
+
};
|
|
517
|
+
const aScore = score(a);
|
|
518
|
+
const bScore = score(b);
|
|
519
|
+
for (let i = 0; i < aScore.length; i += 1) {
|
|
520
|
+
if (aScore[i] === bScore[i]) continue;
|
|
521
|
+
return bScore[i] - aScore[i];
|
|
522
|
+
}
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
function planDuplicatePrune(sessions, filters) {
|
|
526
|
+
const candidates = sessions.filter((record) => ["starting", "running", "stopping", "interrupted"].includes(record.lifecycle)).filter((record) => !filters.providerFilter || record.providerType === filters.providerFilter).filter((record) => !filters.workspaceFilter || record.workspace === filters.workspaceFilter);
|
|
527
|
+
const groups = /* @__PURE__ */ new Map();
|
|
528
|
+
for (const record of candidates) {
|
|
529
|
+
const providerSessionId = typeof record.meta?.providerSessionId === "string" ? String(record.meta.providerSessionId).trim() : "";
|
|
530
|
+
if (!providerSessionId) continue;
|
|
531
|
+
const bindingKey = `${record.providerType}::${record.workspace}::${providerSessionId}`;
|
|
532
|
+
const bucket = groups.get(bindingKey) || [];
|
|
533
|
+
bucket.push(record);
|
|
534
|
+
groups.set(bindingKey, bucket);
|
|
535
|
+
}
|
|
536
|
+
const duplicateGroups = [];
|
|
537
|
+
const keptSessionIds = [];
|
|
538
|
+
const duplicateRecords = [];
|
|
539
|
+
for (const [bindingKey, records] of groups.entries()) {
|
|
540
|
+
if (records.length < 2) continue;
|
|
541
|
+
const sorted = [...records].sort((a, b) => compareDuplicateCandidates(a, b));
|
|
542
|
+
const kept = sorted[0];
|
|
543
|
+
const duplicates = sorted.slice(1);
|
|
544
|
+
const providerSessionId = typeof kept.meta?.providerSessionId === "string" ? String(kept.meta.providerSessionId) : "";
|
|
545
|
+
duplicateGroups.push({
|
|
546
|
+
bindingKey,
|
|
547
|
+
providerType: kept.providerType,
|
|
548
|
+
workspace: kept.workspace,
|
|
549
|
+
providerSessionId,
|
|
550
|
+
keptSessionId: kept.sessionId,
|
|
551
|
+
prunedSessionIds: duplicates.map((record) => record.sessionId)
|
|
552
|
+
});
|
|
553
|
+
keptSessionIds.push(kept.sessionId);
|
|
554
|
+
for (const duplicate of duplicates) {
|
|
555
|
+
duplicateRecords.push(duplicate);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return { duplicateGroups, keptSessionIds, duplicateRecords };
|
|
559
|
+
}
|
|
560
|
+
function buildPayloadFromRecord(record) {
|
|
561
|
+
return {
|
|
562
|
+
sessionId: record.sessionId,
|
|
563
|
+
runtimeKey: record.runtimeKey,
|
|
564
|
+
displayName: record.displayName,
|
|
565
|
+
providerType: record.providerType,
|
|
566
|
+
category: record.category,
|
|
567
|
+
workspace: record.workspace,
|
|
568
|
+
launchCommand: record.launchCommand,
|
|
569
|
+
cols: resolveSessionHostCols2(typeof record.meta?.sessionHostCols === "number" ? record.meta.sessionHostCols : void 0),
|
|
570
|
+
rows: resolveSessionHostRows2(typeof record.meta?.sessionHostRows === "number" ? record.meta.sessionHostRows : void 0),
|
|
571
|
+
meta: record.meta
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function buildRecoveredRecord(persisted) {
|
|
575
|
+
const wasLiveRuntime = !["stopped", "failed"].includes(persisted.record.lifecycle);
|
|
576
|
+
const hadAttachedClients = Array.isArray(persisted.record.attachedClients) && persisted.record.attachedClients.length > 0;
|
|
577
|
+
const hadWriteOwner = !!persisted.record.writeOwner;
|
|
578
|
+
const hadRecoveryInterest = hadAttachedClients || hadWriteOwner;
|
|
579
|
+
const recoveredRecord = {
|
|
580
|
+
...persisted.record,
|
|
581
|
+
attachedClients: [],
|
|
582
|
+
writeOwner: null,
|
|
583
|
+
lifecycle: wasLiveRuntime ? "stopped" : persisted.record.lifecycle,
|
|
584
|
+
lastActivityAt: Date.now(),
|
|
585
|
+
meta: {
|
|
586
|
+
...persisted.record.meta || {},
|
|
587
|
+
restoredFromStorage: true,
|
|
588
|
+
runtimeRecoveryState: wasLiveRuntime ? "orphan_snapshot" : "snapshot",
|
|
589
|
+
runtimeHadAttachedClientsAtCrash: hadAttachedClients,
|
|
590
|
+
runtimeHadWriteOwnerAtCrash: hadWriteOwner,
|
|
591
|
+
runtimeAutoResumeSkipped: wasLiveRuntime && hadRecoveryInterest
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
return { recoveredRecord, wasLiveRuntime, hadRecoveryInterest };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/session-protocol.ts
|
|
598
|
+
function getRequestSessionId(request) {
|
|
599
|
+
const payload = request.payload;
|
|
600
|
+
return typeof payload?.sessionId === "string" ? payload.sessionId : void 0;
|
|
601
|
+
}
|
|
602
|
+
function getRequestClientId(request) {
|
|
603
|
+
const payload = request.payload;
|
|
604
|
+
return typeof payload?.clientId === "string" ? payload.clientId : void 0;
|
|
605
|
+
}
|
|
606
|
+
function mergeRuntimeSnapshot(base, record, opts) {
|
|
607
|
+
const cols = typeof record?.meta?.sessionHostCols === "number" ? record.meta.sessionHostCols : 80;
|
|
608
|
+
const rows = typeof record?.meta?.sessionHostRows === "number" ? record.meta.sessionHostRows : 24;
|
|
609
|
+
if (typeof opts.sinceSeq === "number" || !opts.runtimeText) {
|
|
610
|
+
return {
|
|
611
|
+
...base,
|
|
612
|
+
cols,
|
|
613
|
+
rows
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
...base,
|
|
618
|
+
text: opts.runtimeText,
|
|
619
|
+
truncated: false,
|
|
620
|
+
cols,
|
|
621
|
+
rows
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
364
625
|
// src/server.ts
|
|
365
|
-
var SessionHostServer = class
|
|
366
|
-
static MAX_RECENT_DIAGNOSTICS = 200;
|
|
626
|
+
var SessionHostServer = class extends EventEmitter {
|
|
367
627
|
endpoint;
|
|
368
628
|
registry = new SessionHostRegistry();
|
|
369
629
|
runtimes = /* @__PURE__ */ new Map();
|
|
@@ -510,6 +770,8 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
510
770
|
}
|
|
511
771
|
case "get_snapshot":
|
|
512
772
|
return { success: true, result: this.getSnapshot(request.payload.sessionId, request.payload.sinceSeq) };
|
|
773
|
+
case "get_terminal_snapshot":
|
|
774
|
+
return { success: true, result: this.requireRuntime(request.payload.sessionId).getTerminalSnapshot() };
|
|
513
775
|
case "get_host_diagnostics":
|
|
514
776
|
return { success: true, result: this.getHostDiagnostics(request.payload) };
|
|
515
777
|
case "clear_session_buffer": {
|
|
@@ -610,7 +872,7 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
610
872
|
if (this.runtimes.has(request.payload.sessionId)) {
|
|
611
873
|
return { success: true, result: existing };
|
|
612
874
|
}
|
|
613
|
-
const resumed = this.startRuntime(existing,
|
|
875
|
+
const resumed = this.startRuntime(existing, buildPayloadFromRecord(existing), "session_resumed");
|
|
614
876
|
this.recordRuntimeTransition(request.payload.sessionId, "resume_session", resumed.lifecycle, void 0, true);
|
|
615
877
|
return { success: true, result: resumed };
|
|
616
878
|
}
|
|
@@ -692,7 +954,7 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
692
954
|
sessions.add(sessionId);
|
|
693
955
|
}
|
|
694
956
|
async handleIncomingRequest(socket, envelope) {
|
|
695
|
-
const sessionId =
|
|
957
|
+
const sessionId = getRequestSessionId(envelope.request);
|
|
696
958
|
if (sessionId && (envelope.request.type === "create_session" || envelope.request.type === "attach_session")) {
|
|
697
959
|
this.subscribeSocketToSession(socket, sessionId);
|
|
698
960
|
}
|
|
@@ -706,8 +968,8 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
706
968
|
timestamp: startedAt,
|
|
707
969
|
requestId: envelope.requestId,
|
|
708
970
|
type: envelope.request.type,
|
|
709
|
-
sessionId:
|
|
710
|
-
clientId:
|
|
971
|
+
sessionId: getRequestSessionId(envelope.request),
|
|
972
|
+
clientId: getRequestClientId(envelope.request),
|
|
711
973
|
success: response.success,
|
|
712
974
|
durationMs: Math.max(0, Date.now() - startedAt),
|
|
713
975
|
error: response.success ? void 0 : response.error
|
|
@@ -750,79 +1012,24 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
750
1012
|
const record = this.registry.getSession(sessionId);
|
|
751
1013
|
if (!record) return;
|
|
752
1014
|
const snapshot = this.getSnapshot(sessionId);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (recoveryState === "auto_resumed") return "restored after restart";
|
|
759
|
-
if (recoveryState === "resume_failed") return "restore failed";
|
|
760
|
-
if (recoveryState === "host_restart_interrupted") return "host restart interrupted";
|
|
761
|
-
if (recoveryState === "orphan_snapshot") return "snapshot recovered";
|
|
762
|
-
return recoveryState.replace(/_/g, " ");
|
|
763
|
-
}
|
|
764
|
-
getSessionSurfaceKind(record) {
|
|
765
|
-
if (["starting", "running", "stopping", "interrupted"].includes(record.lifecycle)) {
|
|
766
|
-
return "live_runtime";
|
|
767
|
-
}
|
|
768
|
-
if ((record.lifecycle === "stopped" || record.lifecycle === "failed") && (record.meta?.restoredFromStorage === true || this.getSessionHostRecoveryLabel(record))) {
|
|
769
|
-
return "recovery_snapshot";
|
|
1015
|
+
try {
|
|
1016
|
+
this.storage.save(record, snapshot);
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
const code = typeof error?.code === "string" ? error.code : "persist_failed";
|
|
1019
|
+
console.error(`[session-host] Persist failed for ${sessionId}: ${code}: ${error?.message || error}`);
|
|
770
1020
|
}
|
|
771
|
-
return "inactive_record";
|
|
772
|
-
}
|
|
773
|
-
annotateSessionSurface(record) {
|
|
774
|
-
return {
|
|
775
|
-
...record,
|
|
776
|
-
surfaceKind: this.getSessionSurfaceKind(record)
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
sanitizeDiagnosticsRecord(record) {
|
|
780
|
-
return {
|
|
781
|
-
...record,
|
|
782
|
-
launchCommand: {
|
|
783
|
-
command: record.launchCommand.command,
|
|
784
|
-
args: Array.isArray(record.launchCommand.args) ? [...record.launchCommand.args] : []
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
1021
|
}
|
|
788
1022
|
getHostDiagnostics(payload) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const liveRuntimes = allSessions?.filter((record) => record.surfaceKind === "live_runtime");
|
|
792
|
-
const recoverySnapshots = allSessions?.filter((record) => record.surfaceKind === "recovery_snapshot").slice(0, limit);
|
|
793
|
-
const inactiveRecords = allSessions?.filter((record) => record.surfaceKind === "inactive_record").slice(0, limit);
|
|
794
|
-
const sessions = allSessions ? [
|
|
795
|
-
...liveRuntimes || [],
|
|
796
|
-
...recoverySnapshots || [],
|
|
797
|
-
...inactiveRecords || []
|
|
798
|
-
] : void 0;
|
|
799
|
-
return {
|
|
1023
|
+
return buildHostDiagnostics({
|
|
1024
|
+
payload,
|
|
800
1025
|
hostStartedAt: this.startedAt,
|
|
801
|
-
|
|
1026
|
+
endpointPath: this.endpoint.path,
|
|
802
1027
|
runtimeCount: this.runtimes.size,
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
recentLogs: this.recentLogs.slice(-limit),
|
|
809
|
-
recentRequests: this.recentRequests.slice(-limit),
|
|
810
|
-
recentTransitions: this.recentTransitions.slice(-limit)
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
getRequestSessionId(request) {
|
|
814
|
-
const payload = request.payload;
|
|
815
|
-
return typeof payload?.sessionId === "string" ? payload.sessionId : void 0;
|
|
816
|
-
}
|
|
817
|
-
getRequestClientId(request) {
|
|
818
|
-
const payload = request.payload;
|
|
819
|
-
return typeof payload?.clientId === "string" ? payload.clientId : void 0;
|
|
820
|
-
}
|
|
821
|
-
pushRecent(bucket, entry) {
|
|
822
|
-
bucket.push(entry);
|
|
823
|
-
if (bucket.length > _SessionHostServer.MAX_RECENT_DIAGNOSTICS) {
|
|
824
|
-
bucket.splice(0, bucket.length - _SessionHostServer.MAX_RECENT_DIAGNOSTICS);
|
|
825
|
-
}
|
|
1028
|
+
sessions: this.registry.listSessions(),
|
|
1029
|
+
recentLogs: this.recentLogs,
|
|
1030
|
+
recentRequests: this.recentRequests,
|
|
1031
|
+
recentTransitions: this.recentTransitions
|
|
1032
|
+
});
|
|
826
1033
|
}
|
|
827
1034
|
recordHostLog(level, message, sessionId, data) {
|
|
828
1035
|
const entry = {
|
|
@@ -832,12 +1039,12 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
832
1039
|
sessionId,
|
|
833
1040
|
data
|
|
834
1041
|
};
|
|
835
|
-
|
|
1042
|
+
pushRecent(this.recentLogs, entry);
|
|
836
1043
|
this.emitEvent({ type: "host_log", entry });
|
|
837
1044
|
this.emit("log", `[${level}] ${message}`);
|
|
838
1045
|
}
|
|
839
1046
|
recordRequestTrace(trace) {
|
|
840
|
-
|
|
1047
|
+
pushRecent(this.recentRequests, trace);
|
|
841
1048
|
this.emitEvent({ type: "request_trace", trace });
|
|
842
1049
|
if (!trace.success) {
|
|
843
1050
|
this.recordHostLog(
|
|
@@ -907,7 +1114,7 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
907
1114
|
success,
|
|
908
1115
|
error
|
|
909
1116
|
};
|
|
910
|
-
|
|
1117
|
+
pushRecent(this.recentTransitions, transition);
|
|
911
1118
|
this.emitEvent({ type: "runtime_transition", transition });
|
|
912
1119
|
}
|
|
913
1120
|
waitForRuntimeExit(sessionId, timeoutMs = 5e3) {
|
|
@@ -943,29 +1150,8 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
943
1150
|
getSnapshot(sessionId, sinceSeq) {
|
|
944
1151
|
const snapshot = this.registry.getSnapshot(sessionId, sinceSeq);
|
|
945
1152
|
const record = this.registry.getSession(sessionId);
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
...snapshot,
|
|
949
|
-
cols: typeof record?.meta?.sessionHostCols === "number" ? record.meta.sessionHostCols : 80,
|
|
950
|
-
rows: typeof record?.meta?.sessionHostRows === "number" ? record.meta.sessionHostRows : 24
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
const runtime = this.runtimes.get(sessionId);
|
|
954
|
-
const runtimeText = runtime?.getSnapshotText?.() || "";
|
|
955
|
-
if (!runtimeText) {
|
|
956
|
-
return {
|
|
957
|
-
...snapshot,
|
|
958
|
-
cols: typeof record?.meta?.sessionHostCols === "number" ? record.meta.sessionHostCols : 80,
|
|
959
|
-
rows: typeof record?.meta?.sessionHostRows === "number" ? record.meta.sessionHostRows : 24
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
return {
|
|
963
|
-
...snapshot,
|
|
964
|
-
text: runtimeText,
|
|
965
|
-
truncated: false,
|
|
966
|
-
cols: typeof record?.meta?.sessionHostCols === "number" ? record.meta.sessionHostCols : 80,
|
|
967
|
-
rows: typeof record?.meta?.sessionHostRows === "number" ? record.meta.sessionHostRows : 24
|
|
968
|
-
};
|
|
1153
|
+
const runtimeText = typeof sinceSeq === "number" ? "" : this.runtimes.get(sessionId)?.getSnapshotText?.() || "";
|
|
1154
|
+
return mergeRuntimeSnapshot(snapshot, record, { sinceSeq, runtimeText });
|
|
969
1155
|
}
|
|
970
1156
|
flushAllPersistence() {
|
|
971
1157
|
for (const sessionId of this.runtimes.keys()) {
|
|
@@ -988,7 +1174,7 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
988
1174
|
await this.waitForRuntimeExit(sessionId);
|
|
989
1175
|
}
|
|
990
1176
|
const latest = this.registry.getSession(sessionId) || existing;
|
|
991
|
-
const restarted = this.startRuntime(latest,
|
|
1177
|
+
const restarted = this.startRuntime(latest, buildPayloadFromRecord(latest), "session_resumed");
|
|
992
1178
|
this.recordRuntimeTransition(sessionId, "restart_completed", restarted.lifecycle, void 0, true);
|
|
993
1179
|
return restarted;
|
|
994
1180
|
}
|
|
@@ -996,36 +1182,13 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
996
1182
|
const providerFilter = typeof payload?.providerType === "string" ? payload.providerType.trim() : "";
|
|
997
1183
|
const workspaceFilter = typeof payload?.workspace === "string" ? payload.workspace.trim() : "";
|
|
998
1184
|
const dryRun = payload?.dryRun === true;
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
if (!providerSessionId) continue;
|
|
1004
|
-
const bindingKey = `${record.providerType}::${record.workspace}::${providerSessionId}`;
|
|
1005
|
-
const bucket = groups.get(bindingKey) || [];
|
|
1006
|
-
bucket.push(record);
|
|
1007
|
-
groups.set(bindingKey, bucket);
|
|
1008
|
-
}
|
|
1009
|
-
const duplicateGroups = [];
|
|
1010
|
-
const keptSessionIds = [];
|
|
1185
|
+
const { duplicateGroups, keptSessionIds, duplicateRecords } = planDuplicatePrune(
|
|
1186
|
+
this.registry.listSessions(),
|
|
1187
|
+
{ providerFilter, workspaceFilter }
|
|
1188
|
+
);
|
|
1011
1189
|
const prunedSessionIds = [];
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const sorted = [...records].sort((a, b) => this.compareDuplicateCandidates(a, b));
|
|
1015
|
-
const kept = sorted[0];
|
|
1016
|
-
const duplicates = sorted.slice(1);
|
|
1017
|
-
const providerSessionId = typeof kept.meta?.providerSessionId === "string" ? String(kept.meta.providerSessionId) : "";
|
|
1018
|
-
duplicateGroups.push({
|
|
1019
|
-
bindingKey,
|
|
1020
|
-
providerType: kept.providerType,
|
|
1021
|
-
workspace: kept.workspace,
|
|
1022
|
-
providerSessionId,
|
|
1023
|
-
keptSessionId: kept.sessionId,
|
|
1024
|
-
prunedSessionIds: duplicates.map((record) => record.sessionId)
|
|
1025
|
-
});
|
|
1026
|
-
keptSessionIds.push(kept.sessionId);
|
|
1027
|
-
if (dryRun) continue;
|
|
1028
|
-
for (const duplicate of duplicates) {
|
|
1190
|
+
if (!dryRun) {
|
|
1191
|
+
for (const duplicate of duplicateRecords) {
|
|
1029
1192
|
await this.pruneDuplicateRuntime(duplicate);
|
|
1030
1193
|
prunedSessionIds.push(duplicate.sessionId);
|
|
1031
1194
|
}
|
|
@@ -1053,25 +1216,7 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
1053
1216
|
const states = this.storage.loadAll();
|
|
1054
1217
|
let skippedAutoResumeSessions = 0;
|
|
1055
1218
|
for (const persisted of states) {
|
|
1056
|
-
const wasLiveRuntime =
|
|
1057
|
-
const hadAttachedClients = Array.isArray(persisted.record.attachedClients) && persisted.record.attachedClients.length > 0;
|
|
1058
|
-
const hadWriteOwner = !!persisted.record.writeOwner;
|
|
1059
|
-
const hadRecoveryInterest = hadAttachedClients || hadWriteOwner;
|
|
1060
|
-
const recoveredRecord = {
|
|
1061
|
-
...persisted.record,
|
|
1062
|
-
attachedClients: [],
|
|
1063
|
-
writeOwner: null,
|
|
1064
|
-
lifecycle: wasLiveRuntime ? "stopped" : persisted.record.lifecycle,
|
|
1065
|
-
lastActivityAt: Date.now(),
|
|
1066
|
-
meta: {
|
|
1067
|
-
...persisted.record.meta || {},
|
|
1068
|
-
restoredFromStorage: true,
|
|
1069
|
-
runtimeRecoveryState: wasLiveRuntime ? "orphan_snapshot" : "snapshot",
|
|
1070
|
-
runtimeHadAttachedClientsAtCrash: hadAttachedClients,
|
|
1071
|
-
runtimeHadWriteOwnerAtCrash: hadWriteOwner,
|
|
1072
|
-
runtimeAutoResumeSkipped: wasLiveRuntime && hadRecoveryInterest
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1219
|
+
const { recoveredRecord, wasLiveRuntime, hadRecoveryInterest } = buildRecoveredRecord(persisted);
|
|
1075
1220
|
this.registry.restoreSession(recoveredRecord, persisted.snapshot);
|
|
1076
1221
|
this.storage.save(recoveredRecord, persisted.snapshot);
|
|
1077
1222
|
if (wasLiveRuntime && hadRecoveryInterest) {
|
|
@@ -1082,26 +1227,6 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
1082
1227
|
this.recordHostLog("warn", `session host restored ${skippedAutoResumeSessions} live runtime snapshot(s) without auto-resume`);
|
|
1083
1228
|
}
|
|
1084
1229
|
}
|
|
1085
|
-
compareDuplicateCandidates(a, b) {
|
|
1086
|
-
const score = (record) => {
|
|
1087
|
-
const lifecycleScore = record.lifecycle === "running" ? 4 : record.lifecycle === "starting" ? 3 : record.lifecycle === "stopping" ? 2 : record.lifecycle === "interrupted" ? 1 : 0;
|
|
1088
|
-
return [
|
|
1089
|
-
lifecycleScore,
|
|
1090
|
-
record.writeOwner ? 1 : 0,
|
|
1091
|
-
Array.isArray(record.attachedClients) ? record.attachedClients.length : 0,
|
|
1092
|
-
record.lastActivityAt || 0,
|
|
1093
|
-
record.startedAt || 0,
|
|
1094
|
-
record.createdAt || 0
|
|
1095
|
-
];
|
|
1096
|
-
};
|
|
1097
|
-
const aScore = score(a);
|
|
1098
|
-
const bScore = score(b);
|
|
1099
|
-
for (let i = 0; i < aScore.length; i += 1) {
|
|
1100
|
-
if (aScore[i] === bScore[i]) continue;
|
|
1101
|
-
return bScore[i] - aScore[i];
|
|
1102
|
-
}
|
|
1103
|
-
return 0;
|
|
1104
|
-
}
|
|
1105
1230
|
async pruneDuplicateRuntime(record) {
|
|
1106
1231
|
const providerSessionId = typeof record.meta?.providerSessionId === "string" ? String(record.meta.providerSessionId) : void 0;
|
|
1107
1232
|
this.recordRuntimeTransition(
|
|
@@ -1122,20 +1247,6 @@ var SessionHostServer = class _SessionHostServer extends EventEmitter {
|
|
|
1122
1247
|
this.registry.deleteSession(record.sessionId);
|
|
1123
1248
|
this.storage.remove(record.sessionId);
|
|
1124
1249
|
}
|
|
1125
|
-
buildPayloadFromRecord(record) {
|
|
1126
|
-
return {
|
|
1127
|
-
sessionId: record.sessionId,
|
|
1128
|
-
runtimeKey: record.runtimeKey,
|
|
1129
|
-
displayName: record.displayName,
|
|
1130
|
-
providerType: record.providerType,
|
|
1131
|
-
category: record.category,
|
|
1132
|
-
workspace: record.workspace,
|
|
1133
|
-
launchCommand: record.launchCommand,
|
|
1134
|
-
cols: resolveSessionHostCols2(typeof record.meta?.sessionHostCols === "number" ? record.meta.sessionHostCols : void 0),
|
|
1135
|
-
rows: resolveSessionHostRows2(typeof record.meta?.sessionHostRows === "number" ? record.meta.sessionHostRows : void 0),
|
|
1136
|
-
meta: record.meta
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
1250
|
startRuntime(record, payload, startEventType) {
|
|
1140
1251
|
const runtime = new PtySessionRuntime({
|
|
1141
1252
|
sessionId: record.sessionId,
|