@dmsdc-ai/aigentry-telepty 0.6.1 → 0.6.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/daemon.js +85 -24
  3. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.2] - 2026-06-10
8
+
9
+ ### Fixed — TASK_IDLE_UNCONFIRMED false positives (#48)
10
+
11
+ - **`TASK_IDLE_UNCONFIRMED` fired ~0–0.5s after nearly every inject** even when the inject was
12
+ processed, destroying the signal's value. Two proven causes: (a) the bridge re-sends `ready` on
13
+ every TUI prompt-glyph redraw after an inject, and "working" evidence was only recorded on a
14
+ transition *into* working — so an inject landing on an already-working session left zero evidence
15
+ and the notifier fired on the first weak snapshot; (b) codex's spinner-less TUI (5s silence +
16
+ `›` prompt glyph) flips the real-idle classifier mid-work.
17
+ - **Fix: settle-and-recheck.** A would-be `TASK_IDLE_UNCONFIRMED` is held for
18
+ `TELEPTY_IDLE_UNCONFIRMED_SETTLE_SECONDS` (default 5) and re-checked against the **live** session
19
+ state: working/thinking → suppressed; output advanced while idle-classified → bounded re-settle
20
+ (`TELEPTY_IDLE_UNCONFIRMED_SETTLE_MAX_REARMS`, default 3); still idle and stalled → notify, so the
21
+ genuine "inject not consumed" signal is preserved. The report label is pinned at arm time, so the
22
+ settle window can never promote a stale idle snapshot to `TASK_COMPLETE` (the never-false-complete
23
+ invariant is kept). Message format is unchanged.
24
+
7
25
  ## [0.6.1] - 2026-06-09
8
26
 
9
27
  ### Added — delivery provenance wrapper + audit seams (#47, P4+P5)
