@dmsdc-ai/aigentry-telepty 0.5.4 → 0.5.6
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 +71 -95
- package/package.json +4 -4
- package/session-state.js +11 -2
- package/src/submit-gate.js +9 -3
package/daemon.js
CHANGED
|
@@ -88,8 +88,18 @@ sessionStateManager.onTransition((sessionId, from, to, detail) => {
|
|
|
88
88
|
// Mark as idle-notified (but keep the entry — REPORT is still pending).
|
|
89
89
|
// Entry is cleared when REPORT arrives (via inject endpoint) OR session dies.
|
|
90
90
|
if (pendingReport.idleNotified) return; // only fire once
|
|
91
|
+
// #545: only an OSC133-marked idle with the injected body consumed from the PTY outputRing
|
|
92
|
+
// is trustworthy enough to report TASK_COMPLETE. A weak prompt-glyph / silence flip (the
|
|
93
|
+
// residual WORKING case the THINKING-only state guard doesn't cover) stays
|
|
94
|
+
// TASK_IDLE_UNCONFIRMED — never a false complete.
|
|
95
|
+
const idleTrigger = detail && detail.detail ? detail.detail.trigger : null;
|
|
96
|
+
const bodyText = pendingReport.injectedBodyPreview;
|
|
97
|
+
const bodyVisible = bodyText
|
|
98
|
+
? submitGate.observeBodyVisibility(session, bodyText).visible === true
|
|
99
|
+
: false;
|
|
100
|
+
const idleEvidenceReliable = idleTrigger === 'osc_133_prompt' && !bodyVisible;
|
|
91
101
|
// real-idle: the state manager observed a genuine busy→idle transition.
|
|
92
|
-
fireAutoReport(sessionId, session, pendingReport, 'real-idle');
|
|
102
|
+
fireAutoReport(sessionId, session, pendingReport, 'real-idle', { idleEvidenceReliable });
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
// Fire TASK_DEAD_NO_REPORT when session dies with a pending report
|
|
@@ -307,11 +317,23 @@ function fireAutoReport(targetId, targetSession, pendingReport, trigger, deps =
|
|
|
307
317
|
pendingReport.submitConfirmedAt ||
|
|
308
318
|
(pendingReport.submitConfirm && pendingReport.submitConfirm.accepted === true)
|
|
309
319
|
);
|
|
320
|
+
// #545: a `real-idle` flip with weak evidence (no OSC133 REPL-done mark / injected body still
|
|
321
|
+
// visible in the PTY outputRing) must NOT be reported TASK_COMPLETE — a still-busy worker that
|
|
322
|
+
// merely paused output gets the honest TASK_IDLE_UNCONFIRMED. The caller (onTransition) sets
|
|
323
|
+
// deps.idleEvidenceReliable; === false forces the downgrade. Scoped to submitExpected (the #545
|
|
324
|
+
// symptom — a submit-confirmed worker still thinking), consistent with the BUG-B confirm gate;
|
|
325
|
+
// plain non-submit injects keep their existing floor-based completion. Absent flag / other
|
|
326
|
+
// triggers preserve prior behavior.
|
|
327
|
+
const idleEvidenceUnreliable = trigger === 'real-idle'
|
|
328
|
+
&& pendingReport.submitExpected
|
|
329
|
+
&& deps.idleEvidenceReliable === false;
|
|
310
330
|
const confirmed = trigger === 'ready-signal' && pendingReport.submitExpected
|
|
311
331
|
? false
|
|
312
|
-
:
|
|
313
|
-
?
|
|
314
|
-
:
|
|
332
|
+
: idleEvidenceUnreliable
|
|
333
|
+
? false
|
|
334
|
+
: pendingReport.submitExpected
|
|
335
|
+
? strongSubmitConfirmed
|
|
336
|
+
: (elapsedNum >= AUTO_REPORT_MIN_REAL_SECONDS || hasSubmitEvidence);
|
|
315
337
|
const injTag = pendingReport.injectId ? ` inject=${pendingReport.injectId}` : '';
|
|
316
338
|
const reportMsg = confirmed
|
|
317
339
|
? `TASK_COMPLETE: ${targetId} is now idle after processing inject (${elapsed}s, via ${trigger}${injTag})`
|
|
@@ -1180,32 +1202,38 @@ async function writeDataToSession(id, session, data) {
|
|
|
1180
1202
|
}
|
|
1181
1203
|
|
|
1182
1204
|
/**
|
|
1183
|
-
* Submit Enter to a session
|
|
1205
|
+
* Submit Enter to a session via the PTY/context layer.
|
|
1184
1206
|
* Used by POST /submit endpoint for explicit terminal-level submit.
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1207
|
+
*
|
|
1208
|
+
* Submit is a CONTEXT operation (telepty-owned), not a SURFACE operation
|
|
1209
|
+
* (cmux/kitty adaptor-owned). Deliver the submit Enter via the PTY only — a bare
|
|
1210
|
+
* 0x0D into the CLI's innermost node-pty. The former kitty `send-text` (P1) and
|
|
1211
|
+
* `cmux send-key` (P2) branches were SURFACE ops on a flaky side channel (75×
|
|
1212
|
+
* "Failed to write to socket" vs 0× for pty_cr in a 222k-line run; live
|
|
1213
|
+
* 2026-06-07 confirmed pty-only works 3/3). The `submitViaCmux`/`sendViaKitty`
|
|
1214
|
+
* defs were since removed (#544/#546 — submit-all also migrated to PTY, leaving
|
|
1215
|
+
* zero cmux send-key in the submit path). See
|
|
1216
|
+
* docs/adr/2026-06-07-submit-via-pty-context-layer.md.
|
|
1217
|
+
*
|
|
1218
|
+
* Returns the strategy name ('pty_cr') or null on failure.
|
|
1187
1219
|
*/
|
|
1188
1220
|
function terminalLevelSubmit(id, session) {
|
|
1189
|
-
// Priority 1: kitty send-text (terminal-level, bypasses PTY raw mode quirks)
|
|
1190
|
-
if (session.type === 'wrapped' && sendViaKitty(id, '\r')) return 'kitty';
|
|
1191
|
-
// Priority 2: cmux send-key
|
|
1192
|
-
if (session.backend === 'cmux' && session.cmuxWorkspaceId && submitViaCmux(id)) return 'cmux';
|
|
1193
|
-
// Priority 3: PTY \r
|
|
1194
1221
|
if (submitViaPty(session)) return 'pty_cr';
|
|
1195
1222
|
return null;
|
|
1196
1223
|
}
|
|
1197
1224
|
|
|
1198
|
-
// #537 / Bug B:
|
|
1199
|
-
//
|
|
1200
|
-
//
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1225
|
+
// #544 / #537 / Bug B: with PTY-native submit (terminalLevelSubmit → pty_cr only),
|
|
1226
|
+
// a successful pty_cr IS real delivery on every backend — the bare 0x0D reaches the
|
|
1227
|
+
// CLI's innermost node-pty directly (live 2026-06-07: pty-only delivered 3/3 even
|
|
1228
|
+
// when cmux send-key failed). The honest "was it accepted?" signal is the
|
|
1229
|
+
// PTY-derived confirm (confirmSubmitAccepted: state∈{working,thinking} since≥
|
|
1230
|
+
// submittedAt, or body consumed from outputRing) — NOT the strategy name. We no
|
|
1231
|
+
// longer special-case pty_cr-on-cmux as undelivered; that false-negative was the
|
|
1232
|
+
// direct cause of the BUG B bogus UNCONFIRMED reports + worker re-send loops.
|
|
1233
|
+
// Pure + exported so the decision is unit-testable.
|
|
1234
|
+
// See docs/adr/2026-06-07-submit-via-pty-context-layer.md.
|
|
1203
1235
|
function forceSubmitDeliveredToSurface(session, strategy) {
|
|
1204
|
-
|
|
1205
|
-
if (session && session.backend === 'cmux' && session.cmuxWorkspaceId && strategy === 'pty_cr') {
|
|
1206
|
-
return false;
|
|
1207
|
-
}
|
|
1208
|
-
return true;
|
|
1236
|
+
return !!strategy;
|
|
1209
1237
|
}
|
|
1210
1238
|
|
|
1211
1239
|
async function deliverInjectionToSession(id, session, prompt, options = {}) {
|
|
@@ -2013,45 +2041,6 @@ function findKittyWindowId(socket, sessionId) {
|
|
|
2013
2041
|
return null;
|
|
2014
2042
|
}
|
|
2015
2043
|
|
|
2016
|
-
function sendViaKitty(sessionId, text) {
|
|
2017
|
-
const { execSync } = require('child_process');
|
|
2018
|
-
const socket = findKittySocket();
|
|
2019
|
-
if (!socket) return false;
|
|
2020
|
-
|
|
2021
|
-
const windowId = findKittyWindowId(socket, sessionId);
|
|
2022
|
-
if (!windowId) {
|
|
2023
|
-
console.error(`[KITTY] No window found for ${sessionId}`);
|
|
2024
|
-
return false;
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
try {
|
|
2028
|
-
// Split text and CR — send-text for both (send-key corrupts keyboard protocol)
|
|
2029
|
-
const hasCr = text.endsWith('\r') || text.endsWith('\n');
|
|
2030
|
-
const textOnly = hasCr ? text.slice(0, -1) : text;
|
|
2031
|
-
if (textOnly.length > 0) {
|
|
2032
|
-
const escaped = textOnly.replace(/\\/g, '\\\\').replace(/'/g, "'\\''");
|
|
2033
|
-
execSync(`kitty @ --to unix:${socket} send-text --match id:${windowId} '${escaped}'`, {
|
|
2034
|
-
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
if (hasCr) {
|
|
2038
|
-
// Delay before sending Return — only when text was sent in the same call
|
|
2039
|
-
// (when CR-only, text was already delivered via a different path)
|
|
2040
|
-
if (textOnly.length > 0) {
|
|
2041
|
-
execSync('sleep 0.5', { timeout: 2000 });
|
|
2042
|
-
}
|
|
2043
|
-
execSync(`kitty @ --to unix:${socket} send-text --match id:${windowId} $'\\r'`, {
|
|
2044
|
-
timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
console.log(`[KITTY] Sent ${textOnly.length} chars${hasCr ? ' + Return' : ''} to ${sessionId} (window ${windowId})`);
|
|
2048
|
-
return true;
|
|
2049
|
-
} catch (err) {
|
|
2050
|
-
console.error(`[KITTY] Failed for ${sessionId}:`, err.message);
|
|
2051
|
-
return false;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
2044
|
function submitViaOsascript(sessionId, keyCombo) {
|
|
2056
2045
|
const { execSync } = require('child_process');
|
|
2057
2046
|
const session = sessions[sessionId];
|
|
@@ -2102,22 +2091,6 @@ function submitViaOsascript(sessionId, keyCombo) {
|
|
|
2102
2091
|
}
|
|
2103
2092
|
}
|
|
2104
2093
|
|
|
2105
|
-
function submitViaCmux(sessionId) {
|
|
2106
|
-
const { execSync } = require('child_process');
|
|
2107
|
-
const session = sessions[sessionId];
|
|
2108
|
-
if (!session || !session.cmuxWorkspaceId) return false;
|
|
2109
|
-
try {
|
|
2110
|
-
execSync(`cmux send-key --workspace ${session.cmuxWorkspaceId} return`, {
|
|
2111
|
-
timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
2112
|
-
});
|
|
2113
|
-
console.log(`[SUBMIT] cmux send-key return for ${sessionId} (workspace ${session.cmuxWorkspaceId})`);
|
|
2114
|
-
return true;
|
|
2115
|
-
} catch (err) {
|
|
2116
|
-
console.error(`[SUBMIT] cmux send-key failed for ${sessionId}:`, err.message);
|
|
2117
|
-
return false;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
2094
|
// POST /api/sessions/:id/submit — render-gated CLI-aware submit
|
|
2122
2095
|
//
|
|
2123
2096
|
// Default behavior (0.3.0+): wait for the target REPL to be ready (sessionStateManager
|
|
@@ -2414,27 +2387,22 @@ app.post('/api/sessions/:id/submit', async (req, res) => {
|
|
|
2414
2387
|
return res.json(responseBody);
|
|
2415
2388
|
});
|
|
2416
2389
|
|
|
2417
|
-
//
|
|
2418
|
-
|
|
2390
|
+
// Submit Enter to every active session. #546: submit is a PTY/context op (bare 0x0D) for every
|
|
2391
|
+
// wrapped + spawned backend INCLUDING cmux — the path validated 3/3 live for per-session submit
|
|
2392
|
+
// (#544). The cmux `send-key --surface return` surface op is removed (ZERO cmux send-key);
|
|
2393
|
+
// osascript Cmd+Enter remains only for app-window sessions with no PTY bridge. Exported (pure
|
|
2394
|
+
// over the passed sessions map) so the dispatch is unit-testable without starting the daemon.
|
|
2395
|
+
function runSubmitAll(sessionsMap) {
|
|
2419
2396
|
const results = { successful: [], failed: [] };
|
|
2420
2397
|
|
|
2421
|
-
for (const [id, session] of Object.entries(
|
|
2398
|
+
for (const [id, session] of Object.entries(sessionsMap)) {
|
|
2422
2399
|
const strategy = getSubmitStrategy(session.command);
|
|
2423
2400
|
let success = false;
|
|
2424
2401
|
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
if (!success && session.backend === 'cmux' && session.cmuxWorkspaceId) {
|
|
2430
|
-
success = submitViaCmux(id);
|
|
2431
|
-
}
|
|
2432
|
-
if (!success) {
|
|
2433
|
-
if (strategy === 'pty_cr') {
|
|
2434
|
-
success = submitViaPty(session);
|
|
2435
|
-
} else if (strategy === 'osascript_cmd_enter') {
|
|
2436
|
-
success = submitViaOsascript(id, 'cmd_enter');
|
|
2437
|
-
}
|
|
2402
|
+
if (strategy === 'pty_cr') {
|
|
2403
|
+
success = submitViaPty(session);
|
|
2404
|
+
} else if (strategy === 'osascript_cmd_enter') {
|
|
2405
|
+
success = submitViaOsascript(id, 'cmd_enter');
|
|
2438
2406
|
}
|
|
2439
2407
|
|
|
2440
2408
|
if (success) {
|
|
@@ -2444,7 +2412,12 @@ app.post('/api/sessions/submit-all', (req, res) => {
|
|
|
2444
2412
|
}
|
|
2445
2413
|
}
|
|
2446
2414
|
|
|
2447
|
-
|
|
2415
|
+
return results;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
// POST /api/sessions/submit-all — Submit all active sessions
|
|
2419
|
+
app.post('/api/sessions/submit-all', (req, res) => {
|
|
2420
|
+
res.json({ success: true, results: runSubmitAll(sessions) });
|
|
2448
2421
|
});
|
|
2449
2422
|
|
|
2450
2423
|
app.post('/api/sessions/:id/inject', async (req, res) => {
|
|
@@ -3520,7 +3493,10 @@ if (require.main === module) {
|
|
|
3520
3493
|
// production call sites is unchanged. NOT a public API — internal/test use only.
|
|
3521
3494
|
module.exports = {
|
|
3522
3495
|
fireAutoReport, // #32: provenance-tagged auto-report (deps DI: now/deliver/...)
|
|
3523
|
-
forceSubmitDeliveredToSurface, // #537/Bug B:
|
|
3496
|
+
forceSubmitDeliveredToSurface, // #544/#537/Bug B: PTY-native force-confirm (pty_cr = delivered)
|
|
3497
|
+
terminalLevelSubmit, // #544: PTY-only submit path (pty_cr | null)
|
|
3498
|
+
submitViaPty, // #544: bare-0x0D submit into the innermost node-pty
|
|
3499
|
+
runSubmitAll, // #546: submit-all via PTY for every backend (no cmux send-key)
|
|
3524
3500
|
failBootstrapQueueOnTimeout, // #31: actionable bootstrap-timeout queue flush
|
|
3525
3501
|
shouldApplyOwnerAliveFloor, // #29: owner-alive optimistic-floor decision (deps DI: isProcessRunning/...)
|
|
3526
3502
|
scheduleBootstrapPromptPoll, // #29: arms the floor timer (deps DI: setTimeout/...)
|
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.6",
|
|
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/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/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/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 --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/",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
43
43
|
},
|
package/session-state.js
CHANGED
|
@@ -356,8 +356,7 @@ class SessionStateMachine {
|
|
|
356
356
|
});
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
_tick() {
|
|
360
|
-
const now = Date.now();
|
|
359
|
+
_tick(now = Date.now()) {
|
|
361
360
|
const silenceMs = now - this._lastOutputAt;
|
|
362
361
|
|
|
363
362
|
// Don't override lifecycle states
|
|
@@ -389,6 +388,16 @@ class SessionStateMachine {
|
|
|
389
388
|
: '';
|
|
390
389
|
|
|
391
390
|
const hasOsc133 = this._lastOsc133At && (now - this._lastOsc133At) < this.config.idle_timeout_ms * 2;
|
|
391
|
+
|
|
392
|
+
// #545: a THINKING session that merely went quiet is STILL thinking. The claude TUI
|
|
393
|
+
// input-box glyph (›/❯) false-matches PROMPT_PATTERNS and pure silence flips at 0.6, so
|
|
394
|
+
// a weak signal would wrongly end thinking. Only a reliable REPL-done mark (OSC 133) may.
|
|
395
|
+
// WORKING is intentionally NOT guarded — a real shell prompt `$ ` legitimately settles
|
|
396
|
+
// working→idle (test 255); the daemon real-idle gate covers the residual WORKING case.
|
|
397
|
+
if (this._state === STATES.THINKING && !hasOsc133) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
392
401
|
const hasPrompt = this._matchesAny(lastLine, PROMPT_PATTERNS);
|
|
393
402
|
const confidence = hasOsc133 ? 0.95 : (hasPrompt ? 0.9 : 0.6);
|
|
394
403
|
|
package/src/submit-gate.js
CHANGED
|
@@ -308,10 +308,16 @@ function observeBodyVisibility(session, bodyText, opts = {}) {
|
|
|
308
308
|
};
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// PTY-native confirm (#544): do NOT shell to `cmux read-screen` for the submit
|
|
312
|
+
// confirm. The body-visibility source is the PTY-fed outputRing
|
|
313
|
+
// (observeBodyVisibility falls through to it when this returns null). An explicit
|
|
314
|
+
// opts.readScreen seam is still honored (tests / future surface adaptors); the
|
|
315
|
+
// cmux default is dropped so confirmation is screen-free and no longer depends on
|
|
316
|
+
// the flaky cmux surface. The pre-submit readiness probe (awaitPromptSymbol) keeps
|
|
317
|
+
// its own defaultReadScreen — that is a separate concern, out of scope here.
|
|
318
|
+
// See docs/adr/2026-06-07-submit-via-pty-context-layer.md.
|
|
311
319
|
function readCurrentScreen(session, opts = {}) {
|
|
312
|
-
const readScreen = typeof opts.readScreen === 'function'
|
|
313
|
-
? opts.readScreen
|
|
314
|
-
: (session && session.backend === 'cmux' && session.cmuxWorkspaceId ? defaultReadScreen : null);
|
|
320
|
+
const readScreen = typeof opts.readScreen === 'function' ? opts.readScreen : null;
|
|
315
321
|
if (!readScreen || !session || !session.cmuxWorkspaceId) return null;
|
|
316
322
|
const tailLines = Number.isFinite(opts.tailLines) ? opts.tailLines : 30;
|
|
317
323
|
try {
|