@dmsdc-ai/aigentry-telepty 0.6.14 → 0.6.15
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 +9 -0
- package/README.md +1 -1
- package/cli.js +14 -1
- package/daemon.js +3 -1
- package/package.json +4 -4
- package/src/mailbox/bridge-flush-filter.js +59 -0
- package/src/transport/websocket.js +13 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.6.15 — 2026-07-12
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **#724** Cross-host `telepty attach <sid>@<host>` failed with `Unexpected server response: 200` while local attach worked. On the AUTO_TAILNET path the daemon opens two sockets on the API port (loopback primary + additive tailnet IP), but the WS `upgrade` handler was bound to the loopback listener only — a cross-host handshake hit the tailnet socket, fell through to Express `GET /api/sessions/:id`, and got HTTP 200 instead of 101. The shared upgrade handler is now attached to both listeners. Auth unchanged (`isAllowedPeer || token || jwt`, identical to inject/read-screen); a new regression test asserts both listeners upgrade to 101 and an unauthorized cross-host upgrade still rejects (401).
|
|
9
|
+
- **#720** Bridge mailbox flush now drops stale parked injects (TTL, default 600s, env `TELEPTY_BRIDGE_INJECT_TTL_SECS`) and collapses consecutive duplicate payloads before delivery, with `[BRIDGE] dropped <reason>` logs. Previously every parked inject flushed on gate-open regardless of age or repetition.
|
|
10
|
+
|
|
11
|
+
### Rollout
|
|
12
|
+
- Both daemon-side. #724 requires a daemon restart on the **owning** node (the one hosting the session being attached); the requesting node needs no change.
|
|
13
|
+
|
|
5
14
|
## 0.6.14 — 2026-07-12
|
|
6
15
|
|
|
7
16
|
### Fixed
|
package/README.md
CHANGED
|
@@ -305,7 +305,7 @@ telepty runs **standalone** — it needs none of the other aigentry modules and
|
|
|
305
305
|
|
|
306
306
|
| Module | Package | Version | Role | Maturity |
|
|
307
307
|
| --- | --- | --- | --- | --- |
|
|
308
|
-
| **telepty** | `@dmsdc-ai/aigentry-telepty` | 0.6.
|
|
308
|
+
| **telepty** | `@dmsdc-ai/aigentry-telepty` | 0.6.15 | Cross-terminal / cross-machine prompt transport (PTY daemon) | Shipping |
|
|
309
309
|
| **brain** | `@dmsdc-ai/aigentry-brain` | 0.2.8 | Persistent cross-session memory (MCP server) | Early |
|
|
310
310
|
| **deliberation** | `@dmsdc-ai/aigentry-deliberation` | 0.0.47 | Multi-AI structured debate + synthesis (MCP server) | Early |
|
|
311
311
|
| **devkit** | `@dmsdc-ai/aigentry-devkit` | 0.0.22 | Installer/scaffold for the AI dev environment | Early |
|
package/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ const { decideVersionAction } = require('./src/version-handshake');
|
|
|
38
38
|
const crossMachine = require('./cross-machine');
|
|
39
39
|
const { parseHostSpec, buildDaemonUrl, buildDaemonWsUrl } = require('./host-spec');
|
|
40
40
|
const { FileMailbox } = require('./src/mailbox/index');
|
|
41
|
+
const { filterBridgeBatch, bridgeInjectTtlSecs } = require('./src/mailbox/bridge-flush-filter');
|
|
41
42
|
const readyRegistry = require('./src/prompt-symbol-registry');
|
|
42
43
|
const lifecycle = require('./src/lifecycle');
|
|
43
44
|
const args = process.argv.slice(2);
|
|
@@ -1797,7 +1798,19 @@ async function main() {
|
|
|
1797
1798
|
batch.push(msg);
|
|
1798
1799
|
}
|
|
1799
1800
|
if (batch.length === 0) { bridgePendingCount = 0; return; }
|
|
1800
|
-
|
|
1801
|
+
// #720: drop stale-parked and consecutive-duplicate injects before writing,
|
|
1802
|
+
// so a long-closed gate does not deliver the same question N times or replay
|
|
1803
|
+
// an inject that went stale while parked.
|
|
1804
|
+
const nowSecs = Math.floor(Date.now() / 1000);
|
|
1805
|
+
const { deliver, dropped } = filterBridgeBatch(batch, {
|
|
1806
|
+
ttlSecs: bridgeInjectTtlSecs(process.env), nowSecs,
|
|
1807
|
+
});
|
|
1808
|
+
for (const { msg, reason } of dropped) {
|
|
1809
|
+
// Ack so the dropped inject clears in_flight instead of lingering; never write it.
|
|
1810
|
+
try { bridgeMailbox.ack(bridgeTarget, msg.msg_id); } catch {}
|
|
1811
|
+
console.warn(`\x1b[33m[BRIDGE] dropped ${reason} inject ${msg.msg_id} (age=${nowSecs - msg.created_at}s)\x1b[0m`);
|
|
1812
|
+
}
|
|
1813
|
+
for (const msg of deliver) {
|
|
1801
1814
|
const text = msg.payload;
|
|
1802
1815
|
const msgId = msg.msg_id;
|
|
1803
1816
|
setTimeout(() => {
|
package/daemon.js
CHANGED
|
@@ -4185,6 +4185,7 @@ function startNodeBrokerClient(deps = {}) {
|
|
|
4185
4185
|
// cli.js, never this module — hence the explicit AIGENTRY_TELEPTY_DAEMON_MAIN signal. Guarding on
|
|
4186
4186
|
// require.main ALONE (0.5.0 regression) meant app.listen never ran in production → daemon exited 0.
|
|
4187
4187
|
let server;
|
|
4188
|
+
let tailnetServer; // additive tailnet listener (AUTO_TAILNET path); needs the WS upgrade handler too
|
|
4188
4189
|
let nodeBrokerClient = null;
|
|
4189
4190
|
if (require.main === module || process.env.AIGENTRY_TELEPTY_DAEMON_MAIN === '1') {
|
|
4190
4191
|
if (brokerServer) {
|
|
@@ -4222,7 +4223,7 @@ if (require.main === module || process.env.AIGENTRY_TELEPTY_DAEMON_MAIN === '1')
|
|
|
4222
4223
|
// socket on the same app. ponytail: fixed-PORT production shares one port on both
|
|
4223
4224
|
// listeners; a PORT=0 ephemeral run would split ports, but the auto path is never
|
|
4224
4225
|
// taken under PORT=0 in the suite (TELEPTY_NO_TAILNET_AUTO=1 default in setup-env.js).
|
|
4225
|
-
|
|
4226
|
+
tailnetServer = app.listen(Number(PORT), TAILNET_IP);
|
|
4226
4227
|
tailnetServer.on('error', (e) => {
|
|
4227
4228
|
console.warn(`[BIND] tailnet listener ${TAILNET_IP}:${Number(PORT)} unavailable (staying loopback-only): ${e && e.message}`);
|
|
4228
4229
|
});
|
|
@@ -4544,6 +4545,7 @@ const busClients = new Set();
|
|
|
4544
4545
|
|
|
4545
4546
|
installWebSocketTransport({
|
|
4546
4547
|
server,
|
|
4548
|
+
tailnetServer,
|
|
4547
4549
|
sessions,
|
|
4548
4550
|
busClients,
|
|
4549
4551
|
expectedToken: EXPECTED_TOKEN,
|
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.15",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"prepublishOnly": "node scripts/gen-readme.mjs",
|
|
39
39
|
"postinstall": "node scripts/postinstall.js",
|
|
40
40
|
"preuninstall": "node scripts/preuninstall.js",
|
|
41
|
-
"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/read-screen-ansi-715.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-bracketed-paste-716.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/",
|
|
42
|
-
"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/read-screen-ansi-715.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-bracketed-paste-716.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",
|
|
43
|
-
"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/read-screen-ansi-715.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-bracketed-paste-716.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": "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/read-screen-ansi-715.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/attach-crosshost-upgrade.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-bracketed-paste-716.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/",
|
|
42
|
+
"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/read-screen-ansi-715.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/attach-crosshost-upgrade.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-bracketed-paste-716.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",
|
|
43
|
+
"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/read-screen-ansi-715.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/attach-crosshost-upgrade.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-bracketed-paste-716.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/",
|
|
44
44
|
"typecheck": "tsc --noEmit",
|
|
45
45
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
46
46
|
},
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// #720 — bridge mailbox flush-path protections.
|
|
4
|
+
//
|
|
5
|
+
// When the bridge promptReady gate stays closed for a long window, every inject
|
|
6
|
+
// PARKS in the bridge mailbox with a UNIQUE msg_id, so the mailbox's own msg_id
|
|
7
|
+
// dedup can't collapse identical content. On gate-open, flushBridgeMailbox
|
|
8
|
+
// dequeues the whole batch — without these filters it delivers stale copies and
|
|
9
|
+
// every consecutive duplicate (observed live: same question answered 3×).
|
|
10
|
+
//
|
|
11
|
+
// Pure functions (no I/O) so the flush logic is unit-testable in isolation.
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TTL_SECS = 600; // 10 min
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the bridge-flush inject TTL (seconds) from env.
|
|
17
|
+
* Distinct from the mailbox's 24h ttlSecs (daemon delivery) — this is a
|
|
18
|
+
* bridge-flush-local staleness bound. Invalid / non-positive → default.
|
|
19
|
+
*/
|
|
20
|
+
function bridgeInjectTtlSecs(env = process.env) {
|
|
21
|
+
const raw = env.TELEPTY_BRIDGE_INJECT_TTL_SECS;
|
|
22
|
+
const n = Number.parseInt(raw, 10);
|
|
23
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_TTL_SECS;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Filter a dequeued bridge batch (oldest-first) into what to deliver vs drop.
|
|
28
|
+
*
|
|
29
|
+
* Single ordered pass:
|
|
30
|
+
* 1. TTL — nowSecs - created_at > ttlSecs → drop, reason 'stale'.
|
|
31
|
+
* 2. Dedup — payload identical to the last DELIVERED payload → drop, reason
|
|
32
|
+
* 'duplicate'. Consecutive-only: a re-ask after a different message
|
|
33
|
+
* (Q,R,Q) survives, and a stale drop does not reset the anchor so
|
|
34
|
+
* Q,Q(stale),Q still collapses the trailing duplicate.
|
|
35
|
+
*
|
|
36
|
+
* @returns {{ deliver: object[], dropped: {msg: object, reason: string}[] }}
|
|
37
|
+
*/
|
|
38
|
+
function filterBridgeBatch(batch, { ttlSecs, nowSecs }) {
|
|
39
|
+
const deliver = [];
|
|
40
|
+
const dropped = [];
|
|
41
|
+
let lastDeliveredPayload;
|
|
42
|
+
let haveDelivered = false;
|
|
43
|
+
for (const msg of batch) {
|
|
44
|
+
if (nowSecs - msg.created_at > ttlSecs) {
|
|
45
|
+
dropped.push({ msg, reason: 'stale' });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (haveDelivered && msg.payload === lastDeliveredPayload) {
|
|
49
|
+
dropped.push({ msg, reason: 'duplicate' });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
deliver.push(msg);
|
|
53
|
+
lastDeliveredPayload = msg.payload;
|
|
54
|
+
haveDelivered = true;
|
|
55
|
+
}
|
|
56
|
+
return { deliver, dropped };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { filterBridgeBatch, bridgeInjectTtlSecs, DEFAULT_TTL_SECS };
|
|
@@ -10,6 +10,7 @@ function isOpenWebSocket(ws) {
|
|
|
10
10
|
function installWebSocketTransport(deps) {
|
|
11
11
|
const {
|
|
12
12
|
server,
|
|
13
|
+
tailnetServer,
|
|
13
14
|
sessions,
|
|
14
15
|
busClients,
|
|
15
16
|
expectedToken,
|
|
@@ -281,7 +282,12 @@ function installWebSocketTransport(deps) {
|
|
|
281
282
|
});
|
|
282
283
|
});
|
|
283
284
|
|
|
284
|
-
|
|
285
|
+
// Shared upgrade handler. Attached to every provided listener so a cross-host attach reaching
|
|
286
|
+
// the additive tailnet socket (daemon.js:4225) upgrades exactly like loopback — the auth check
|
|
287
|
+
// (isAllowedPeer || token || jwt) is identical to the inject/read-screen HTTP path, so attach
|
|
288
|
+
// gains no reach those already lack. Was bound to loopback only → tailnet WS fell through to
|
|
289
|
+
// Express GET /api/sessions/:id → HTTP 200 ("Unexpected server response: 200").
|
|
290
|
+
function handleUpgrade(req, socket, head) {
|
|
285
291
|
const url = new URL(req.url, 'http://' + req.headers.host);
|
|
286
292
|
const token = url.searchParams.get('token');
|
|
287
293
|
|
|
@@ -304,9 +310,13 @@ function installWebSocketTransport(deps) {
|
|
|
304
310
|
} else {
|
|
305
311
|
socket.destroy();
|
|
306
312
|
}
|
|
307
|
-
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for (const listener of [server, tailnetServer]) {
|
|
316
|
+
if (listener) listener.on('upgrade', handleUpgrade);
|
|
317
|
+
}
|
|
308
318
|
|
|
309
|
-
return { wss, busWss };
|
|
319
|
+
return { wss, busWss, handleUpgrade };
|
|
310
320
|
}
|
|
311
321
|
|
|
312
322
|
module.exports = {
|