@dmsdc-ai/aigentry-telepty 0.5.5 → 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 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
- : pendingReport.submitExpected
313
- ? strongSubmitConfirmed
314
- : (elapsedNum >= AUTO_REPORT_MIN_REAL_SECONDS || hasSubmitEvidence);
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})`
@@ -1188,9 +1210,10 @@ async function writeDataToSession(id, session, data) {
1188
1210
  * 0x0D into the CLI's innermost node-pty. The former kitty `send-text` (P1) and
1189
1211
  * `cmux send-key` (P2) branches were SURFACE ops on a flaky side channel (75×
1190
1212
  * "Failed to write to socket" vs 0× for pty_cr in a 222k-line run; live
1191
- * 2026-06-07 confirmed pty-only works 3/3). `submitViaCmux`/`sendViaKitty` defs
1192
- * are kept (non-submit callers); codebase removal is gated on the warp/tmux
1193
- * matrix. See docs/adr/2026-06-07-submit-via-pty-context-layer.md.
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.
1194
1217
  *
1195
1218
  * Returns the strategy name ('pty_cr') or null on failure.
1196
1219
  */
@@ -2018,45 +2041,6 @@ function findKittyWindowId(socket, sessionId) {
2018
2041
  return null;
2019
2042
  }
2020
2043
 
2021
- function sendViaKitty(sessionId, text) {
2022
- const { execSync } = require('child_process');
2023
- const socket = findKittySocket();
2024
- if (!socket) return false;
2025
-
2026
- const windowId = findKittyWindowId(socket, sessionId);
2027
- if (!windowId) {
2028
- console.error(`[KITTY] No window found for ${sessionId}`);
2029
- return false;
2030
- }
2031
-
2032
- try {
2033
- // Split text and CR — send-text for both (send-key corrupts keyboard protocol)
2034
- const hasCr = text.endsWith('\r') || text.endsWith('\n');
2035
- const textOnly = hasCr ? text.slice(0, -1) : text;
2036
- if (textOnly.length > 0) {
2037
- const escaped = textOnly.replace(/\\/g, '\\\\').replace(/'/g, "'\\''");
2038
- execSync(`kitty @ --to unix:${socket} send-text --match id:${windowId} '${escaped}'`, {
2039
- timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
2040
- });
2041
- }
2042
- if (hasCr) {
2043
- // Delay before sending Return — only when text was sent in the same call
2044
- // (when CR-only, text was already delivered via a different path)
2045
- if (textOnly.length > 0) {
2046
- execSync('sleep 0.5', { timeout: 2000 });
2047
- }
2048
- execSync(`kitty @ --to unix:${socket} send-text --match id:${windowId} $'\\r'`, {
2049
- timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
2050
- });
2051
- }
2052
- console.log(`[KITTY] Sent ${textOnly.length} chars${hasCr ? ' + Return' : ''} to ${sessionId} (window ${windowId})`);
2053
- return true;
2054
- } catch (err) {
2055
- console.error(`[KITTY] Failed for ${sessionId}:`, err.message);
2056
- return false;
2057
- }
2058
- }
2059
-
2060
2044
  function submitViaOsascript(sessionId, keyCombo) {
2061
2045
  const { execSync } = require('child_process');
2062
2046
  const session = sessions[sessionId];
@@ -2107,22 +2091,6 @@ function submitViaOsascript(sessionId, keyCombo) {
2107
2091
  }
2108
2092
  }
2109
2093
 
2110
- function submitViaCmux(sessionId) {
2111
- const { execSync } = require('child_process');
2112
- const session = sessions[sessionId];
2113
- if (!session || !session.cmuxWorkspaceId) return false;
2114
- try {
2115
- execSync(`cmux send-key --workspace ${session.cmuxWorkspaceId} return`, {
2116
- timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
2117
- });
2118
- console.log(`[SUBMIT] cmux send-key return for ${sessionId} (workspace ${session.cmuxWorkspaceId})`);
2119
- return true;
2120
- } catch (err) {
2121
- console.error(`[SUBMIT] cmux send-key failed for ${sessionId}:`, err.message);
2122
- return false;
2123
- }
2124
- }
2125
-
2126
2094
  // POST /api/sessions/:id/submit — render-gated CLI-aware submit
2127
2095
  //
2128
2096
  // Default behavior (0.3.0+): wait for the target REPL to be ready (sessionStateManager
@@ -2419,27 +2387,22 @@ app.post('/api/sessions/:id/submit', async (req, res) => {
2419
2387
  return res.json(responseBody);
2420
2388
  });
2421
2389
 
2422
- // POST /api/sessions/submit-all Submit all active sessions
2423
- app.post('/api/sessions/submit-all', (req, res) => {
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) {
2424
2396
  const results = { successful: [], failed: [] };
2425
2397
 
2426
- for (const [id, session] of Object.entries(sessions)) {
2398
+ for (const [id, session] of Object.entries(sessionsMap)) {
2427
2399
  const strategy = getSubmitStrategy(session.command);
2428
2400
  let success = false;
2429
2401
 
2430
- // cmux per-session backend
2431
- if (session.backend === 'cmux') {
2432
- success = terminalBackend.cmuxSendEnter(id);
2433
- }
2434
- if (!success && session.backend === 'cmux' && session.cmuxWorkspaceId) {
2435
- success = submitViaCmux(id);
2436
- }
2437
- if (!success) {
2438
- if (strategy === 'pty_cr') {
2439
- success = submitViaPty(session);
2440
- } else if (strategy === 'osascript_cmd_enter') {
2441
- success = submitViaOsascript(id, 'cmd_enter');
2442
- }
2402
+ if (strategy === 'pty_cr') {
2403
+ success = submitViaPty(session);
2404
+ } else if (strategy === 'osascript_cmd_enter') {
2405
+ success = submitViaOsascript(id, 'cmd_enter');
2443
2406
  }
2444
2407
 
2445
2408
  if (success) {
@@ -2449,7 +2412,12 @@ app.post('/api/sessions/submit-all', (req, res) => {
2449
2412
  }
2450
2413
  }
2451
2414
 
2452
- res.json({ success: true, results });
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) });
2453
2421
  });
2454
2422
 
2455
2423
  app.post('/api/sessions/:id/inject', async (req, res) => {
@@ -3528,6 +3496,7 @@ module.exports = {
3528
3496
  forceSubmitDeliveredToSurface, // #544/#537/Bug B: PTY-native force-confirm (pty_cr = delivered)
3529
3497
  terminalLevelSubmit, // #544: PTY-only submit path (pty_cr | null)
3530
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)
3531
3500
  failBootstrapQueueOnTimeout, // #31: actionable bootstrap-timeout queue flush
3532
3501
  shouldApplyOwnerAliveFloor, // #29: owner-alive optimistic-floor decision (deps DI: isProcessRunning/...)
3533
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.5",
3
+ "version": "0.5.6",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",
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