@dmsdc-ai/aigentry-telepty 0.6.8 → 0.6.10
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/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/cli.js +10 -2
- package/daemon.js +81 -8
- package/package.json +4 -4
- package/src/submit-gate.js +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,8 @@ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.10] - 2026-07-05
|
|
8
|
+
|
|
7
9
|
### Added — seamless cross-machine on Tailscale (auto bind + auto trust) (#672)
|
|
8
10
|
|
|
9
11
|
- **On a Tailscale host, a fresh install is cross-machine-ready with zero manual env.** At
|
|
@@ -31,6 +33,22 @@ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
|
31
33
|
Zero-config cross-machine is Tailscale-specific; other topologies use the manual
|
|
32
34
|
`TELEPTY_BIND` path. Addressing stays IP-free — use MagicDNS names with `<id>@<host>`.
|
|
33
35
|
|
|
36
|
+
### Fixed — busy-target `--submit` latency (busy-dispatch fast-path) (#694)
|
|
37
|
+
|
|
38
|
+
- **A gated `--submit` to a BUSY (mid-turn) recipient no longer burns the full gate timeout.**
|
|
39
|
+
A busy claude sits in `working`/`thinking` — neither is a ready state — so the render-gate
|
|
40
|
+
(`awaitReplReady`) could never pass mid-turn and waited out the entire `gate_timeout_ms`
|
|
41
|
+
(default 10s) before dispatching best-effort. A new busy-dispatch fast-path detects a
|
|
42
|
+
**genuine ongoing turn** (`working`/`thinking` held ≥ `TELEPTY_SUBMIT_BUSY_GRACE_MS`, default
|
|
43
|
+
250ms — which excludes the transient `working` a target emits while echoing our own
|
|
44
|
+
just-injected text) and dispatches after only the existing echo+micro-quiet settle
|
|
45
|
+
(`awaitInputSettled`), cutting the busy path from ~10s to **sub-second**.
|
|
46
|
+
- **Never fires blindly, idle path unchanged.** The downstream `gatedTerminalSubmit` still
|
|
47
|
+
runs its own echo+quiet gate before the `\r`; a CR into a busy composer merely queues and the
|
|
48
|
+
#617 hold-and-redeliver loop re-fires it on idle (delivery reliability unchanged). `idle`/
|
|
49
|
+
`waiting` targets never match the fast-path, so the proven idle behavior is byte-for-byte
|
|
50
|
+
identical. Rollback via `TELEPTY_SUBMIT_BUSY_DISPATCH=off`.
|
|
51
|
+
|
|
34
52
|
## [0.6.6] - 2026-06-14
|
|
35
53
|
|
|
36
54
|
### Fixed — duplicate same-id `allow` flap loop + `kill` doesn't stick (#56)
|
package/README.md
CHANGED
package/cli.js
CHANGED
|
@@ -2375,7 +2375,14 @@ async function main() {
|
|
|
2375
2375
|
const target = await resolveSessionTarget(sessionId);
|
|
2376
2376
|
if (!target) {
|
|
2377
2377
|
console.error(`❌ Session '${sessionId}' was not found on any discovered host.`);
|
|
2378
|
-
process.exit(
|
|
2378
|
+
// #691: drain-exit, do NOT process.exit() here. resolveSessionTarget →
|
|
2379
|
+
// discoverSessions() leaves undici keep-alive sockets and AbortSignal.timeout
|
|
2380
|
+
// timers still mid-close; a hard process.exit() races that teardown and trips a
|
|
2381
|
+
// libuv double-close assertion (src/win/async.c:76 UV_HANDLE_CLOSING) on Windows,
|
|
2382
|
+
// which wedges the session. Setting exitCode + returning lets the event loop close
|
|
2383
|
+
// each handle exactly once. Same exit code (1); success path unchanged.
|
|
2384
|
+
process.exitCode = 1;
|
|
2385
|
+
return;
|
|
2379
2386
|
}
|
|
2380
2387
|
|
|
2381
2388
|
let injectPrompt = prompt;
|
|
@@ -2388,7 +2395,8 @@ async function main() {
|
|
|
2388
2395
|
const ent = checkEntitlement({ feature: 'telepty.remote_sessions' });
|
|
2389
2396
|
if (!ent.allowed) {
|
|
2390
2397
|
console.error(`⚠️ ${ent.reason}\n Upgrade: ${ent.upgrade_url}`);
|
|
2391
|
-
process.exit(
|
|
2398
|
+
process.exitCode = 1; // #691: drain-exit (see not-found above), not process.exit() mid-teardown
|
|
2399
|
+
return;
|
|
2392
2400
|
}
|
|
2393
2401
|
|
|
2394
2402
|
if (useRef) {
|
package/daemon.js
CHANGED
|
@@ -53,6 +53,15 @@ const REDELIVER_ENABLED = String(process.env.TELEPTY_REDELIVER || '').toLowerCas
|
|
|
53
53
|
const REDELIVER_MAX_ATTEMPTS = Math.max(1, Number(process.env.TELEPTY_REDELIVER_MAX_ATTEMPTS || 3));
|
|
54
54
|
const REDELIVER_TOTAL_TIMEOUT_MS = Math.max(1000, Number(process.env.TELEPTY_REDELIVER_TOTAL_TIMEOUT_MS || 600000));
|
|
55
55
|
const REDELIVER_IDLE_WAIT_MS = Math.max(1000, Number(process.env.TELEPTY_REDELIVER_IDLE_WAIT_MS || 120000));
|
|
56
|
+
// #694: busy-dispatch fast-path — on a busy (mid-turn) recipient the render-gate (awaitReplReady)
|
|
57
|
+
// can never reach idle/waiting, so it burns the full gate timeout before best-effort dispatch.
|
|
58
|
+
// When enabled, a genuine ongoing turn (working/thinking held ≥ grace) dispatches after only the
|
|
59
|
+
// echo+micro-quiet settle instead of the full timeout. `off` restores the pure-idle-gate behavior.
|
|
60
|
+
const SUBMIT_BUSY_DISPATCH_ENABLED = String(process.env.TELEPTY_SUBMIT_BUSY_DISPATCH || '').toLowerCase() !== 'off';
|
|
61
|
+
// Grace floor separating a REAL ongoing turn from the transient `working` a target emits while
|
|
62
|
+
// echoing our OWN just-injected text (duration_ms ≈ 0). Default 250ms: above the echo transient,
|
|
63
|
+
// below any human-perceptible latency.
|
|
64
|
+
const SUBMIT_BUSY_GRACE_MS = Math.max(0, Number(process.env.TELEPTY_SUBMIT_BUSY_GRACE_MS || 250));
|
|
56
65
|
|
|
57
66
|
// Session state machine manager — auto-detects session state from PTY output
|
|
58
67
|
const sessionStateManager = new SessionStateManager({
|
|
@@ -1790,6 +1799,26 @@ function forceSubmitDeliveredToSurface(session, strategy) {
|
|
|
1790
1799
|
return !!strategy;
|
|
1791
1800
|
}
|
|
1792
1801
|
|
|
1802
|
+
// #678: which submit render-gate outcomes are TERMINAL (hard-fail 504, never dispatch the CR)
|
|
1803
|
+
// vs best-effort DISPATCH (fire the CR anyway). A missing render-state — `no_state` (no state
|
|
1804
|
+
// machine registered for the session) or `no_state_manager` — is NOT terminal: it says nothing
|
|
1805
|
+
// about PTY/ownerWs liveness. A wrapped session restored across a daemon restart has no machine
|
|
1806
|
+
// yet a perfectly live bridge/PTY, so we dispatch best-effort exactly like `timeout` (and if the
|
|
1807
|
+
// PTY/ownerWs really is gone, submitViaPty returns false → a clean 503, not a false success).
|
|
1808
|
+
// Only genuinely-terminal PTY states — session_dead / session_error / session_restarting — still
|
|
1809
|
+
// short-circuit, because writing a CR to a dead/errored/restarting PTY is pointless.
|
|
1810
|
+
// #694: `busy_settled` (busy-dispatch fast-path) joins the best-effort DISPATCH set — the target is
|
|
1811
|
+
// alive and mid-turn, so firing the CR is correct (it queues + #617 redelivers), never a hard-fail.
|
|
1812
|
+
const NON_TERMINAL_GATE_REASONS = new Set(['timeout', 'no_state', 'no_state_manager', 'busy_settled']);
|
|
1813
|
+
function isTerminalGateFailure(gateResult) {
|
|
1814
|
+
return !!(
|
|
1815
|
+
gateResult &&
|
|
1816
|
+
gateResult.ready !== true &&
|
|
1817
|
+
gateResult.reason &&
|
|
1818
|
+
!NON_TERMINAL_GATE_REASONS.has(gateResult.reason)
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1793
1822
|
async function deliverInjectionToSession(id, session, prompt, options = {}) {
|
|
1794
1823
|
const now = Date.now();
|
|
1795
1824
|
if (!options.bypassBootstrapQueue && shouldQueueBootstrapOperation(session)) {
|
|
@@ -2066,6 +2095,11 @@ for (const [id, meta] of Object.entries(_persisted)) {
|
|
|
2066
2095
|
if (!restored) continue;
|
|
2067
2096
|
sessions[id] = restored;
|
|
2068
2097
|
initializeBootstrapState(sessions[id]);
|
|
2098
|
+
// #678: a session restored across a daemon restart must get a render-state machine,
|
|
2099
|
+
// else the submit gate reads getState()=null → no_state and never fires the CR. The
|
|
2100
|
+
// machine is otherwise created only at first register(), which the reconnecting bridge's
|
|
2101
|
+
// idempotent re-register skips (see the /api/sessions/register early-return below).
|
|
2102
|
+
sessionStateManager.register(id);
|
|
2069
2103
|
console.log(`[PERSIST] Restored session ${id} (awaiting reconnect)`);
|
|
2070
2104
|
}
|
|
2071
2105
|
const STRIPPED_SESSION_ENV_KEYS = [
|
|
@@ -2282,6 +2316,12 @@ app.post('/api/sessions/register', (req, res) => {
|
|
|
2282
2316
|
// on a metadata re-register, or a session's delivered bytes would change mid-flight.
|
|
2283
2317
|
if (req.body.provenance_capable === true) existing.provenanceCapable = true;
|
|
2284
2318
|
existing.provenanceNonce = ensureSessionNonce(session_id);
|
|
2319
|
+
// #678: a bridge reconnecting to an already-known session (e.g. one restored across a
|
|
2320
|
+
// daemon restart, or a re-`allow` of the same id) must still end up with a render-state
|
|
2321
|
+
// machine. register() is idempotent (returns the existing machine untouched), so this is
|
|
2322
|
+
// safe for the normal re-register too — and it is the ONLY thing that recreates the
|
|
2323
|
+
// machine for a same-id reconnect, since a daemon restart is not required to reach here.
|
|
2324
|
+
sessionStateManager.register(session_id);
|
|
2285
2325
|
console.log(`[REGISTER] Re-registered session ${session_id} (type: ${existing.type}, updated metadata)`);
|
|
2286
2326
|
return res.status(200).json({ session_id, type: existing.type, command: existing.command, cwd: existing.cwd, reregistered: true, session_token: mintSessionToken(session_id), session_nonce: existing.provenanceNonce, provenance_capable: !!existing.provenanceCapable });
|
|
2287
2327
|
}
|
|
@@ -2814,8 +2854,10 @@ function submitViaOsascript(sessionId, keyCombo) {
|
|
|
2814
2854
|
// POST /api/sessions/:id/submit — render-gated CLI-aware submit
|
|
2815
2855
|
//
|
|
2816
2856
|
// Default behavior (0.3.0+): wait for the target REPL to be ready (sessionStateManager
|
|
2817
|
-
// reports `idle`/`waiting` with confidence ≥ 0.
|
|
2818
|
-
//
|
|
2857
|
+
// reports `idle`/`waiting` with confidence ≥ 0.5, the awaitReplReady default) before firing
|
|
2858
|
+
// Enter. #694: a BUSY recipient (working/thinking) is never idle/waiting, so the busy-dispatch
|
|
2859
|
+
// fast-path below dispatches best-effort after an echo+quiet settle instead of burning the gate
|
|
2860
|
+
// timeout. When the caller passes `injected_body`, also verify the body has been consumed (i.e.
|
|
2819
2861
|
// disappeared from the input box) by polling the session output ring; if still
|
|
2820
2862
|
// visible, perform one bounded retry.
|
|
2821
2863
|
//
|
|
@@ -3015,12 +3057,40 @@ app.post('/api/sessions/:id/submit', async (req, res) => {
|
|
|
3015
3057
|
// Hard-fail reasons (session_dead/error/restarting/no_state/no_state_manager)
|
|
3016
3058
|
// still short-circuit to 504 because dispatching to a dead/missing PTY is
|
|
3017
3059
|
// pointless. See spec §1.3 / §3.3.
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3060
|
+
//
|
|
3061
|
+
// #694 busy-dispatch fast-path: a BUSY (mid-turn) recipient sits in working/thinking — neither
|
|
3062
|
+
// is a READY_STATE — so awaitReplReady can never pass mid-turn and would burn the FULL
|
|
3063
|
+
// gateTimeoutMs (up to 10s) before best-effort dispatch. When the target is a GENUINE ongoing
|
|
3064
|
+
// turn (isBusyDispatchState: working/thinking held ≥ SUBMIT_BUSY_GRACE_MS, which excludes the
|
|
3065
|
+
// transient `working` from echoing our OWN just-injected text, duration_ms ≈ 0), wait ONLY for
|
|
3066
|
+
// the input to settle (body echoed + micro-quiet, via awaitInputSettled) and dispatch
|
|
3067
|
+
// best-effort — the same downstream path as a gate timeout, but in ~hundreds of ms. idle/waiting
|
|
3068
|
+
// never match isBusyDispatchState, so the idle path is byte-unchanged. gatedTerminalSubmit below
|
|
3069
|
+
// still runs its OWN echo+quiet gate before the \r (never fires blindly), and a CR into a busy
|
|
3070
|
+
// composer merely queues → #617 redeliver fires it on idle. Rollback: TELEPTY_SUBMIT_BUSY_DISPATCH=off.
|
|
3071
|
+
let gateResult = null;
|
|
3072
|
+
if (injectedBody && SUBMIT_BUSY_DISPATCH_ENABLED) {
|
|
3073
|
+
const cur = sessionStateManager.getState(id);
|
|
3074
|
+
if (submitGate.isBusyDispatchState(cur, SUBMIT_BUSY_GRACE_MS)) {
|
|
3075
|
+
const settle = await submitGate.awaitInputSettled(session, injectedBody, {
|
|
3076
|
+
timeoutMs: 1200,
|
|
3077
|
+
quietWindowMs: 100,
|
|
3078
|
+
echoGraceMs: 400,
|
|
3079
|
+
pollIntervalMs: 30,
|
|
3080
|
+
stripAnsi: stripAnsiState,
|
|
3081
|
+
});
|
|
3082
|
+
gateResult = { ready: false, reason: 'busy_settled', last_state: cur.state, waited_ms: settle.waited_ms };
|
|
3083
|
+
console.log(`[SUBMIT] busy-dispatch fast-path ${id}: state=${cur.state} (${cur.duration_ms}ms) settle=${settle.reason} (${settle.waited_ms}ms) — dispatching without idle-gate burn`);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
if (!gateResult) {
|
|
3087
|
+
gateResult = await submitGate.awaitReplReady(id, sessionStateManager, {
|
|
3088
|
+
timeoutMs: gateTimeoutMs,
|
|
3089
|
+
...(minConfidence !== undefined ? { minConfidence } : {}),
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3022
3092
|
const gatedDispatchAfterTimeout = !gateResult.ready;
|
|
3023
|
-
if (
|
|
3093
|
+
if (isTerminalGateFailure(gateResult)) {
|
|
3024
3094
|
console.log(`[SUBMIT] gate hard-fail ${id}: ${gateResult.reason} (last_state=${gateResult.last_state})`);
|
|
3025
3095
|
return res.status(504).json({
|
|
3026
3096
|
error: 'Submit gated-timeout — target REPL not in a dispatchable state',
|
|
@@ -3034,7 +3104,9 @@ app.post('/api/sessions/:id/submit', async (req, res) => {
|
|
|
3034
3104
|
});
|
|
3035
3105
|
}
|
|
3036
3106
|
if (gatedDispatchAfterTimeout) {
|
|
3037
|
-
|
|
3107
|
+
// #678: `no_state`/`no_state_manager` join `timeout` here — dispatch the CR best-effort
|
|
3108
|
+
// rather than hard-fail, so a live-bridge session with no render-state machine still submits.
|
|
3109
|
+
console.log(`[SUBMIT] gate ${gateResult.reason || 'not-ready'} ${id}: dispatching anyway (last_state=${gateResult.last_state})`);
|
|
3038
3110
|
}
|
|
3039
3111
|
|
|
3040
3112
|
// Step 2: dispatch Enter via the PTY/context path, render-gated (#568).
|
|
@@ -4523,6 +4595,7 @@ module.exports = {
|
|
|
4523
4595
|
fireAutoReport, // #32: provenance-tagged auto-report (deps DI: now/deliver/...)
|
|
4524
4596
|
maybeRecordInjectConsumption, // #619: durable early-consumption fact capture (idle-gate decay-proofing)
|
|
4525
4597
|
forceSubmitDeliveredToSurface, // #544/#537/Bug B: PTY-native force-confirm (pty_cr = delivered)
|
|
4598
|
+
isTerminalGateFailure, // #678: submit-gate disposition — no_state is best-effort dispatch, not hard-fail
|
|
4526
4599
|
terminalLevelSubmit, // #544: PTY-only submit path (pty_cr | null)
|
|
4527
4600
|
submitViaPty, // #544: bare-0x0D submit into the innermost node-pty
|
|
4528
4601
|
runSubmitAll, // #546: submit-all via PTY for every backend (no cmux send-key)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"postinstall": "node scripts/postinstall.js",
|
|
39
39
|
"preuninstall": "node scripts/preuninstall.js",
|
|
40
|
-
"test": "node --require ./test-support/setup-env.js --test test/auth.test.js test/http-auth.test.js test/tailnet-autobind.test.js test/win-firewall.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/daemon-bind-default.test.js test/integration/daemon-launch.test.js test/cli.test.js test/subcommand-help.test.js test/telepty-kill.test.js test/dupid-flap-kill-stick-56.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/inject-consumption-evidence.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/uninstall.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/daemon-restart-fallback-15.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/daemon-lifecycle-55.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/idle-unconfirmed-consumption.test.js test/idle-unconfirmed-decayed-619.test.js test/inject-redeliver.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
|
-
"test:watch": "node --require ./test-support/setup-env.js --test --watch test/auth.test.js test/http-auth.test.js test/tailnet-autobind.test.js test/win-firewall.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/daemon-bind-default.test.js test/integration/daemon-launch.test.js test/cli.test.js test/subcommand-help.test.js test/telepty-kill.test.js test/dupid-flap-kill-stick-56.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/inject-consumption-evidence.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/uninstall.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/daemon-restart-fallback-15.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/daemon-lifecycle-55.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/idle-unconfirmed-consumption.test.js test/idle-unconfirmed-decayed-619.test.js test/inject-redeliver.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",
|
|
42
|
-
"test:ci": "node --require ./test-support/setup-env.js --test --test-reporter=spec test/auth.test.js test/http-auth.test.js test/tailnet-autobind.test.js test/win-firewall.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/daemon-bind-default.test.js test/integration/daemon-launch.test.js test/cli.test.js test/subcommand-help.test.js test/telepty-kill.test.js test/dupid-flap-kill-stick-56.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/inject-consumption-evidence.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/uninstall.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/daemon-restart-fallback-15.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/daemon-lifecycle-55.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/idle-unconfirmed-consumption.test.js test/idle-unconfirmed-decayed-619.test.js test/inject-redeliver.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/",
|
|
40
|
+
"test": "node --require ./test-support/setup-env.js --test test/auth.test.js test/http-auth.test.js test/tailnet-autobind.test.js test/win-firewall.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/daemon-bind-default.test.js test/integration/daemon-launch.test.js test/cli.test.js test/subcommand-help.test.js test/telepty-kill.test.js test/dupid-flap-kill-stick-56.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/submit-gate-restore-register-678.test.js test/submit-busy-dispatch-694.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/inject-consumption-evidence.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/uninstall.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/daemon-restart-fallback-15.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/daemon-lifecycle-55.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/idle-unconfirmed-consumption.test.js test/idle-unconfirmed-decayed-619.test.js test/inject-redeliver.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 test/inject-notfound-teardown-691.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
41
|
+
"test:watch": "node --require ./test-support/setup-env.js --test --watch test/auth.test.js test/http-auth.test.js test/tailnet-autobind.test.js test/win-firewall.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/daemon-bind-default.test.js test/integration/daemon-launch.test.js test/cli.test.js test/subcommand-help.test.js test/telepty-kill.test.js test/dupid-flap-kill-stick-56.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/submit-gate-restore-register-678.test.js test/submit-busy-dispatch-694.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/inject-consumption-evidence.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/uninstall.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/daemon-restart-fallback-15.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/daemon-lifecycle-55.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/idle-unconfirmed-consumption.test.js test/idle-unconfirmed-decayed-619.test.js test/inject-redeliver.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 test/inject-notfound-teardown-691.test.js",
|
|
42
|
+
"test:ci": "node --require ./test-support/setup-env.js --test --test-reporter=spec test/auth.test.js test/http-auth.test.js test/tailnet-autobind.test.js test/win-firewall.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/daemon-bind-default.test.js test/integration/daemon-launch.test.js test/cli.test.js test/subcommand-help.test.js test/telepty-kill.test.js test/dupid-flap-kill-stick-56.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/submit-gate-restore-register-678.test.js test/submit-busy-dispatch-694.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/inject-consumption-evidence.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/uninstall.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/daemon-restart-fallback-15.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/daemon-lifecycle-55.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/idle-unconfirmed-consumption.test.js test/idle-unconfirmed-decayed-619.test.js test/inject-redeliver.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 test/inject-notfound-teardown-691.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
44
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
45
45
|
},
|
package/src/submit-gate.js
CHANGED
|
@@ -35,6 +35,19 @@ function isFailed(state) {
|
|
|
35
35
|
return !!(state && FAIL_STATES.has(state.state));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// #694: a "busy-dispatch" state — the recipient is mid-turn (working/thinking, the
|
|
39
|
+
// ACCEPTED_AFTER_SUBMIT_STATES) and has been for at least graceMs. Used by the submit gate to
|
|
40
|
+
// short-circuit the idle-wait on a genuinely-busy target: working/thinking are NOT READY_STATES,
|
|
41
|
+
// so awaitReplReady can never pass mid-turn and would burn its full timeout before best-effort
|
|
42
|
+
// dispatch. The graceMs floor is what distinguishes a REAL ongoing turn from the transient
|
|
43
|
+
// `working` a target emits while echoing our OWN just-injected text (duration_ms ≈ 0) — so a
|
|
44
|
+
// fresh inject never trips the fast-path prematurely, and idle/waiting never match at all.
|
|
45
|
+
function isBusyDispatchState(state, graceMs) {
|
|
46
|
+
if (!state || !ACCEPTED_AFTER_SUBMIT_STATES.has(state.state)) return false;
|
|
47
|
+
const grace = Number.isFinite(graceMs) ? graceMs : 0;
|
|
48
|
+
return Number.isFinite(state.duration_ms) && state.duration_ms >= grace;
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
/**
|
|
39
52
|
* Wait until the session's REPL is ready to accept Enter.
|
|
40
53
|
*
|
|
@@ -784,6 +797,7 @@ module.exports = {
|
|
|
784
797
|
defaultReadScreen,
|
|
785
798
|
isReady,
|
|
786
799
|
isFailed,
|
|
800
|
+
isBusyDispatchState,
|
|
787
801
|
READY_STATES,
|
|
788
802
|
FAIL_STATES,
|
|
789
803
|
ACCEPTED_AFTER_SUBMIT_STATES,
|