@jhizzard/termdeck 1.10.0 → 1.10.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/package.json +1 -1
- package/packages/server/src/index.js +74 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.1",
|
|
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"
|
|
@@ -2350,7 +2350,7 @@ function createServer(config) {
|
|
|
2350
2350
|
// Body: { text: string, source?: 'user' | 'reply' | 'ai', fromSessionId?: string }
|
|
2351
2351
|
// Used by T1.3 reply button and any agent-to-agent routing.
|
|
2352
2352
|
const inputRateLimit = new Map(); // sessionId -> { windowStart, count }
|
|
2353
|
-
app.post('/api/sessions/:id/input', (req, res) => {
|
|
2353
|
+
app.post('/api/sessions/:id/input', async (req, res) => {
|
|
2354
2354
|
const session = sessions.get(req.params.id);
|
|
2355
2355
|
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
2356
2356
|
// Sprint 72 T2 — web-chat panels have no PTY. Route the inject to the
|
|
@@ -2428,7 +2428,7 @@ function createServer(config) {
|
|
|
2428
2428
|
});
|
|
2429
2429
|
}
|
|
2430
2430
|
|
|
2431
|
-
const { text, source, fromSessionId } = req.body || {};
|
|
2431
|
+
const { text, source, fromSessionId, submit } = req.body || {};
|
|
2432
2432
|
if (typeof text !== 'string') {
|
|
2433
2433
|
return res.status(400).json({ error: 'Missing text' });
|
|
2434
2434
|
}
|
|
@@ -2449,11 +2449,65 @@ function createServer(config) {
|
|
|
2449
2449
|
// CRLF normalize: zsh/readline want \r for Enter
|
|
2450
2450
|
const normalized = text.replace(/\r\n?/g, '\r').replace(/\n/g, '\r');
|
|
2451
2451
|
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2452
|
+
// Sprint 76.1 (Bug B — Brad's "POST /input returns 200 but never submits"):
|
|
2453
|
+
// optional server-sequenced submit. The documented two-stage inject (paste
|
|
2454
|
+
// body, ~400ms settle, then a lone `\r` as a SECOND POST) is a CALLER-side
|
|
2455
|
+
// race — when the bracketed-paste close marker and the `\r` ride one PTY
|
|
2456
|
+
// write the foreground TUI absorbs the `\r` as paste content, so under
|
|
2457
|
+
// concurrent / mid-turn injects the submit is silently swallowed and the
|
|
2458
|
+
// text sits unsubmitted (a 200 here only ever meant "bytes written", not
|
|
2459
|
+
// "became a turn"). With `submit:true` the SERVER owns the ordering: write
|
|
2460
|
+
// the body, await the settle, then write a lone `\r` as its OWN PTY write —
|
|
2461
|
+
// the OS chunk-boundary race is impossible because the two writes are
|
|
2462
|
+
// distinct with a server-held gap between them. Mirrors the web-chat arm's
|
|
2463
|
+
// server-side assembly above. Absent/falsy `submit` ⇒ byte-identical to the
|
|
2464
|
+
// pre-76.1 pass-through (existing two-stage callers are untouched).
|
|
2465
|
+
let bytesWritten;
|
|
2466
|
+
let submitted;
|
|
2467
|
+
if (submit === true) {
|
|
2468
|
+
const rawSettle = process.env.TERMDECK_INPUT_SUBMIT_SETTLE_MS;
|
|
2469
|
+
const parsedSettle = Number(rawSettle);
|
|
2470
|
+
const settleMs = (rawSettle !== undefined && rawSettle !== ''
|
|
2471
|
+
&& Number.isFinite(parsedSettle) && parsedSettle >= 0) ? parsedSettle : 400;
|
|
2472
|
+
// Strip any caller-supplied trailing CR so the body never self-submits;
|
|
2473
|
+
// the lone `\r` below is the one and only submit keystroke.
|
|
2474
|
+
const body = normalized.replace(/\r+$/, '');
|
|
2475
|
+
try {
|
|
2476
|
+
if (body) { session.pty.write(body); session.trackInput(body); }
|
|
2477
|
+
await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
2478
|
+
// The PTY can be torn down DURING the server-held settle (panel closed
|
|
2479
|
+
// mid-submit). Re-validate before the submit keystroke and return a
|
|
2480
|
+
// clean 410 (the route's exited-panel convention) instead of leaning on
|
|
2481
|
+
// the catch below to surface a generic 500 — so a caller can tell
|
|
2482
|
+
// "panel closed mid-submit, don't retry" from a real write error. The
|
|
2483
|
+
// try/catch remains the backstop for any unexpected write throw.
|
|
2484
|
+
if (session.meta.status === 'exited' || !session.pty) {
|
|
2485
|
+
const msg = `Panel ${req.params.id} exited during submit settle`;
|
|
2486
|
+
return res.status(410).json({
|
|
2487
|
+
ok: false, code: 'panel_exited', error: msg, message: msg,
|
|
2488
|
+
exitCode: session.meta.exitCode ?? null,
|
|
2489
|
+
exitedAt: session.meta.exitedAt || null,
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
session.pty.write('\r');
|
|
2493
|
+
session.trackInput('\r');
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
return res.status(500).json({ error: err.message });
|
|
2496
|
+
}
|
|
2497
|
+
bytesWritten = body.length + 1;
|
|
2498
|
+
// The server completed the atomic submit sequence. This is the mechanical
|
|
2499
|
+
// guarantee that removes the caller-side `\r`-swallow race; `status` below
|
|
2500
|
+
// is the best-effort "did the TUI start the turn" signal (adapter-derived,
|
|
2501
|
+
// so it may lag a beat on a freshly-idle panel).
|
|
2502
|
+
submitted = true;
|
|
2503
|
+
} else {
|
|
2504
|
+
try {
|
|
2505
|
+
session.pty.write(normalized);
|
|
2506
|
+
session.trackInput(normalized);
|
|
2507
|
+
} catch (err) {
|
|
2508
|
+
return res.status(500).json({ error: err.message });
|
|
2509
|
+
}
|
|
2510
|
+
bytesWritten = normalized.length;
|
|
2457
2511
|
}
|
|
2458
2512
|
|
|
2459
2513
|
session.meta.replyCount = (session.meta.replyCount || 0) + 1;
|
|
@@ -2471,7 +2525,19 @@ function createServer(config) {
|
|
|
2471
2525
|
}
|
|
2472
2526
|
}
|
|
2473
2527
|
|
|
2474
|
-
|
|
2528
|
+
// submit-confirm: callers (e.g. Brad's tg-poll re-inject) read
|
|
2529
|
+
// `status` / `inputBufferLength` to detect a stuck inject and retry
|
|
2530
|
+
// deterministically instead of separately polling GET /buffer. `submitted`
|
|
2531
|
+
// is present only when `submit:true` was requested.
|
|
2532
|
+
const responseBody = {
|
|
2533
|
+
ok: true,
|
|
2534
|
+
bytes: bytesWritten,
|
|
2535
|
+
replyCount: session.meta.replyCount,
|
|
2536
|
+
status: session.meta.status,
|
|
2537
|
+
inputBufferLength: (session._inputBuffer || '').length,
|
|
2538
|
+
};
|
|
2539
|
+
if (submit === true) responseBody.submitted = submitted;
|
|
2540
|
+
res.json(responseBody);
|
|
2475
2541
|
});
|
|
2476
2542
|
|
|
2477
2543
|
// POST /api/sessions/:id/upload?name=<filename> - File drop / clipboard image paste
|