package/daemon.js CHANGED
@@ -286,6 +286,14 @@ const AUTO_REPORT_IDLE_SECONDS = Number(process.env.TELEPTY_AUTO_REPORT_IDLE_SEC
286
286
  // by the recipient. Below this elapsed floor the idle is NOT trusted as a processed-inject
287
287
  // completion; the text-inject is relabeled so a stuck/hung target is never reported as DONE.
288
288
  const AUTO_REPORT_MIN_REAL_SECONDS = Number(process.env.TELEPTY_AUTO_REPORT_MIN_REAL_SECONDS) || 1.0;
289
+ // #48: a momentary idle/ready snapshot right after an inject (the bridge re-sends 'ready' on a
290
+ // TUI prompt-glyph redraw; codex's silence+glyph flips real-idle mid-work) is almost always a
291
+ // transition-gap false positive — the session is, or moments later is, working. Before emitting
292
+ // TASK_IDLE_UNCONFIRMED, hold for this settle window and recheck the LIVE session state.
293
+ const IDLE_UNCONFIRMED_SETTLE_SECONDS = Number(process.env.TELEPTY_IDLE_UNCONFIRMED_SETTLE_SECONDS) || 5;
294
+ // Output advanced during the settle window while still idle-classified (sparse TUI redraw) →
295
+ // re-settle, bounded so periodic idle redraws cannot starve the genuinely-unconsumed signal.
296
+ const IDLE_UNCONFIRMED_SETTLE_MAX_REARMS = Math.max(0, Number(process.env.TELEPTY_IDLE_UNCONFIRMED_SETTLE_MAX_REARMS) || 3);
289
297
 
290
298
  function pendingReportHasSubmitEvidence(pendingReport) {
291
299
  return !!(pendingReport && (
@@ -358,6 +366,11 @@ function fireAutoReport(targetId, targetSession, pendingReport, trigger, deps =
358
366
  const _sessions = deps.sessions || sessions;
359
367
  const _pendingReports = deps.pendingReports || pendingReports;
360
368
  const _deliver = deps.deliverInjectionToSession || deliverInjectionToSession;
369
+ // #48: live auto-state lookup for the settle recheck (DI for unit tests).
370
+ const _getAutoState = deps.getAutoState || ((sid) => {
371
+ const st = sessionStateManager.getState(sid);
372
+ return st && st.state ? st.state : null;
373
+ });
361
374
 
362
375
  const elapsedNum = (_now() - new Date(pendingReport.injectedAt).getTime()) / 1000;
363
376
  const elapsed = elapsedNum.toFixed(1);
@@ -390,25 +403,6 @@ function fireAutoReport(targetId, targetSession, pendingReport, trigger, deps =
390
403
  }
391
404
  }
392
405
 
393
- pendingReport.idleNotified = true;
394
- pendingReport.idleAt = new Date(_now()).toISOString();
395
-
396
- // Richer bus event (observability) — now also carries the trigger provenance.
397
- _broadcast('TASK_IDLE_NO_REPORT', targetId, targetSession, {
398
- extra: {
399
- source: pendingReport.source,
400
- inject_id: pendingReport.injectId,
401
- elapsed_secs: Number(elapsed),
402
- injected_at: pendingReport.injectedAt,
403
- trigger
404
- }
405
- });
406
- console.log(`[ENFORCE-REPORT] ${targetId} idle after ${elapsed}s (trigger=${trigger}) — awaiting REPORT from ${pendingReport.source}`);
407
-
408
- const srcId = _resolveAlias(pendingReport.source) || pendingReport.source;
409
- const srcSession = _sessions[srcId];
410
- if (!srcSession) return;
411
-
412
406
  // #537 / Bug B: a never-started worker (transient submit failure → claude startup
413
407
  // busy→idle settle at ~4.5s) must NOT be reported TASK_COMPLETE. When a submit was
414
408
  // expected, the elapsed floor and startup-polluted sawWorkingAfterInject are NOT trusted
@@ -428,13 +422,80 @@ function fireAutoReport(targetId, targetSession, pendingReport, trigger, deps =
428
422
  const idleEvidenceUnreliable = trigger === 'real-idle'
429
423
  && pendingReport.submitExpected
430
424
  && deps.idleEvidenceReliable === false;
431
- const confirmed = trigger === 'ready-signal' && pendingReport.submitExpected
425
+ // #48: a settled recheck re-enters ONLY to emit the UNCONFIRMED label — pinned at arm time,
426
+ // so elapsed growing past the floor during the settle window can never promote a stale idle
427
+ // snapshot to TASK_COMPLETE (never a false complete).
428
+ const confirmed = pendingReport.unconfirmedSettleDone
432
429
  ? false
433
- : idleEvidenceUnreliable
430
+ : trigger === 'ready-signal' && pendingReport.submitExpected
434
431
  ? false
435
- : pendingReport.submitExpected
436
- ? strongSubmitConfirmed
437
- : (elapsedNum >= AUTO_REPORT_MIN_REAL_SECONDS || hasSubmitEvidence);
432
+ : idleEvidenceUnreliable
433
+ ? false
434
+ : pendingReport.submitExpected
435
+ ? strongSubmitConfirmed
436
+ : (elapsedNum >= AUTO_REPORT_MIN_REAL_SECONDS || hasSubmitEvidence);
437
+
438
+ // #48: settle-and-recheck before any UNCONFIRMED notification. The first weak idle/ready
439
+ // snapshot right after an inject is almost always a transition gap — the bridge re-sends
440
+ // 'ready' on a TUI prompt-glyph redraw (with no state transition, no evidence flag is ever
441
+ // set even though the session IS working), and codex's silence+glyph heuristic flips
442
+ // real-idle mid-work. Hold the notification for a settle window and recheck the LIVE
443
+ // session: notify only when it is still not working AND its output has not advanced.
444
+ // Suppression does NOT consume the once-only idleNotified guard, so a later genuine
445
+ // busy→idle transition re-enters this path (and an evidence-backed one reports COMPLETE).
446
+ if (!confirmed && !pendingReport.unconfirmedSettleDone) {
447
+ if (pendingReport.unconfirmedSettleTimer) return; // settle window already open
448
+ const settleMs = Math.max(50, Math.round(IDLE_UNCONFIRMED_SETTLE_SECONDS * 1000));
449
+ const armSettle = () => {
450
+ const liveAtArm = _sessions[targetId] || targetSession;
451
+ const activityAtArm = liveAtArm ? liveAtArm.lastActivityAt : null;
452
+ pendingReport.unconfirmedSettleTimer = _setTimeout(() => {
453
+ pendingReport.unconfirmedSettleTimer = null;
454
+ const currentPending = getPendingReport(targetId, _pendingReports);
455
+ // REPORT arrived / entry replaced / another path already notified — stand down.
456
+ if (currentPending !== pendingReport || currentPending.idleNotified) return;
457
+ const liveSession = _sessions[targetId] || targetSession;
458
+ const autoState = _getAutoState(targetId);
459
+ if (autoState === 'working' || autoState === 'thinking') {
460
+ console.log(`[AUTO-REPORT] ${targetId} idle-unconfirmed suppressed after settle — session is ${autoState} (trigger=${trigger})`);
461
+ return;
462
+ }
463
+ const activityNow = liveSession ? liveSession.lastActivityAt : null;
464
+ if (activityNow !== activityAtArm
465
+ && (pendingReport.unconfirmedSettleRearms || 0) < IDLE_UNCONFIRMED_SETTLE_MAX_REARMS) {
466
+ pendingReport.unconfirmedSettleRearms = (pendingReport.unconfirmedSettleRearms || 0) + 1;
467
+ console.log(`[AUTO-REPORT] ${targetId} output advanced during settle — re-settling (${pendingReport.unconfirmedSettleRearms}/${IDLE_UNCONFIRMED_SETTLE_MAX_REARMS})`);
468
+ armSettle();
469
+ return;
470
+ }
471
+ pendingReport.unconfirmedSettleDone = true;
472
+ fireAutoReport(targetId, liveSession || targetSession, currentPending, trigger, deps);
473
+ }, settleMs);
474
+ };
475
+ armSettle();
476
+ console.log(`[AUTO-REPORT] ${targetId} idle unconfirmed at ${elapsed}s (trigger=${trigger}) — settling ${IDLE_UNCONFIRMED_SETTLE_SECONDS}s before notify`);
477
+ return;
478
+ }
479
+
480
+ pendingReport.idleNotified = true;
481
+ pendingReport.idleAt = new Date(_now()).toISOString();
482
+
483
+ // Richer bus event (observability) — now also carries the trigger provenance.
484
+ _broadcast('TASK_IDLE_NO_REPORT', targetId, targetSession, {
485
+ extra: {
486
+ source: pendingReport.source,
487
+ inject_id: pendingReport.injectId,
488
+ elapsed_secs: Number(elapsed),
489
+ injected_at: pendingReport.injectedAt,
490
+ trigger
491
+ }
492
+ });
493
+ console.log(`[ENFORCE-REPORT] ${targetId} idle after ${elapsed}s (trigger=${trigger}) — awaiting REPORT from ${pendingReport.source}`);
494
+
495
+ const srcId = _resolveAlias(pendingReport.source) || pendingReport.source;
496
+ const srcSession = _sessions[srcId];
497
+ if (!srcSession) return;
498
+
438
499
  const injTag = pendingReport.injectId ? ` inject=${pendingReport.injectId}` : '';
439
500
  const reportMsg = confirmed
440
501
  ? `TASK_COMPLETE: ${targetId} is now idle after processing inject (${elapsed}s, via ${trigger}${injTag})`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
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 --require ./test-support/setup-env.js --test test/auth.test.js test/http-auth.test.js test/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/daemon-harness-port.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/submit-render-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/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.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 test/provenance.test.js test/inject-audit-broker-seam.test.js test/inject-provenance-daemon.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.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/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/daemon-harness-port.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/submit-render-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/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.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 test/provenance.test.js test/inject-audit-broker-seam.test.js test/inject-provenance-daemon.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.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/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/daemon-harness-port.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/submit-render-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/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.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 test/provenance.test.js test/inject-audit-broker-seam.test.js test/inject-provenance-daemon.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.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/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/daemon-harness-port.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/submit-render-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/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.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 test/idle-unconfirmed-settle.test.js test/provenance.test.js test/inject-audit-broker-seam.test.js test/inject-provenance-daemon.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.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/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/daemon-harness-port.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/submit-render-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/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.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 test/idle-unconfirmed-settle.test.js test/provenance.test.js test/inject-audit-broker-seam.test.js test/inject-provenance-daemon.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.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/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/daemon-harness-port.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/submit-render-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/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.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 test/idle-unconfirmed-settle.test.js test/provenance.test.js test/inject-audit-broker-seam.test.js test/inject-provenance-daemon.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.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
  },