@jhizzard/termdeck 1.1.0 → 1.2.0
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/package.json +1 -1
- package/packages/cli/src/stack.js +20 -3
- package/packages/client/public/app.js +57 -0
- package/packages/server/src/agent-adapters/gemini.js +14 -8
- package/packages/server/src/health.js +354 -110
- package/packages/server/src/index.js +138 -20
- package/packages/server/src/preflight.js +7 -1
- package/packages/server/src/setup/migrations.js +27 -1
- package/packages/server/src/setup/mnestra-migrations/021_project_tag_canonicalize_claimguard.sql +175 -0
- package/packages/server/src/setup/mnestra-migrations/022_source_agent_backfill.sql +182 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
|
|
5
5
|
"bin": {
|
|
6
6
|
"termdeck": "./packages/cli/src/index.js"
|
|
@@ -410,7 +410,14 @@ async function checkRumen() {
|
|
|
410
410
|
}
|
|
411
411
|
const pool = new pg.Pool({ connectionString: dbUrl, max: 1, connectionTimeoutMillis: 5000 });
|
|
412
412
|
try {
|
|
413
|
-
|
|
413
|
+
// Sprint 63 T3 §3.1 — `rumen_jobs` has `started_at` (migration 001), NOT
|
|
414
|
+
// `created_at`. Pre-Sprint-63 this probed `created_at` and threw a
|
|
415
|
+
// generic WARN that doctor's same-DB check did not (Sprint 35 doctor fix
|
|
416
|
+
// landed RUMEN_TIME_COL.rumen_jobs='started_at' but never propagated
|
|
417
|
+
// here). Brad reproduced on r730 2026-05-11; doctor 23/23 GREEN while
|
|
418
|
+
// launcher Step 3 emitted `WARN (query failed: column "created_at"
|
|
419
|
+
// does not exist)`. Aligned both probes to the same column.
|
|
420
|
+
const r = await pool.query("SELECT to_char(NOW() - MAX(started_at), 'HH24:MI:SS') AS ago FROM rumen_jobs");
|
|
414
421
|
const ago = r.rows[0] && r.rows[0].ago;
|
|
415
422
|
if (ago) {
|
|
416
423
|
stepLine('3/4', 'Checking Rumen', 'OK', `(last job ${ago} ago)`);
|
|
@@ -419,10 +426,20 @@ async function checkRumen() {
|
|
|
419
426
|
stepLine('3/4', 'Checking Rumen', 'WARN', '(no jobs yet — try termdeck init --rumen)');
|
|
420
427
|
return { ago: null };
|
|
421
428
|
} catch (err) {
|
|
422
|
-
|
|
429
|
+
const msg = String(err && err.message ? err.message : err);
|
|
430
|
+
if (/relation .*rumen_jobs.* does not exist/i.test(msg)) {
|
|
423
431
|
stepLine('3/4', 'Checking Rumen', 'SKIP', '(rumen_jobs table not present — run termdeck init --rumen)');
|
|
424
432
|
} else {
|
|
425
|
-
|
|
433
|
+
const colMatch = msg.match(/column "([^"]+)" does not exist/i);
|
|
434
|
+
if (colMatch) {
|
|
435
|
+
// Schema drift — rumen_jobs is missing the column we queried. Naming
|
|
436
|
+
// the column + remediation beats a bare `query failed` that operators
|
|
437
|
+
// learn to filter out (Brad's r730, 2026-05-11).
|
|
438
|
+
stepLine('3/4', 'Checking Rumen', 'WARN',
|
|
439
|
+
`(rumen_jobs.${colMatch[1]} column missing — re-run \`termdeck init --rumen\` to apply migration 001)`);
|
|
440
|
+
} else {
|
|
441
|
+
stepLine('3/4', 'Checking Rumen', 'WARN', `(query failed: ${err.message})`);
|
|
442
|
+
}
|
|
426
443
|
}
|
|
427
444
|
return { ago: null };
|
|
428
445
|
} finally {
|
|
@@ -129,6 +129,12 @@
|
|
|
129
129
|
// Sprint 37 T1: orchestrator Guide right-rail. Lazy — fetches the doc
|
|
130
130
|
// on first expand to keep page load light.
|
|
131
131
|
setupGuideRail();
|
|
132
|
+
|
|
133
|
+
// 2026-05-08 hotfix: document-level capture-phase image-paste handler.
|
|
134
|
+
// Intercepts Cmd+V image data before xterm-helper-textarea consumes it
|
|
135
|
+
// (xterm reads only text/plain, drops images silently). See comment on
|
|
136
|
+
// setupGlobalImagePaste() near uploadFilesAndType() for details.
|
|
137
|
+
setupGlobalImagePaste();
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
// ===== Drag/drop reorder of PTY panels (Sprint 42 T4) =====
|
|
@@ -275,6 +281,57 @@
|
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
// Document-level capture-phase image paste handler.
|
|
285
|
+
//
|
|
286
|
+
// The Sprint 59 per-panel `paste` listener at line 218 is bubble-phase, but
|
|
287
|
+
// xterm.js@5.5.0's hidden helper-textarea has its own `paste` handler that
|
|
288
|
+
// reads only `clipboardData.getData('text/plain')`. Image data lives in
|
|
289
|
+
// `clipboardData.items` with `kind: 'file'` and never reaches xterm's
|
|
290
|
+
// text path — and the panel-level bubble-phase handler runs after xterm's,
|
|
291
|
+
// by which point xterm has already returned (silently dropping the image).
|
|
292
|
+
// Net: pre-fix, Cmd+V'ing a screenshot into a focused TermDeck panel did
|
|
293
|
+
// nothing. Joshua reported this on 2026-05-08 (post-v1.1.0 upgrade).
|
|
294
|
+
//
|
|
295
|
+
// Fix: document-level listener with `{capture: true}` runs in capture
|
|
296
|
+
// phase BEFORE the event reaches xterm-helper-textarea. If the event
|
|
297
|
+
// target is inside a `.term-panel` AND the clipboard contains image
|
|
298
|
+
// files, we preventDefault + stopPropagation (so xterm + the bubble-phase
|
|
299
|
+
// panel handler don't see it) and route through `uploadFilesAndType`.
|
|
300
|
+
// For text paste (no image files) we let the event continue normally.
|
|
301
|
+
//
|
|
302
|
+
// Idempotent: setupGlobalImagePaste() is called once from init().
|
|
303
|
+
let _globalImagePasteSetup = false;
|
|
304
|
+
function setupGlobalImagePaste() {
|
|
305
|
+
if (_globalImagePasteSetup) return;
|
|
306
|
+
_globalImagePasteSetup = true;
|
|
307
|
+
document.addEventListener('paste', (e) => {
|
|
308
|
+
const target = e.target;
|
|
309
|
+
if (!(target instanceof Element)) return;
|
|
310
|
+
const panel = target.closest('.term-panel');
|
|
311
|
+
if (!panel) return;
|
|
312
|
+
const items = (e.clipboardData && e.clipboardData.items) || [];
|
|
313
|
+
const blobs = [];
|
|
314
|
+
for (const item of items) {
|
|
315
|
+
if (item.kind === 'file' && item.type && item.type.startsWith('image/')) {
|
|
316
|
+
const blob = item.getAsFile();
|
|
317
|
+
if (blob) blobs.push(blob);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (blobs.length === 0) return;
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
e.stopPropagation();
|
|
323
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
324
|
+
const named = blobs.map((b, i) => {
|
|
325
|
+
const ext = (b.type.split('/')[1] || 'png').replace(/[^a-z0-9]/gi, '');
|
|
326
|
+
const name = b.name && b.name.length > 0
|
|
327
|
+
? b.name
|
|
328
|
+
: `pasted-${ts}${blobs.length > 1 ? '-' + i : ''}.${ext}`;
|
|
329
|
+
return new File([b], name, { type: b.type });
|
|
330
|
+
});
|
|
331
|
+
uploadFilesAndType(panel, named);
|
|
332
|
+
}, { capture: true });
|
|
333
|
+
}
|
|
334
|
+
|
|
278
335
|
// ===== Create Terminal Panel =====
|
|
279
336
|
function createTerminalPanel(sessionData) {
|
|
280
337
|
const id = sessionData.id;
|
|
@@ -50,13 +50,18 @@ function statusFor(data) {
|
|
|
50
50
|
// resolveTranscriptPath — Sprint 50 T1.
|
|
51
51
|
//
|
|
52
52
|
// Gemini CLI persists chats at
|
|
53
|
-
// ~/.gemini/tmp/<basename(cwd)>/chats/session-<ISO-ts>-<short-id>.json
|
|
54
|
-
// (single-JSON-object shape that matches parseGeminiJson
|
|
55
|
-
// 2026-05-02 substrate probe
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
53
|
+
// ~/.gemini/tmp/<basename(cwd)>/chats/session-<ISO-ts>-<short-id>.{json,jsonl}
|
|
54
|
+
// (single-JSON-object shape that matches parseGeminiJson for the .json
|
|
55
|
+
// flavor, verified 2026-05-02 substrate probe; .jsonl flavor introduced
|
|
56
|
+
// some time between 2026-05-02 and 2026-05-08, surfaced by Sprint 63 T2
|
|
57
|
+
// acceptance — see docs/sprint-63-wave-2/EXIT-CAPTURE-VERIFICATION.md
|
|
58
|
+
// Finding #2. The extension filter accepts both shapes; downstream parser
|
|
59
|
+
// handling of JSONL deltas is a Sprint 64 candidate). Pick the most
|
|
60
|
+
// recently modified file whose mtime is at-or-after
|
|
61
|
+
// `session.meta.createdAt`. Falls back to walking every project directory
|
|
62
|
+
// under `~/.gemini/tmp/*/chats/` if the basename heuristic produces no
|
|
63
|
+
// candidate (e.g., Gemini renormalized the project name to deduplicate
|
|
64
|
+
// against an existing one).
|
|
60
65
|
// ──────────────────────────────────────────────────────────────────────────
|
|
61
66
|
|
|
62
67
|
async function resolveTranscriptPath(session) {
|
|
@@ -83,7 +88,8 @@ async function resolveTranscriptPath(session) {
|
|
|
83
88
|
let entries;
|
|
84
89
|
try { entries = fs.readdirSync(dir); } catch (_) { return; }
|
|
85
90
|
for (const name of entries) {
|
|
86
|
-
if (!name.startsWith('session-')
|
|
91
|
+
if (!name.startsWith('session-')) continue;
|
|
92
|
+
if (!name.endsWith('.json') && !name.endsWith('.jsonl')) continue;
|
|
87
93
|
const full = path.join(dir, name);
|
|
88
94
|
let st;
|
|
89
95
|
try { st = fs.statSync(full); } catch (_) { continue; }
|