@dmsdc-ai/aigentry-telepty 0.5.6 → 0.5.8
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/daemon.js +118 -2
- package/package.json +4 -4
- package/session-state.js +8 -0
- package/terminal-backend.js +0 -19
package/daemon.js
CHANGED
|
@@ -139,6 +139,13 @@ app.use(express.json());
|
|
|
139
139
|
// Peer allowlist: comma-separated IPs/CIDRs in TELEPTY_PEER_ALLOWLIST env
|
|
140
140
|
const PEER_ALLOWLIST = (process.env.TELEPTY_PEER_ALLOWLIST || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
141
141
|
|
|
142
|
+
// #533 Phase 2 — peer-lane inject guardrail. The orchestrator sid(s) define the
|
|
143
|
+
// ORCH LANE (always allowed). Space-separated; default matches aigentry-orchestrator
|
|
144
|
+
// bin/ask.sh so both ends agree on "who is the orchestrator" from one config. If this
|
|
145
|
+
// resolves empty the guardrail fails OPEN (see classifyPeerLaneInject).
|
|
146
|
+
const ORCHESTRATOR_SIDS = (process.env.AIGENTRY_ORCHESTRATOR_SIDS || 'orchestrator aigentry-orchestrator-claude')
|
|
147
|
+
.split(/\s+/).map(s => s.trim()).filter(Boolean);
|
|
148
|
+
|
|
142
149
|
// Cross-machine bus relay: forward bus events to peer daemons
|
|
143
150
|
const relayToPeers = createPeerRelay({
|
|
144
151
|
relayPeers: relayPeersFromEnv(process.env),
|
|
@@ -368,6 +375,68 @@ function respondWithError(res, httpStatus, code, error, extra = {}) {
|
|
|
368
375
|
return res.status(httpStatus).json(buildErrorBody(code, error, extra));
|
|
369
376
|
}
|
|
370
377
|
|
|
378
|
+
// #533 Phase 2 — pure peer-lane inject policy verdict (self-contained; no parser
|
|
379
|
+
// dependency). The PEER LANE is sender ≠ orchestrator AND target ≠ orchestrator.
|
|
380
|
+
// On that lane the body MUST be a sanctioned compact-JSON envelope (the shape
|
|
381
|
+
// produced by aigentry-orchestrator bin/ask.sh build_envelope); anything else is
|
|
382
|
+
// out-of-policy peer→peer traffic (e.g. work-delegation) and is blocked.
|
|
383
|
+
// Returns { lane, decision, reason, kind, envelopePresent }:
|
|
384
|
+
// lane ∈ 'orchestrator' | 'peer' | 'disabled'
|
|
385
|
+
// decision ∈ 'allow' | 'block'
|
|
386
|
+
// kind ∈ 'ask-request' | 'ask-reply' | null
|
|
387
|
+
const PEER_INJECT_KINDS = new Set(['ask-request', 'ask-reply']);
|
|
388
|
+
|
|
389
|
+
function classifyPeerLaneInject({ from, to, prompt, orchestratorSids } = {}) {
|
|
390
|
+
const orchSet = Array.isArray(orchestratorSids) ? orchestratorSids : [];
|
|
391
|
+
// Fail-OPEN: with no known orchestrator sid we cannot tell the orch lane apart
|
|
392
|
+
// from the peer lane (every inject would look peer-lane), which would over-block
|
|
393
|
+
// legitimate orchestrator traffic and brick the mesh. Degrade to allow + warn;
|
|
394
|
+
// the Phase-1 orchestrator-side auditor still detects raw bypass (defense in depth).
|
|
395
|
+
if (orchSet.length === 0) {
|
|
396
|
+
return { lane: 'disabled', decision: 'allow', reason: 'orch-sid-unconfigured-fail-open', kind: null, envelopePresent: false };
|
|
397
|
+
}
|
|
398
|
+
// No sender → operator/CLI/multicast/broadcast, never peer-lane.
|
|
399
|
+
if (!from) {
|
|
400
|
+
return { lane: 'orchestrator', decision: 'allow', reason: 'no-sender', kind: null, envelopePresent: false };
|
|
401
|
+
}
|
|
402
|
+
// Orchestrator lane (either end is the orchestrator) → always allowed, untouched.
|
|
403
|
+
if (orchSet.includes(from) || orchSet.includes(to)) {
|
|
404
|
+
return { lane: 'orchestrator', decision: 'allow', reason: 'orch-lane', kind: null, envelopePresent: false };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Peer lane: require a sanctioned envelope on the first non-empty line.
|
|
408
|
+
let env = null;
|
|
409
|
+
try {
|
|
410
|
+
const firstLine = String(prompt || '').split(/\r?\n/).map(l => l.trim()).find(l => l.length > 0);
|
|
411
|
+
if (firstLine) {
|
|
412
|
+
const parsed = JSON.parse(firstLine);
|
|
413
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) env = parsed;
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
env = null;
|
|
417
|
+
}
|
|
418
|
+
if (!env) {
|
|
419
|
+
return { lane: 'peer', decision: 'block', reason: 'malformed-envelope', kind: null, envelopePresent: false };
|
|
420
|
+
}
|
|
421
|
+
if (!PEER_INJECT_KINDS.has(env.kind)) {
|
|
422
|
+
return { lane: 'peer', decision: 'block', reason: 'wrong-kind', kind: null, envelopePresent: true };
|
|
423
|
+
}
|
|
424
|
+
// Common required fields + per-kind payload (matches ask.sh build_envelope).
|
|
425
|
+
const nonEmptyStr = (v) => typeof v === 'string' && v.length > 0;
|
|
426
|
+
const baseOk = nonEmptyStr(env.from) && nonEmptyStr(env.to) && nonEmptyStr(env.thread_id) && Number.isInteger(env.round);
|
|
427
|
+
const payloadOk = env.kind === 'ask-request' ? nonEmptyStr(env.question) : nonEmptyStr(env.answer);
|
|
428
|
+
if (!baseOk || !payloadOk) {
|
|
429
|
+
return { lane: 'peer', decision: 'block', reason: 'invalid-field', kind: null, envelopePresent: true };
|
|
430
|
+
}
|
|
431
|
+
// Sender-consistency: the envelope's declared sender must match the inject's
|
|
432
|
+
// from (cheap anti-spoof). `to` is NOT cross-checked — the route resolves aliases,
|
|
433
|
+
// which would false-block legitimate aliased targets.
|
|
434
|
+
if (env.from !== from) {
|
|
435
|
+
return { lane: 'peer', decision: 'block', reason: 'from-mismatch', kind: null, envelopePresent: true };
|
|
436
|
+
}
|
|
437
|
+
return { lane: 'peer', decision: 'allow', reason: 'sanctioned-envelope', kind: env.kind, envelopePresent: true };
|
|
438
|
+
}
|
|
439
|
+
|
|
371
440
|
function normalizeNullableText(value) {
|
|
372
441
|
if (value === undefined || value === null) {
|
|
373
442
|
return null;
|
|
@@ -1548,6 +1617,24 @@ function resolveSessionAlias(requestedId) {
|
|
|
1548
1617
|
return candidates[0];
|
|
1549
1618
|
}
|
|
1550
1619
|
|
|
1620
|
+
// #548 (alias-cascade shared-fate): resolveSessionAlias' most-recent-wins fuzzy match is correct for
|
|
1621
|
+
// READ/inject ("talk to the current `coder`"), but DESTRUCTIVE ops (DELETE / kill) must NEVER cascade
|
|
1622
|
+
// across distinct sids that merely share an alias. The incident: cleaning an already-gone `coder-532`
|
|
1623
|
+
// fuzzy-fell-through to its live sibling `coder-533` (same `coder` track) and KILLED it. Distinct sids
|
|
1624
|
+
// = distinct lifecycles. This resolver enforces "destroy exactly the session you named":
|
|
1625
|
+
// - exact sid match → that sid;
|
|
1626
|
+
// - a fully-qualified sid (ends in `-<digits>`) with no exact match → null (a stale/duplicate DELETE
|
|
1627
|
+
// of a gone sid must NOT fall through to a sibling — this is the exact #548 cascade);
|
|
1628
|
+
// - a bare alias → resolve ONLY when a single session carries it (unambiguous); multiple siblings → null
|
|
1629
|
+
// (refuse rather than silently pick most-recent and kill the wrong one).
|
|
1630
|
+
function resolveSessionForDestroy(requestedId) {
|
|
1631
|
+
if (sessions[requestedId]) return requestedId;
|
|
1632
|
+
if (/-\d+$/.test(requestedId)) return null;
|
|
1633
|
+
const baseAlias = requestedId.replace(/-\d+$/, '');
|
|
1634
|
+
const candidates = Object.keys(sessions).filter(id => id.replace(/-\d+$/, '') === baseAlias);
|
|
1635
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1551
1638
|
app.post('/api/sessions/spawn', (req, res) => {
|
|
1552
1639
|
const { session_id, command, args = [], cwd = process.cwd(), cols = 80, rows = 30, type = 'AGENT' } = req.body;
|
|
1553
1640
|
if (!session_id) return res.status(400).json({ error: 'session_id is strictly required.' });
|
|
@@ -2435,6 +2522,32 @@ app.post('/api/sessions/:id/inject', async (req, res) => {
|
|
|
2435
2522
|
// Routing metadata stays in session/bus state, not in the visible prompt text.
|
|
2436
2523
|
const finalPrompt = prompt;
|
|
2437
2524
|
const inject_id = crypto.randomUUID();
|
|
2525
|
+
|
|
2526
|
+
// #533 Phase 2 — peer-lane inject guardrail (in-band hard block, before delivery).
|
|
2527
|
+
// Out-of-policy peer→peer injects (no sanctioned ask-request/ask-reply envelope)
|
|
2528
|
+
// are blocked here so raw work-delegation bypass is prevented, not just detected.
|
|
2529
|
+
// Orchestrator↔peer, broadcast/multicast (no `from`), and existing kinds are untouched.
|
|
2530
|
+
const peerVerdict = classifyPeerLaneInject({ from, to: requestedId, prompt, orchestratorSids: ORCHESTRATOR_SIDS });
|
|
2531
|
+
if (peerVerdict.decision === 'block') {
|
|
2532
|
+
broadcastSessionEvent('peer_inject_blocked', id, session, {
|
|
2533
|
+
extra: {
|
|
2534
|
+
target_agent: id,
|
|
2535
|
+
from: from || null,
|
|
2536
|
+
reason: peerVerdict.reason,
|
|
2537
|
+
attempted_kind: peerVerdict.kind,
|
|
2538
|
+
envelope_present: peerVerdict.envelopePresent,
|
|
2539
|
+
inject_id
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2542
|
+
console.warn(`[PEER-GUARD] blocked peer inject ${from} → ${id} (${peerVerdict.reason})`);
|
|
2543
|
+
return respondWithError(res, 403, 'PEER_INJECT_BLOCKED',
|
|
2544
|
+
'Peer-lane inject blocked: not a sanctioned ask-request/ask-reply envelope. Use bin/ask.sh.',
|
|
2545
|
+
{ reason: peerVerdict.reason, sanctioned_channel: 'bin/ask.sh' });
|
|
2546
|
+
}
|
|
2547
|
+
if (peerVerdict.lane === 'disabled') {
|
|
2548
|
+
console.warn('[PEER-GUARD] orchestrator sid unconfigured (AIGENTRY_ORCHESTRATOR_SIDS empty) — peer guardrail disabled (fail-open)');
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2438
2551
|
try {
|
|
2439
2552
|
const delivery = await deliverInjectionToSession(id, session, finalPrompt, {
|
|
2440
2553
|
noEnter: !!no_enter,
|
|
@@ -2728,7 +2841,8 @@ app.patch('/api/sessions/:id', (req, res) => {
|
|
|
2728
2841
|
|
|
2729
2842
|
app.post('/api/sessions/:id/kill', async (req, res) => {
|
|
2730
2843
|
const requestedId = req.params.id;
|
|
2731
|
-
|
|
2844
|
+
// #548: destructive op — must not cascade across alias-sharing siblings.
|
|
2845
|
+
const resolvedId = resolveSessionForDestroy(requestedId);
|
|
2732
2846
|
if (!resolvedId) return res.status(404).json({ error: 'Session not found', requested: requestedId });
|
|
2733
2847
|
|
|
2734
2848
|
try {
|
|
@@ -2757,7 +2871,8 @@ app.post('/api/sessions/:id/kill', async (req, res) => {
|
|
|
2757
2871
|
|
|
2758
2872
|
app.delete('/api/sessions/:id', (req, res) => {
|
|
2759
2873
|
const requestedId = req.params.id;
|
|
2760
|
-
|
|
2874
|
+
// #548: destructive op — must not cascade across alias-sharing siblings.
|
|
2875
|
+
const resolvedId = resolveSessionForDestroy(requestedId);
|
|
2761
2876
|
if (!resolvedId) return res.status(404).json({ error: 'Session not found', requested: requestedId });
|
|
2762
2877
|
const session = sessions[resolvedId];
|
|
2763
2878
|
const id = resolvedId;
|
|
@@ -3502,4 +3617,5 @@ module.exports = {
|
|
|
3502
3617
|
scheduleBootstrapPromptPoll, // #29: arms the floor timer (deps DI: setTimeout/...)
|
|
3503
3618
|
decideSurfaceGc, // #17: surface-liveness verdict→action (incl. INV-17 unknown→skip)
|
|
3504
3619
|
applySurfaceMismatchProbe, // surface_mismatched debounce + payload helper (deps DI: emit/clock)
|
|
3620
|
+
classifyPeerLaneInject, // #533 Phase 2: pure peer-lane inject policy verdict
|
|
3505
3621
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"postinstall": "node scripts/postinstall.js",
|
|
38
|
-
"test": "node --test test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
|
-
"test:watch": "node --test --watch test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js",
|
|
40
|
-
"test:ci": "node --test --test-reporter=spec test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
38
|
+
"test": "node --require ./test-support/setup-env.js --test test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
|
+
"test:watch": "node --require ./test-support/setup-env.js --test --watch test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js",
|
|
40
|
+
"test:ci": "node --require ./test-support/setup-env.js --test --test-reporter=spec test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
43
43
|
},
|
package/session-state.js
CHANGED
|
@@ -96,6 +96,14 @@ const THINKING_PATTERNS = [
|
|
|
96
96
|
/\breading\b/i, // Claude Code "Reading..."
|
|
97
97
|
/\bsearching\b/i, // Claude Code "Searching..."
|
|
98
98
|
/\bplanning\b/i, // Claude Code "Planning..."
|
|
99
|
+
// codex CLI active-work markers (#558): codex emits NO braille spinner and NO OSC 133, so its
|
|
100
|
+
// "busy" state went unrecognized → blank sidebar pill. These are high-signal codex/claude markers
|
|
101
|
+
// shown ONLY while actively generating/running tools. ("Working" is intentionally NOT matched —
|
|
102
|
+
// it false-positives on common dev output like "working tree" / "working directory".)
|
|
103
|
+
/\besc to interrupt\b/i, // codex + claude: shown only during active generation / tool run
|
|
104
|
+
/\bstarting mcp servers?\b/i,// codex: MCP bootstrap on launch
|
|
105
|
+
/\bbooting mcp server\b/i, // codex: MCP server boot
|
|
106
|
+
/\bexploring\b/i, // codex activity verb (parallels Claude Code "Searching")
|
|
99
107
|
/\.{3,}\s*$/, // trailing dots "..."
|
|
100
108
|
];
|
|
101
109
|
|
package/terminal-backend.js
CHANGED
|
@@ -107,24 +107,6 @@ function cmuxSendText(sessionId, text) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
// Send enter key to a cmux surface
|
|
111
|
-
function cmuxSendEnter(sessionId) {
|
|
112
|
-
const surface = findSurface(sessionId);
|
|
113
|
-
if (!surface) return false;
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
execSync(`cmux send-key --surface ${surface} return`, {
|
|
117
|
-
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
118
|
-
});
|
|
119
|
-
console.log(`[BACKEND] cmux send-key return to ${sessionId} (${surface})`);
|
|
120
|
-
return true;
|
|
121
|
-
} catch (err) {
|
|
122
|
-
console.error(`[BACKEND] cmux send-key failed for ${sessionId}:`, err.message);
|
|
123
|
-
surfaceCache.delete(sessionId);
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
110
|
// Invalidate cache for a session (e.g., when surface changes)
|
|
129
111
|
function invalidateCache(sessionId) {
|
|
130
112
|
surfaceCache.delete(sessionId);
|
|
@@ -534,7 +516,6 @@ module.exports = {
|
|
|
534
516
|
detectTerminal,
|
|
535
517
|
findSurface,
|
|
536
518
|
cmuxSendText,
|
|
537
|
-
cmuxSendEnter,
|
|
538
519
|
refreshSurfaceCache,
|
|
539
520
|
invalidateCache,
|
|
540
521
|
clearCache,
|