@dmsdc-ai/aigentry-telepty 0.6.11 → 0.6.12
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 +10 -0
- package/README.md +17 -0
- package/README.tmpl.md +310 -0
- package/daemon.js +33 -26
- package/package.json +5 -4
- package/session-state.js +4 -1
- package/src/prompt-symbol-registry.js +4 -1
- package/src/screen-ansi.js +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.6.12 — 2026-07-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **#715** `read-screen` leaked modern escape sequences (kitty keyboard protocol, DECSCUSR, colon sub-parameter SGR, DECRQM) as literal text — codex/claude screens showed `0 q`/`<u>1u`/`4:2m` garbage. The screen ANSI stripper now consumes the full ECMA-48 CSI grammar (params `0x30–0x3F`, intermediates `0x20–0x2F`, final `0x40–0x7E`), extracted to `src/screen-ansi.js` with a regression suite.
|
|
9
|
+
- **#716** `inject --submit` into codex never registered: codex's composer paste-burst detection swallowed a CR coalesced with the injected text. Paste-capable CLIs (detected via `ESC[?2004h`) now receive the text wrapped in bracketed paste (`ESC[200~ … ESC[201~`) with the submit CR written separately outside the envelope — submission is timing-independent. Non-paste CLIs are byte-identical. Live-validated on codex 0.142.5.
|
|
10
|
+
- **#713 (partial)** The same ECMA-48 gap in `session-state.js` / `prompt-symbol-registry.js` broke prompt/consumption detection for claude v2.1.198 (which emits kitty-protocol sequences every render). Both matchers aligned to the corrected CSI form. Note: fresh claude v2.1.198 sessions still gate on bridge `promptReady` prompt-symbol detection over the raw stream, which the new absolute-positioning composer defeats — full fix tracked separately.
|
|
11
|
+
|
|
12
|
+
### Rollout
|
|
13
|
+
- #715/#716 are daemon-side (daemon restart only). #713's bridge-side detection improvement additionally applies to newly started `telepty allow` bridges.
|
|
14
|
+
|
|
5
15
|
## [Unreleased]
|
|
6
16
|
|
|
7
17
|
## [0.6.11] - 2026-07-05
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Connect any terminal to any terminal, any machine.**
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
telepty is a PTY orchestration daemon and session bridge for AI CLI workflows. It lets you spawn, attach to, and inject commands into terminal sessions — locally or across machines via Tailscale.
|
|
6
8
|
|
|
7
9
|
Built for AI CLI workflows (Claude Code, Codex, Gemini CLI), but works with any interactive terminal program.
|
|
@@ -297,6 +299,21 @@ npm test # 70 tests (node:test)
|
|
|
297
299
|
npm run test:watch # Watch mode
|
|
298
300
|
```
|
|
299
301
|
|
|
302
|
+
## Ecosystem
|
|
303
|
+
|
|
304
|
+
telepty runs **standalone** — it needs none of the other aigentry modules and installs with the single command above. It is also the transport layer of the broader **aigentry** ecosystem, whose modules are each independently published and independently useful:
|
|
305
|
+
|
|
306
|
+
| Module | Package | Version | Role | Maturity |
|
|
307
|
+
| --- | --- | --- | --- | --- |
|
|
308
|
+
| **telepty** | `@dmsdc-ai/aigentry-telepty` | 0.6.12 | Cross-terminal / cross-machine prompt transport (PTY daemon) | Shipping |
|
|
309
|
+
| **brain** | `@dmsdc-ai/aigentry-brain` | 0.2.8 | Persistent cross-session memory (MCP server) | Early |
|
|
310
|
+
| **deliberation** | `@dmsdc-ai/aigentry-deliberation` | 0.0.47 | Multi-AI structured debate + synthesis (MCP server) | Early |
|
|
311
|
+
| **devkit** | `@dmsdc-ai/aigentry-devkit` | 0.0.22 | Installer/scaffold for the AI dev environment | Early |
|
|
312
|
+
| **aterm** | `@dmsdc-ai/aterm` | 0.2.14 | Terminal launcher with native session IPC | Early |
|
|
313
|
+
| **orchestrator** | *(unpublished)* | — | Control tower that drives sessions via telepty | Internal |
|
|
314
|
+
|
|
315
|
+
> Licenses: all MIT except `@dmsdc-ai/aterm` (UNLICENSED).
|
|
316
|
+
|
|
300
317
|
## License
|
|
301
318
|
|
|
302
319
|
MIT
|
package/README.tmpl.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# telepty
|
|
2
|
+
|
|
3
|
+
**Connect any terminal to any terminal, any machine.**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
telepty is a PTY orchestration daemon and session bridge for AI CLI workflows. It lets you spawn, attach to, and inject commands into terminal sessions — locally or across machines via Tailscale.
|
|
8
|
+
|
|
9
|
+
Built for AI CLI workflows (Claude Code, Codex, Gemini CLI), but works with any interactive terminal program.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# macOS / Linux
|
|
15
|
+
curl -fsSL https://raw.githubusercontent.com/dmsdc-ai/aigentry-telepty/main/install.sh | bash
|
|
16
|
+
|
|
17
|
+
# Windows (PowerShell as Admin)
|
|
18
|
+
iwr -useb https://raw.githubusercontent.com/dmsdc-ai/aigentry-telepty/main/install.ps1 | iex
|
|
19
|
+
|
|
20
|
+
# Or via npm
|
|
21
|
+
npm install -g @dmsdc-ai/aigentry-telepty
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The installer sets up telepty as a background service (`launchd` on macOS, `systemd` on Linux, detached process on Windows).
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 1. Start the daemon
|
|
30
|
+
telepty daemon
|
|
31
|
+
|
|
32
|
+
# 2. Wrap an existing CLI session for remote control
|
|
33
|
+
telepty allow --id my-session claude
|
|
34
|
+
|
|
35
|
+
# 3. List active sessions (local + Tailnet)
|
|
36
|
+
telepty list
|
|
37
|
+
|
|
38
|
+
# 4. Inject a prompt into a session
|
|
39
|
+
telepty inject my-session "explain this codebase"
|
|
40
|
+
|
|
41
|
+
# 5. Attach to a session interactively
|
|
42
|
+
telepty attach my-session
|
|
43
|
+
|
|
44
|
+
# 6. Broadcast to all sessions
|
|
45
|
+
telepty broadcast "status report"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What telepty is — and what it is not
|
|
49
|
+
|
|
50
|
+
telepty is a **PTY orchestration daemon for AI CLI workflows**.
|
|
51
|
+
It is **not** a terminal multiplexer and does not replace tmux.
|
|
52
|
+
|
|
53
|
+
> **tmux is better at being a terminal. telepty is better at letting
|
|
54
|
+
> software operate many terminals.**
|
|
55
|
+
|
|
56
|
+
tmux owns terminal *fidelity*: panes, windows, full VT emulation, scrollback,
|
|
57
|
+
copy-mode, capture-pane, local Unix-socket operation, zero runtime deps.
|
|
58
|
+
telepty owns *automation*: HTTP/WS APIs, authenticated remote access,
|
|
59
|
+
readiness-aware inject/submit, event streams, cross-machine session control.
|
|
60
|
+
|
|
61
|
+
## telepty vs tmux
|
|
62
|
+
|
|
63
|
+
| Area | tmux | telepty |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Core layer | terminal multiplexer + emulator | PTY orchestration daemon |
|
|
66
|
+
| Primary user | a human at a keyboard | software / an orchestrator |
|
|
67
|
+
| Terminal fidelity | full VT/grid/scrollback/copy-mode | output stream + heuristic state |
|
|
68
|
+
| IPC | local Unix socket | HTTP/WS/REST daemon (:3848) |
|
|
69
|
+
| Input model | open-loop `send-keys` | readiness-gated inject/submit |
|
|
70
|
+
| Multi-session fan-out | `synchronize-panes` (1 window, 1 host) | broadcast/multicast (cross-machine) |
|
|
71
|
+
| Remote | via SSH | native daemon HTTP, no sshd |
|
|
72
|
+
| Dependencies | zero runtime deps (C) | Node daemon + deps |
|
|
73
|
+
|
|
74
|
+
## When to use which
|
|
75
|
+
- **Use tmux** for panes, scrollback, copy-mode, capture-pane, and local
|
|
76
|
+
human terminal work — telepty does none of this and doesn't try to.
|
|
77
|
+
- **Use telepty** when software needs to spawn, inspect, inject into, and
|
|
78
|
+
track many AI-CLI sessions over an API — across machines.
|
|
79
|
+
|
|
80
|
+
## Limitations (honest)
|
|
81
|
+
- No terminal emulation: no cell grid, cursor model, or copy-mode. Screen
|
|
82
|
+
reads are buffered bytes + heuristic state, not a ground-truth screen.
|
|
83
|
+
- Requires a background daemon and a network port (:3848, auth-gated).
|
|
84
|
+
|
|
85
|
+
## Core Commands
|
|
86
|
+
|
|
87
|
+
| Command | Description |
|
|
88
|
+
|---------|-------------|
|
|
89
|
+
| `telepty daemon` | Start the background daemon (port 3848) |
|
|
90
|
+
| `telepty allow --id <name> <cmd>` | Wrap a CLI for inject control |
|
|
91
|
+
| `telepty spawn --id <name> <cmd>` | Spawn a new background session |
|
|
92
|
+
| `telepty list [--json]` | List sessions across all discovered hosts |
|
|
93
|
+
| `telepty attach [id[@host]]` | Attach to a session (interactive picker if no ID) |
|
|
94
|
+
| `telepty inject <id[@host]> "text"` | Inject text into a session |
|
|
95
|
+
| `telepty inject --submit <id> "text"` | Inject text and press Enter (render-gated, retries once on safe gate-timeout) |
|
|
96
|
+
| `telepty inject --submit --submit-force <id> "text"` | As above, but bypass the gate (skip Layer 1/3 detection — opt-in escape hatch) |
|
|
97
|
+
| `telepty inject --submit --submit-retry N <id> "text"` | Override retry count [0–3] on safe 504 (default 1) |
|
|
98
|
+
| `telepty enter <id[@host]>` | Send Enter/Return to a session |
|
|
99
|
+
| `telepty multicast <id1,id2> "text"` | Inject into multiple sessions |
|
|
100
|
+
| `telepty broadcast "text"` | Inject into ALL sessions |
|
|
101
|
+
| `telepty rename <old> <new>` | Rename a session |
|
|
102
|
+
| `telepty read-screen <id> [--lines N]` | Read session screen buffer |
|
|
103
|
+
| `telepty reply "text"` | Reply to the last injector |
|
|
104
|
+
| `telepty monitor` | Real-time event billboard |
|
|
105
|
+
| `telepty listen` | Stream event bus as JSON |
|
|
106
|
+
| `telepty tui` | Full TUI dashboard |
|
|
107
|
+
| `telepty layout [grid\|tall\|stack]` | Arrange kitty windows |
|
|
108
|
+
| `telepty update` | Update to latest version |
|
|
109
|
+
|
|
110
|
+
## Environment variables
|
|
111
|
+
|
|
112
|
+
| Variable | Values | Default | Description |
|
|
113
|
+
|----------|--------|---------|-------------|
|
|
114
|
+
| `TELEPTY_SUBMIT_FORCE_DEFAULT` | `1`, `true`, `yes`, `on` to enable; unset, `0`, or `off` to disable | unset | Makes `telepty inject --submit <id> "text"` behave as if `--submit-force` was passed. |
|
|
115
|
+
|
|
116
|
+
`TELEPTY_SUBMIT_FORCE_DEFAULT=1` is for orchestrators and automation that
|
|
117
|
+
already know their targets are real, initialized REPLs. It avoids the transient
|
|
118
|
+
504 `bootstrap_not_ready` path where injected text lands in the target input box
|
|
119
|
+
but the render-gated submit refuses to press Enter while the target session is in
|
|
120
|
+
a temporary working state.
|
|
121
|
+
|
|
122
|
+
This bypasses the safety gate that protects sessions still booting. Set it only
|
|
123
|
+
when you understand that trade-off. Use `--no-submit-force` on a specific
|
|
124
|
+
`telepty inject --submit` call to restore the gated behavior even when the
|
|
125
|
+
environment default is enabled.
|
|
126
|
+
|
|
127
|
+
## Cross-Machine Sessions
|
|
128
|
+
|
|
129
|
+
telepty auto-discovers sessions across your Tailnet. All commands (`list`, `attach`, `inject`, `rename`, `multicast`, `broadcast`) work seamlessly across machines.
|
|
130
|
+
|
|
131
|
+
### Zero-config on Tailscale (auto bind + auto trust)
|
|
132
|
+
|
|
133
|
+
On a host that is on a **Tailscale tailnet**, a fresh install is cross-machine-ready with
|
|
134
|
+
**no manual env**. At startup the daemon detects its tailnet interface (a `100.64.0.0/10`
|
|
135
|
+
address) and:
|
|
136
|
+
|
|
137
|
+
- **binds :3848 to the tailnet IP only** (plus loopback) — LAN/public interfaces stay
|
|
138
|
+
closed, so the control API is reachable **only from your Tailnet**, never the flat LAN;
|
|
139
|
+
- **trusts tailnet peers automatically** — Tailscale's own ACLs already gate who is on the
|
|
140
|
+
tailnet, so a tailnet peer needs no token or manual allowlist.
|
|
141
|
+
|
|
142
|
+
> **Trust boundary:** on a Tailscale host, telepty auto-exposes :3848 to your **entire
|
|
143
|
+
> tailnet** (every peer Tailscale's ACLs let onto it). If you share your tailnet with
|
|
144
|
+
> machines you don't fully trust, set `TELEPTY_PEER_ALLOWLIST` to restrict to specific
|
|
145
|
+
> peers/CIDRs, or `TELEPTY_NO_TAILNET_AUTO=1` to stay loopback-only.
|
|
146
|
+
|
|
147
|
+
**Zero-config cross-machine is Tailscale-specific.** On a non-Tailscale host (plain LAN,
|
|
148
|
+
other mesh VPNs) the daemon stays **loopback-only** (the safe default) — set `TELEPTY_BIND`
|
|
149
|
+
+ `TELEPTY_PEER_ALLOWLIST` manually to expose it. Manual `TELEPTY_BIND` / `HOST` /
|
|
150
|
+
`TELEPTY_PEER_ALLOWLIST` always win over auto-detect.
|
|
151
|
+
|
|
152
|
+
**Windows:** Defender Firewall blocks inbound on the tailnet interface by default. On the
|
|
153
|
+
auto path the daemon adds the inbound allow-rule automatically when run elevated, otherwise
|
|
154
|
+
it prints the exact one-time `netsh` command in the startup banner.
|
|
155
|
+
|
|
156
|
+
Addressing stays **IP-free**: use MagicDNS / hostnames with `<id>@<host>` (below) — you
|
|
157
|
+
never need to type a `100.x.y.z` address.
|
|
158
|
+
|
|
159
|
+
### `<id>@<host>` syntax
|
|
160
|
+
|
|
161
|
+
To target a specific host (when the same session ID exists on multiple hosts,
|
|
162
|
+
or when there is no Tailnet auto-discovery), append `@<host>` to the session
|
|
163
|
+
ID. `<host>` can be a hostname, LAN IP, or Tailnet name.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Hostname / Tailnet name
|
|
167
|
+
telepty inject my-session@macbook "hello"
|
|
168
|
+
telepty attach worker@server-01
|
|
169
|
+
|
|
170
|
+
# LAN IP — useful when no Tailnet is configured
|
|
171
|
+
telepty inject orchestrator-claude@172.28.4.165 "ping"
|
|
172
|
+
telepty read-screen build-runner@10.0.0.42 --lines 50
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Requirements**:
|
|
176
|
+
- The remote daemon must be reachable on port **3848** from the calling host
|
|
177
|
+
(LAN routing, firewall rules, or Tailscale).
|
|
178
|
+
- No SSH or `sshd` is required on either side — the call hits the remote
|
|
179
|
+
daemon's HTTP API directly. This is the recommended path for laptop
|
|
180
|
+
daemons that don't run sshd.
|
|
181
|
+
- The `@<host>` qualifier works for `inject`, `attach`, `read-screen`,
|
|
182
|
+
`enter`, `multicast`, and `rename`.
|
|
183
|
+
|
|
184
|
+
## How It Works
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
CLI (telepty) ──> HTTP/WS ──> Daemon (:3848)
|
|
188
|
+
├── Session WebSocket (/api/sessions/:id)
|
|
189
|
+
├── Event Bus WebSocket (/api/bus)
|
|
190
|
+
└── REST API (/api/sessions/*)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- **`allow`** wraps a CLI process in a PTY bridge, enabling remote inject
|
|
194
|
+
- **`inject`** delivers text via the fastest available path: kitty terminal API, WebSocket, or UDS (Unix Domain Socket for embedded integrations)
|
|
195
|
+
- **`submit`** is handled separately from text injection for reliability across all AI CLIs
|
|
196
|
+
|
|
197
|
+
## `[context-ref]` Protocol — long payloads via shared file
|
|
198
|
+
|
|
199
|
+
When a sender uses `telepty inject --ref <file> <target> "<message>"`, telepty
|
|
200
|
+
stores the payload in a shared file under `~/.telepty/shared/<sha256>.md` and
|
|
201
|
+
injects only a short pointer prompt of the form:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
[context-ref] Read ~/.telepty/shared/<sha256>.md and use it as the source of truth for this task.
|
|
205
|
+
<inline message>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
This avoids prompt rot in the receiving session (and in the orchestrator's
|
|
209
|
+
window when the reply is small).
|
|
210
|
+
|
|
211
|
+
### Receiver contract
|
|
212
|
+
|
|
213
|
+
The receiving AI session is expected to:
|
|
214
|
+
1. Detect the `[context-ref]` prefix on the first line.
|
|
215
|
+
2. Read the file at the absolute path.
|
|
216
|
+
3. Treat the file contents as the **authoritative payload** for the task — the
|
|
217
|
+
inline message is supplementary (topic / hint), not the source of truth.
|
|
218
|
+
|
|
219
|
+
### Storage location
|
|
220
|
+
|
|
221
|
+
- File path: `~/.telepty/shared/<sha256>.md` (sha256 of payload body)
|
|
222
|
+
- Created with mode `0600`; readable only by the local user
|
|
223
|
+
- Persists across sessions; not garbage-collected automatically (run
|
|
224
|
+
`telepty clean --shared` to prune)
|
|
225
|
+
|
|
226
|
+
### When to use `--ref`
|
|
227
|
+
|
|
228
|
+
- Payload exceeds ~1KB or contains structured content (code, logs, tables).
|
|
229
|
+
- You want the receiver to load the payload deterministically rather than
|
|
230
|
+
paraphrase it from the inject prompt.
|
|
231
|
+
- You're orchestrating a multi-hop conversation where the orchestrator should
|
|
232
|
+
not see the full payload in its own context window.
|
|
233
|
+
|
|
234
|
+
### Integration scope
|
|
235
|
+
|
|
236
|
+
Per-agent receiver integrations (auto-loading the file via Claude Code
|
|
237
|
+
`UserPromptSubmit` hooks, Codex `AGENTS.md` directives, etc.) are **out of
|
|
238
|
+
scope for telepty core** — they live in the agent's own configuration.
|
|
239
|
+
Per-CLI hook installation lives in devkit: run `aigentry scaffold
|
|
240
|
+
install-hooks {claude|codex|gemini}` after installing
|
|
241
|
+
`@dmsdc-ai/aigentry-devkit`. (Older drafts proposed a receiver-side
|
|
242
|
+
`telepty install` subcommand for this; that direction is rejected per ADR
|
|
243
|
+
2026-05-05-telepty-devkit-boundary §3.1.2 / §3.4 row 2.)
|
|
244
|
+
|
|
245
|
+
## Inject Delivery Paths
|
|
246
|
+
|
|
247
|
+
| Priority | Method | When |
|
|
248
|
+
|----------|--------|------|
|
|
249
|
+
| 1 | `kitty @ send-text` | Terminal supports kitty protocol |
|
|
250
|
+
| 2 | UDS (Unix Domain Socket) | Embedded IPC sessions (e.g. aterm) |
|
|
251
|
+
| 3 | WebSocket PTY write | Wrapped sessions via allow-bridge |
|
|
252
|
+
|
|
253
|
+
## AI CLI Integration
|
|
254
|
+
|
|
255
|
+
telepty works as a session bridge for AI CLIs. Use `allow` to wrap any CLI:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Claude Code
|
|
259
|
+
telepty allow --id claude-main claude
|
|
260
|
+
|
|
261
|
+
# Codex
|
|
262
|
+
telepty allow --id codex-main codex
|
|
263
|
+
|
|
264
|
+
# Gemini CLI
|
|
265
|
+
telepty allow --id gemini-main gemini
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Then inject prompts, read output, or attach from anywhere:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
telepty inject claude-main "refactor the auth module"
|
|
272
|
+
telepty read-screen claude-main --lines 50
|
|
273
|
+
telepty attach claude-main
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Deliberation (Multi-Session Discussion)
|
|
277
|
+
|
|
278
|
+
Coordinate structured discussions across multiple AI sessions:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
telepty deliberate --topic "API design for v2" --sessions claude-1,claude-2,codex-1
|
|
282
|
+
telepty deliberate status
|
|
283
|
+
telepty deliberate end <thread_id>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Skill Installation
|
|
287
|
+
|
|
288
|
+
telepty ships with packaged skills for Claude Code, Codex, and Gemini CLI. Run the interactive installer:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
telepty
|
|
292
|
+
# Choose "Install telepty skills"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Testing
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm test # 70 tests (node:test)
|
|
299
|
+
npm run test:watch # Watch mode
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Ecosystem
|
|
303
|
+
|
|
304
|
+
telepty runs **standalone** — it needs none of the other aigentry modules and installs with the single command above. It is also the transport layer of the broader **aigentry** ecosystem, whose modules are each independently published and independently useful:
|
|
305
|
+
|
|
306
|
+
{{ecosystem_table}}
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|
package/daemon.js
CHANGED
|
@@ -19,6 +19,7 @@ const { UnixSocketNotifier } = require('./src/mailbox/notifier');
|
|
|
19
19
|
const { SessionStateManager, STATE_DISPLAY, stripAnsi: stripAnsiState } = require('./session-state');
|
|
20
20
|
const { classifyReportPrompt, buildAutoSummary } = require('./src/report-enforcement');
|
|
21
21
|
const submitGate = require('./src/submit-gate');
|
|
22
|
+
const { stripAnsiForScreen } = require('./src/screen-ansi'); // #715: read-screen ANSI/VT stripper
|
|
22
23
|
const { sampleChildCpuSeconds } = require('./src/child-cpu'); // #52: quiet-thinking CPU recheck
|
|
23
24
|
const readyRegistry = require('./src/prompt-symbol-registry');
|
|
24
25
|
const lifecycle = require('./src/lifecycle');
|
|
@@ -1819,6 +1820,22 @@ function isTerminalGateFailure(gateResult) {
|
|
|
1819
1820
|
);
|
|
1820
1821
|
}
|
|
1821
1822
|
|
|
1823
|
+
// #716: bracketed-paste submit envelope. codex/claude composers swallow a submit CR
|
|
1824
|
+
// that arrives coalesced with the injected text burst (paste-burst / coalesced read).
|
|
1825
|
+
// For a session the CLI marked paste-capable (it emitted ESC[?2004h — tracked in
|
|
1826
|
+
// appendToOutputRing), wrap the injected TEXT in bracketed-paste markers so the burst
|
|
1827
|
+
// is an explicit, delimited paste; the submit CR is written SEPARATELY and OUTSIDE
|
|
1828
|
+
// this envelope, so it is an unambiguous keystroke regardless of inter-write timing.
|
|
1829
|
+
// Non-paste-capable sessions (legacy claude/gemini/agy that never advertised ?2004h)
|
|
1830
|
+
// are byte-identical. An empty body is never wrapped.
|
|
1831
|
+
const BRACKETED_PASTE_START = '\x1b[200~';
|
|
1832
|
+
const BRACKETED_PASTE_END = '\x1b[201~';
|
|
1833
|
+
function maybeBracketedPaste(text, session) {
|
|
1834
|
+
if (!text) return text;
|
|
1835
|
+
if (!session || !session.bracketedPasteCapable) return text;
|
|
1836
|
+
return BRACKETED_PASTE_START + text + BRACKETED_PASTE_END;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1822
1839
|
async function deliverInjectionToSession(id, session, prompt, options = {}) {
|
|
1823
1840
|
const now = Date.now();
|
|
1824
1841
|
if (!options.bypassBootstrapQueue && shouldQueueBootstrapOperation(session)) {
|
|
@@ -1869,12 +1886,17 @@ async function deliverInjectionToSession(id, session, prompt, options = {}) {
|
|
|
1869
1886
|
origin: options.origin
|
|
1870
1887
|
}).payload;
|
|
1871
1888
|
|
|
1889
|
+
// #716: wrap the delivered text in bracketed paste for paste-capable CLIs so the
|
|
1890
|
+
// deferred/gated submit CR (written separately, outside the 200~/201~ envelope)
|
|
1891
|
+
// reliably fires instead of being swallowed into the paste burst. No-op otherwise.
|
|
1892
|
+
const deliveredBody = maybeBracketedPaste(deliveredPrompt, session);
|
|
1893
|
+
|
|
1872
1894
|
try {
|
|
1873
1895
|
const ack = mailbox.enqueue({
|
|
1874
1896
|
msg_id: msgId,
|
|
1875
1897
|
from,
|
|
1876
1898
|
to: id,
|
|
1877
|
-
payload:
|
|
1899
|
+
payload: deliveredBody,
|
|
1878
1900
|
created_at: Math.floor(now / 1000),
|
|
1879
1901
|
attempt: 0,
|
|
1880
1902
|
});
|
|
@@ -1915,7 +1937,7 @@ async function deliverInjectionToSession(id, session, prompt, options = {}) {
|
|
|
1915
1937
|
} catch (err) {
|
|
1916
1938
|
console.error(`[MAILBOX] Enqueue failed for ${id}: ${err.message}`);
|
|
1917
1939
|
// Fallback: direct delivery (backward compat during migration)
|
|
1918
|
-
const textResult = await writeDataToSession(id, session,
|
|
1940
|
+
const textResult = await writeDataToSession(id, session, deliveredBody);
|
|
1919
1941
|
if (!textResult.success) return textResult;
|
|
1920
1942
|
|
|
1921
1943
|
if (!options.noEnter && session.type !== 'aterm') {
|
|
@@ -1942,6 +1964,12 @@ async function deliverInjectionToSession(id, session, prompt, options = {}) {
|
|
|
1942
1964
|
|
|
1943
1965
|
function appendToOutputRing(session, data) {
|
|
1944
1966
|
if (!session.outputRing) session.outputRing = [];
|
|
1967
|
+
// #716: track bracketed-paste capability from the CLI's own mode-set output so
|
|
1968
|
+
// injects are wrapped (maybeBracketedPaste) only for paste-capable composers —
|
|
1969
|
+
// codex/claude emit ESC[?2004h. Last h/l in the chunk wins.
|
|
1970
|
+
const bpOn = data.lastIndexOf('\x1b[?2004h');
|
|
1971
|
+
const bpOff = data.lastIndexOf('\x1b[?2004l');
|
|
1972
|
+
if (bpOn !== -1 || bpOff !== -1) session.bracketedPasteCapable = bpOn > bpOff;
|
|
1945
1973
|
// #52: monotonic byte counter — the inject-time watermark that scopes echo-evidence
|
|
1946
1974
|
// matching to frames appended AFTER the inject (survives ring trimming below).
|
|
1947
1975
|
session.outputRingTotalBytes = (session.outputRingTotalBytes || 0) + data.length;
|
|
@@ -3561,30 +3589,7 @@ app.get('/api/sessions/:id/screen', (req, res) => {
|
|
|
3561
3589
|
// Join all buffered output
|
|
3562
3590
|
const fullOutput = session.outputRing.join('');
|
|
3563
3591
|
|
|
3564
|
-
|
|
3565
|
-
function stripAnsi(str) {
|
|
3566
|
-
return str
|
|
3567
|
-
// Replace cursor-forward (ESC[NC, ESC[C) with N spaces to preserve whitespace
|
|
3568
|
-
.replace(/\[(\d*)C/g, (_, n) => ' '.repeat(Number(n) || 1))
|
|
3569
|
-
// CSI sequences: ESC [ ? (optional) params final_byte
|
|
3570
|
-
.replace(/\[\??[0-9;]*[a-zA-Z@`]/g, '')
|
|
3571
|
-
// OSC sequences: ESC ] ... BEL
|
|
3572
|
-
.replace(/\][^]*/g, '')
|
|
3573
|
-
// OSC sequences: ESC ] ... ST (ESC \)
|
|
3574
|
-
.replace(/\][^]*\\/g, '')
|
|
3575
|
-
// Character set selection: ESC ( / ) + charset
|
|
3576
|
-
.replace(/[()][AB012]/g, '')
|
|
3577
|
-
// Keypad and other 2-char ESC sequences
|
|
3578
|
-
.replace(/[>=<78DMEHcNOZ~}|]/g, '')
|
|
3579
|
-
// DCS / PM / APC sequences
|
|
3580
|
-
.replace(/[P^_][^]*\\/g, '')
|
|
3581
|
-
// Any remaining bare ESC + single char
|
|
3582
|
-
.replace(/./g, '')
|
|
3583
|
-
// Carriage returns
|
|
3584
|
-
.replace(/\r/g, '');
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
const cleaned = raw ? fullOutput : stripAnsi(fullOutput);
|
|
3592
|
+
const cleaned = raw ? fullOutput : stripAnsiForScreen(fullOutput);
|
|
3588
3593
|
|
|
3589
3594
|
// Take last N lines
|
|
3590
3595
|
const allLines = cleaned.split('\n');
|
|
@@ -4611,6 +4616,8 @@ module.exports = {
|
|
|
4611
4616
|
loadNodeBrokerConfig, // node-mode: resolve broker.json / env config (or null)
|
|
4612
4617
|
startNodeBrokerClient, // node-mode: start createBrokerClient (default-OFF; in-process deliver)
|
|
4613
4618
|
deliverInjectionToSession, // §4.3: the in-process delivery wired into the broker-client
|
|
4619
|
+
appendToOutputRing, // #716: seam that tracks bracketed-paste capability (?2004h)
|
|
4620
|
+
maybeBracketedPaste, // #716: capability-gated bracketed-paste wrap of injected text
|
|
4614
4621
|
resolveBindHost, // telepty#50 + #672: pure bind-address policy (loopback default, env opt-in, tailnet auto)
|
|
4615
4622
|
formatBindHint, // telepty#50 + #672: startup bind/exposure banner line
|
|
4616
4623
|
isTailnetAuto, // #672: pure predicate — is the zero-config tailnet path active
|
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.12",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
"CHANGELOG.md"
|
|
36
36
|
],
|
|
37
37
|
"scripts": {
|
|
38
|
+
"prepublishOnly": "node scripts/gen-readme.mjs",
|
|
38
39
|
"postinstall": "node scripts/postinstall.js",
|
|
39
40
|
"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/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/",
|
|
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/",
|
|
43
44
|
"typecheck": "tsc --noEmit",
|
|
44
45
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
45
46
|
},
|
package/session-state.js
CHANGED
|
@@ -144,7 +144,10 @@ const ERROR_PATTERNS = [
|
|
|
144
144
|
];
|
|
145
145
|
|
|
146
146
|
// ANSI escape stripper (preserves OSC 133 detection by running after OSC check)
|
|
147
|
-
|
|
147
|
+
// CSI branch = ECMA-48 CSI (see #715): params 0x30-0x3f (incl. < > = : ? used by
|
|
148
|
+
// kitty-keyboard/modifyOtherKeys), intermediates 0x20-0x2f, final 0x40-0x7e. The
|
|
149
|
+
// prior [0-9;] param class leaked claude v2.1.198's ESC[<u/ESC[>1u/ESC[>4;2m (#713).
|
|
150
|
+
const ANSI_RE = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]|\x1b\][^\x07]*\x07|\x1b[()][AB012]/g;
|
|
148
151
|
|
|
149
152
|
function stripAnsi(str) {
|
|
150
153
|
return str.replace(ANSI_RE, '');
|
|
@@ -143,7 +143,10 @@ function isKnownAiCli(command) {
|
|
|
143
143
|
return !!lookup(command);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
// CSI branch = ECMA-48 CSI (see #715): params 0x30-0x3f (incl. < > = : used by
|
|
147
|
+
// kitty-keyboard/modifyOtherKeys), intermediates 0x20-0x2f, final 0x40-0x7e. The
|
|
148
|
+
// prior [0-9;?] param class leaked claude v2.1.198's ESC[<u/ESC[>1u/ESC[>4;2m (#713).
|
|
149
|
+
const ANSI_RE = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]|\x1b\][^\x07]*(?:\x07|\x1b\\)|\x1b[()][AB012]|\x1b[>=<78DMEHcNOZ~}|]/g;
|
|
147
150
|
|
|
148
151
|
function stripAnsi(value) {
|
|
149
152
|
return String(value == null ? '' : value).replace(ANSI_RE, '');
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// #715 — telepty read-screen rendered codex/claude sessions as garbage. The /screen
|
|
4
|
+
// endpoint has no VT emulator: it concatenates the raw PTY output ring and strips ANSI
|
|
5
|
+
// by regex. The classic CSI matcher only handled `ESC [ params final`, so CSI sequences
|
|
6
|
+
// with intermediate bytes (SPACE, $), the < = > private markers, or : sub-parameters
|
|
7
|
+
// failed to match and the fallback leaked their tail as literal text. This is the
|
|
8
|
+
// screen-render ANSI stripper; kept pure + standalone so it is unit-testable without
|
|
9
|
+
// booting the daemon. (Parser-only fix; the submit/gate path is a separate concern.)
|
|
10
|
+
function stripAnsiForScreen(str) {
|
|
11
|
+
return str
|
|
12
|
+
// Replace cursor-forward (ESC[NC, ESC[C) with N spaces to preserve whitespace
|
|
13
|
+
.replace(/\[(\d*)C/g, (_, n) => ' '.repeat(Number(n) || 1))
|
|
14
|
+
// CSI (ECMA-48): ESC [ params(0x30-3F incl : ; < = > ?) intermediates(0x20-2F) final(0x40-7E).
|
|
15
|
+
// #715: the classic /\x1b[\??[0-9;]*.../ form dropped intermediates, the < = >
|
|
16
|
+
// private markers and colon sub-params, so a partial match leaked the tail as
|
|
17
|
+
// literal text (DECSCUSR ESC[0 q -> `0 q`, kitty ESC[>1u -> `>1u`, etc.).
|
|
18
|
+
.replace(/\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, '')
|
|
19
|
+
// OSC sequences: ESC ] ... BEL
|
|
20
|
+
.replace(/\][^]*/g, '')
|
|
21
|
+
// OSC sequences: ESC ] ... ST (ESC \)
|
|
22
|
+
.replace(/\][^]*\\/g, '')
|
|
23
|
+
// Character set selection: ESC ( / ) + charset
|
|
24
|
+
.replace(/[()][AB012]/g, '')
|
|
25
|
+
// Keypad and other 2-char ESC sequences
|
|
26
|
+
.replace(/[>=<78DMEHcNOZ~}|]/g, '')
|
|
27
|
+
// DCS / PM / APC sequences
|
|
28
|
+
.replace(/[P^_][^]*\\/g, '')
|
|
29
|
+
// Any remaining bare ESC + single char
|
|
30
|
+
.replace(/./g, '')
|
|
31
|
+
// Carriage returns
|
|
32
|
+
.replace(/\r/g, '');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { stripAnsiForScreen };
|