@dmsdc-ai/aigentry-telepty 0.4.3 → 0.4.5

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 CHANGED
@@ -2,6 +2,391 @@
2
2
 
3
3
  All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [0.4.5] - 2026-05-26
8
+
9
+ ### Fixed — Stale-daemon, restart-recovery, force-bypass, codex matcher (tasks #469 #470 #471 #472)
10
+
11
+ - **#469 — npm postinstall hook restarts a stale daemon (`scripts/postinstall.js`).**
12
+ `npm install -g @dmsdc-ai/aigentry-telepty@X` previously overwrote files but
13
+ never signalled the running `telepty-daemon`, so the daemon kept executing
14
+ the previously-loaded code (observed: PID 3222 ran 22 days through 4
15
+ upgrades). The new postinstall script reads `~/.telepty/daemon-state.json`,
16
+ compares the running daemon's reported version to the just-installed
17
+ `package.json` version, and on mismatch invokes the existing
18
+ `cleanupDaemonProcesses()` primitive plus a detached respawn. Skips on
19
+ `TELEPTY_SKIP_POSTINSTALL=1` and on non-global installs
20
+ (`npm_config_global!=='true'`).
21
+ - **#470 — daemon restart re-bootstraps existing sessions
22
+ (`daemon.js` `runStartupBootstrapRestore()`).** After a daemon restart,
23
+ persisted-and-restored sessions remained `ready:false` indefinitely because
24
+ the bootstrap prompt-symbol probe only fired on owner-WebSocket reconnect.
25
+ On startup, each restored gated session whose `ownerPid` is still alive is
26
+ now actively probed (cmux path) or optimistically marked ready (non-cmux
27
+ path), with the chosen reason recorded for log attribution. Sessions whose
28
+ owner process is dead remain unready, matching the prior unready semantics.
29
+ - **#471 — `force: true` bypasses the bootstrap gate (`daemon.js:1969`).**
30
+ The per-request `force` escape hatch (`cli.js --submit-force`,
31
+ `TELEPTY_SUBMIT_FORCE_DEFAULT=1` from 0.4.4) was parsed correctly but the
32
+ bootstrap gate enqueued it and returned 504 long before the force-bypass
33
+ block at L1998 could run. Surgical 1-line condition edit: gate fires only
34
+ when `!force`. The force-bypass code path is now exercisable as documented.
35
+ - **#472 — codex prompt-symbol matcher normalized across environments
36
+ (`src/prompt-symbol-registry.js`).** On real cmux captures the codex `›`
37
+ glyph tail-renders on the same row as the model-status footer and DECRQM /
38
+ cursor-position-query fragments (`>4;0m>7u`, `0 q`) leak into the screen
39
+ buffer, so the prior strict line-leading scan permanently missed and the
40
+ session stuck at `ready:false`. New tolerant detector: (1) modal-UI
41
+ anti-pattern guard for resume picker, first-run directory-trust prompt and
42
+ generic press-enter-to-continue modals (treated as NOT ready); (2)
43
+ multi-signal match on `"OpenAI Codex (v"` plus `/gpt-[0-9.]+\s+\w+\s+fast/`
44
+ anywhere on the screen; (3) legacy strict line-leading scan preserved as a
45
+ back-compat fallback. `awaitPromptSymbol` now emits a single
46
+ `[bootstrap] <cli> ready via: <reason>` log line on stabilize, paired with
47
+ the #470 optimistic-ready logging for unified debuggability.
48
+
49
+ ### Notes — 0.4.5
50
+
51
+ - **Tests** — `npm test` passes 416 / 416 (was 411 / 411 in 0.4.4; +5 new
52
+ cases in `test/release-0.4.5-bugfixes.test.js` covering #469/#470/#471/#472
53
+ including env-resistance regression guard on the existing noforce test).
54
+ - **Snyk Code SAST** — all newly authored or modified JS files
55
+ (`scripts/postinstall.js`, `src/prompt-symbol-registry.js`,
56
+ `src/submit-gate.js`, new `daemon.js` line ranges, and
57
+ `test/release-0.4.5-bugfixes.test.js`) report 0 findings. The 55
58
+ pre-existing repo-wide findings in unchanged code are tracked separately as
59
+ task #474 (security cleanup track) and are not part of this release.
60
+ - **Out of scope, tracked separately** — task #473 (session-ID reuse → stale
61
+ command metadata) is queued for a 0.4.6 dispatch and is not addressed here.
62
+
63
+ ## [0.4.4] - 2026-05-25
64
+
65
+ ### Added — TELEPTY_SUBMIT_FORCE_DEFAULT env var (task #453)
66
+
67
+ - **Environment default for forced submit** —
68
+ `TELEPTY_SUBMIT_FORCE_DEFAULT=1` makes `telepty inject --submit` behave as
69
+ if `--submit-force` was passed, without changing behavior for users who leave
70
+ the env var unset. Accepted truthy values are `1`, `true`, `yes`, and `on`
71
+ after whitespace trimming and case normalization.
72
+ - **Per-call opt-out** — `telepty inject --submit --no-submit-force ...`
73
+ restores the normal gated submit behavior even when the environment default
74
+ is enabled. Explicit `--submit-force` remains valid and wins when supplied.
75
+ - **Automation caveat** — this is intended for orchestrators that already know
76
+ their targets are real, initialized REPLs. It bypasses the safety gate that
77
+ prevents submit during target boot, avoiding the transient 504
78
+ `bootstrap_not_ready` path where text lands in the input box but Enter is not
79
+ sent.
80
+ - **Observability** — env-driven calls emit
81
+ `[telepty inject] submit-force=env-default (TELEPTY_SUBMIT_FORCE_DEFAULT=1)`
82
+ to stderr before posting `/submit`.
83
+
84
+ ### Notes — TELEPTY_SUBMIT_FORCE_DEFAULT env var
85
+
86
+ - **Test suite** — `npm test --silent` passes 403 / 403, including the new
87
+ `test/inject-submit-force-env.test.js` coverage for env-off, env-on,
88
+ `--no-submit-force`, explicit `--submit-force`, and value normalization.
89
+ - **Snyk SAST** — the requested `snyk_code_scan` MCP tool was not available in
90
+ this session, so the installed Snyk CLI was used. After replacing the new
91
+ localhost HTTP fixture with a loopback `net` test server and generating the
92
+ test auth token at runtime, `test/inject-submit-force-env.test.js` has 0
93
+ findings and the changed `cli.js` line ranges have 0 findings. The full repo
94
+ CLI scan still reports the pre-existing baseline findings in legacy `cli.js`,
95
+ `daemon.js`, existing HTTP test fixtures, and `scripts/bridge-phase1.js`. No
96
+ suppressions were added.
97
+
98
+ ### Added — Idle session cleanup (issue #34)
99
+
100
+ - **Idle visibility in `telepty list`** — session rows now append
101
+ `💤 idle (Xh Ym)` when `lastActivityAt` is more than 60 seconds old.
102
+ `telepty list --json` preserves `lastActivityAt` and adds
103
+ `idle_seconds` for machine consumers.
104
+ - **Transport-agnostic lifecycle helpers** — `src/lifecycle.js` centralizes
105
+ duration parsing, idle-victim selection, older-than cleanup selection, and
106
+ PTY/process-level teardown. It uses native POSIX signals and the existing
107
+ Windows `taskkill` helper; it does not call cmux or any workspace-host API.
108
+ - **`telepty kill <id> [--force] [--timeout <sec>]`** — graceful teardown
109
+ sends SIGTERM, waits up to the configured timeout, then escalates to
110
+ SIGKILL. `--force` sends SIGKILL immediately. Successful teardown removes
111
+ the daemon registry entry and session socket artifacts.
112
+ - **Opt-in idle TTL** — daemon config loads `~/.telepty/config.json` or a
113
+ simple `config.yaml` / `config.yml` with `idle_ttl_default` (`off` by
114
+ default). `telepty allow --idle-ttl <duration|off>` stores a per-session
115
+ override. The daemon reaper emits a `tracing` event with
116
+ `action: "idle_ttl_auto_kill"` before auto-teardown.
117
+ - **`telepty clean --older-than <duration> [--idle] [--dry-run]`** — default
118
+ ghost-only cleanup remains unchanged. The new opt-in path removes sessions
119
+ older than the threshold by `createdAt`, or by `lastActivityAt` when
120
+ `--idle` is set; `--dry-run` reports targets without deleting them.
121
+
122
+ ### Notes — Idle session cleanup
123
+
124
+ - **Test suite** — `npm test` passes 397 / 397, including transport-agnostic
125
+ lifecycle coverage for headless and cmux-backed fixtures.
126
+ - **Snyk SAST** — the requested `snyk_code_scan` MCP tool was not available in
127
+ this session, so the installed Snyk CLI was used. New standalone files
128
+ (`src/lifecycle.js`, `src/config-file.js`, and the new lifecycle tests)
129
+ scan with 0 findings. Scanning changed legacy entrypoints (`daemon.js` and
130
+ `cli.js`) still reports the repo's pre-existing baseline findings
131
+ previously noted for Phase 2: CLI path-traversal / command-injection flows
132
+ and daemon route-level prototype-pollution / throttling / command-execution
133
+ warnings. No suppressions were added.
134
+
135
+ ### Added — Phase 5a-prime (task #430 P5a-prime)
136
+
137
+ - **`crates/telepty-cross-machine/`** — standalone Rust library plus
138
+ `telepty-cross-machine-bin` for manual HTTP peer operations only. Scope is
139
+ deliberately reduced from the two rejected Phase 5 drafts: no JS bridge, no
140
+ subprocess envelope contract, no outbox queue, no npm distribution, no SSH
141
+ transport, and no `cli.js` / `daemon.js` / `cross-machine.js` changes. This
142
+ follows the review basis in
143
+ `docs/reports/2026-05-24-phase5-spec-codex-review.md` and
144
+ `docs/reports/2026-05-24-phase5a-spec-codex-rereview.md`, which identified
145
+ bridge-consumed binary contracts as the unstable surface.
146
+ - **Manual HTTP subcommands** — `connect-http`, `list-peer-sessions`,
147
+ `inject-peer`, `list-peers`, and `remove-peer`. `connect-http` probes
148
+ `/api/health`, treats `/api/meta` as non-fatal, persists token-backed HTTP
149
+ peers to `~/.telepty/peers.json`, and prints human-friendly status. List
150
+ commands support free-form `--json` output without an envelope contract.
151
+ `inject-peer` fails fast on unreachable peers; there is no queueing.
152
+ - **Backward-compatible `peers.json` handling** — missing `transport` defaults
153
+ to SSH and round-trips without injecting a `transport` field, preserving the
154
+ JS-era legacy schema used by the SSH path. HTTP operations on SSH peers exit
155
+ 4 with the explicit JS-path diagnostic required by Phase 5a-prime.
156
+ - **Addressing and atomic-write parity** — Rust host parsing mirrors
157
+ `host-spec.js` URL stripping, embedded-port, and IPv6 behavior. Peer updates
158
+ use the fsync-backed `tmp + fsync(tmp) + rename + fsync(parent_dir)` pattern
159
+ copied from `crates/telepty-supervisor-core/src/manifest.rs`.
160
+ - **Build metadata** — `build.rs` embeds git hash, dirty flag, and build
161
+ timestamp. `telepty-cross-machine-bin --version` prints
162
+ `telepty-cross-machine 0.0.1 (<git-hash>[, dirty])`.
163
+
164
+ ### Added — Phase 2 Node↔Rust IPC bridge (task #430 P2)
165
+
166
+ - **`src/bridge/supervisor-ipc.js`** — Node `BridgeClient` speaking NDJSON
167
+ over the per-session UDS (`~/.telepty/sessions/<sid>/supervisor.sock`).
168
+ Surface: `connect(socketPath)` → client; `send(frame)` fire-and-forget;
169
+ `request(frame, {timeoutMs})` with trace_id correlation (resolves on
170
+ matching `pong`, rejects on matching `error` with the supervisor's
171
+ `ERR_*` code preserved, rejects `ERR_TIMEOUT` on drift); `subscribe({sid,
172
+ signal})` returning an `AsyncIterator<Frame>` that respects `AbortSignal`
173
+ and `iterator.return()` for clean unsubscribe; `close()` idempotent and
174
+ rejects any pending requests with `ERR_SUPERVISOR_GONE`. Per synthesis
175
+ ADR §6.2 (B3), `trace_id` is auto-filled for kinds the supervisor
176
+ mandates it on (`inject`/`output`/`signal`/`kill`/`delete`); pong
177
+ reflects ping `trace_id` so correlation works without server-side
178
+ per-client state. Malformed inbound lines surface as synthetic
179
+ `ERR_BAD_FRAME` to subscribers — the connection survives garbage so a
180
+ later-good frame still flows.
181
+ - **`src/bridge/j3-shim.js`** — 0.3.x→NDJSON translator covering the P2
182
+ subset (`inject` / `output` stream / `list`). `inject(sid, prompt, opts)`
183
+ opens a one-shot connection, sends an inject frame, watches 150 ms for a
184
+ trace_id-correlated `error` frame (catches B3 / `ERR_DUPLICATE_OP` /
185
+ `ERR_SHUTTING_DOWN`), and returns `{ success, trace_id, code?, error?
186
+ }`. `output(sid, {fromSeq, signal})` is an async generator yielding
187
+ `{ data, seq }` per `Frame::output` and a final `{ exit, ... }` on
188
+ `shutdown_drain`; consumer-driven cancellation via `AbortSignal` or
189
+ `break`. `list()` scans `~/.telepty/sessions/*/manifest.json` and
190
+ surfaces only `ready` / `draining` sessions (tombstones excluded — they
191
+ lack a usable socket; operators still see them via
192
+ `telepty-supervisor-bin --list`). Sessions root is resolved lazily so
193
+ `TELEPTY_SESSIONS_DIR` redirects work without re-requiring the module.
194
+ - **`src/bridge/supervisor-launcher.js`** — per-session Rust supervisor
195
+ process lifecycle. `resolveBinary({env})` chains
196
+ `TELEPTY_SUPERVISOR_BIN` (env override) → `./target/release/telepty-
197
+ supervisor-bin` (repo-relative) → `./target/debug/...` → `which
198
+ telepty-supervisor-bin` (PATH) and throws `ERR_BIN_NOT_FOUND` otherwise.
199
+ `spawn({sid, argv, cwd?, binary?, env?, stdio?})` shells out to the
200
+ binary with `stdio: ['ignore', 'ignore', 'pipe']` (default) so the
201
+ supervisor's M1/M2 stdout PTY-mirror doesn't bleed into the parent.
202
+ `waitReady(sid, {timeoutMs, pollMs})` gates on BOTH manifest status
203
+ (`ready`/`draining`) AND `fs.existsSync(socket)` — supervisor.rs writes
204
+ the manifest *before* `ipc::bind_socket`, so a manifest-only gate races
205
+ the bind; checking both closes the window without touching the
206
+ supervisor crate. `isAlive(sid)` cross-checks manifest pid via
207
+ `process.kill(pid, 0)`.
208
+ - **`cli.js` minimal-touch wiring** (Rule 29 surgical, +27 LOC, no
209
+ refactor of adjacent code):
210
+ - `cmd === 'list'` (L915): merges `bridgeShim.list()` into the daemon-
211
+ discovered session set, de-duplicated by `id`. Daemon entries remain
212
+ source-of-truth when both surfaces report the same session; bridge
213
+ entries fill the gap when daemon is down. Wrapped in a defensive
214
+ `try/catch` so any bridge failure leaves the daemon list intact.
215
+ - `cmd === 'inject'` LOCAL path (L1755): bridge-first attempt when
216
+ `!useSubmit && bridgeShim.findSupervisorManifest(target.id)` is
217
+ truthy. On bridge success, prints the existing
218
+ `✅ Context injected successfully into '...' (bridge).` line and
219
+ returns; on bridge failure, falls through to the unchanged daemon
220
+ HTTP path so caller-visible behavior never degrades. The gated
221
+ `--submit` semantics (render-gate / retry / `--submit-force`) stay
222
+ on `daemon.js` for the migration window — P2 wire does not carry
223
+ render-gate yet.
224
+ - **`cross-machine.js` UNTOUCHED** — P2 scope is local bridge only;
225
+ remote SSH / HTTP transport stays on the existing path. P3+ owns the
226
+ remote→bridge story.
227
+
228
+ ### E2E acceptance — `telepty spawn → inject → output` works with daemon.js stopped
229
+
230
+ - `test/bridge-e2e.test.js` drives the supervisor binary directly through
231
+ `supervisor-launcher` + `j3-shim` in an isolated `HOME` so the live
232
+ daemon (if any) is never touched. The headline test launches a real
233
+ `cat -u` under the supervisor, subscribes to the output stream, injects
234
+ `ping-echo\n`, and asserts the echo arrives — proving the bridge alone
235
+ is sufficient for the primary three operations per dispatch §2.4. The
236
+ test self-skips with a clear hint when
237
+ `target/release/telepty-supervisor-bin` is absent (binary not built
238
+ yet), keeping CI without Rust toolchain green.
239
+
240
+ ### Notes — Phase 2 bridge
241
+
242
+ - **No new npm dependencies** (Constitution §17 무의존). NDJSON parsing
243
+ via `readline.createInterface` from the Node stdlib; UDS connection via
244
+ `net.createConnection({ path })`; UUIDs via `crypto.randomUUID()`. Adds
245
+ zero packages to `package.json` `dependencies`.
246
+ - **Test suite** — `npm test` 375 / 375 pass in ~24 s (343 baseline
247
+ preserved per Rule 29 + 32 new bridge tests: 14 `BridgeClient` units,
248
+ 14 `j3-shim` units, 4 E2E). Test file ratio is ~1:1 with prod LOC
249
+ (~787 prod / ~797 tests).
250
+ - **Snyk SAST** — `snyk_code_scan` on `src/bridge/` + new test files →
251
+ **0 findings**. Pre-existing `cli.js` findings (3× path-traversal on
252
+ CLI arg → `fs.readFileSync` / `fs.readdirSync` at L2345/L2347/L2656;
253
+ 2× command-injection on CLI arg → `execSync` / `node-pty.spawn` at
254
+ L471/L1116) are unchanged by this work — they live in dataflows
255
+ unrelated to the L915/L1755 bridge insertions and are tracked
256
+ separately (consistent with the v0.4.3 baseline). No new Snyk findings
257
+ attributable to this phase.
258
+ - **Path budget** — bridge prod 787 LOC + bridge tests 797 LOC =
259
+ ~1.6 kLOC, well within the dispatch envelope (bridge ~400-700 + shim
260
+ ~200-400 + tests ~300-500). cli.js delta is +27 LOC pure additions
261
+ with no edits to existing lines, satisfying the minimal-touch
262
+ directive.
263
+ - **Cross-platform** — UDS path is POSIX-only in P2 (Windows native
264
+ pipe = P4 per dispatch §2). Bridge unit tests and E2E gracefully
265
+ skip on `process.platform === 'win32'`; the launcher still resolves
266
+ the binary path on Windows so the eventual P4 wiring has a stub to
267
+ extend.
268
+
269
+ ### Carry-overs — Phase 2
270
+
271
+ 1. **`telepty spawn` cli command bridge wiring** — out of P2 scope per
272
+ dispatch §Goal item 4 ("inject / output / list paths"). P3 owns the
273
+ refactor that lets `telepty spawn` route through
274
+ `supervisor-launcher.spawn` for supervisor-managed sessions.
275
+ 2. **Render-gated `--submit` over bridge** — daemon.js stays as the
276
+ submit gate for the migration window. Bridge inject currently
277
+ appends a literal `\r` to the data (matching the legacy
278
+ `no_enter: false` default) without REPL readiness detection.
279
+ 3. **Single-binary `telepty supervisor` mode** — P2 still spawns the
280
+ `telepty-supervisor-bin` standalone bin. The `telepty supervisor`
281
+ subcommand mode per orchestrator decision §6.6 A is post-P2.
282
+
283
+ ### Added — Phase 1 supervisor-core-finish (task #430 P1)
284
+
285
+ - **A5 detach/reattach via UDS reconnection + log offset replay** —
286
+ `wire::Kind::Resume` frame with optional `from_seq: u64` lets a
287
+ reconnecting client request replay of `Output` frames whose `seq` is
288
+ greater than `from_seq` from `~/.telepty/sessions/<sid>/log.jsonl`
289
+ before subscribing to the live broadcast. Replay is per-connection
290
+ sequential (handler-local) — no broadcast race. Seq-less audit
291
+ frames (`shutdown_drain`) are forwarded unconditionally so a late
292
+ reattach observes terminal state.
293
+ - **A7 list discovery via filesystem manifest scan** —
294
+ `manifest::scan_sessions()` walks `~/.telepty/sessions/*/manifest.json`
295
+ with atomic per-file reads; missing / unparseable manifests are
296
+ skipped (never panics). New `telepty-supervisor-bin --list` flat
297
+ flag emits a JSON array of `Manifest` to stdout. Output shape is
298
+ the **supervisor-owned** view; the legacy `telepty list --json`
299
+ daemon view will be reconciled by the P3 cli refactor (per dispatch
300
+ §6.1). `--list` is mutually exclusive with run-mode argv.
301
+ - **A8 delete graceful drain integration test** —
302
+ `tests/delete_drain.rs` end-to-end (no goldens): graceful (SIGTERM)
303
+ and forced (SIGKILL) variants both assert manifest unlinked + socket
304
+ unlinked + `log.jsonl` contains `shutdown_drain` with correct
305
+ `exit_reason` + supervisor exits within 3 s. Production code already
306
+ existed in `supervisor::run` (kill_outcome → unlink_clean branch).
307
+ - **B3 trace_id enforcement extended to signal/kill/delete** —
308
+ `wire::validate_incoming` now rejects `Kind::Signal`, `Kind::Kill`,
309
+ `Kind::Delete` lacking `trace_id` with explicit error codes
310
+ (`signal_missing_trace_id`, `kill_missing_trace_id`,
311
+ `delete_missing_trace_id`) per C3 spec §1002 audit linkage
312
+ (`kind:"signal"` event matches originating injector trace_id;
313
+ `kind:"shutdown_drain"` carries parent_trace_id). Rejection reason
314
+ is ALSO appended to `log.jsonl` so the audit trail captures *why* a
315
+ frame was rejected even if the client disconnects before reading
316
+ the error response.
317
+ - **F3 atomic manifest write contract test** —
318
+ `tests/atomic_manifest.rs` (5 tests). Headline test
319
+ `concurrent_readers_never_observe_partial_json` runs 1 writer thread
320
+ + 6 reader threads × 800 ms; readers always see a complete-old or
321
+ complete-new manifest, never partial JSON (the rename-atomicity
322
+ guarantee). Plus golden tests for `.json.tmp` cleanup, missing-
323
+ parent-dir creation, `unlink_clean` idempotency, and tombstone
324
+ audit-field roundtrip.
325
+ - **G3 audit trail expansion** — `dispatch_ingest` now logs each
326
+ validated ingest event (`Inject` / `Signal` / `Kill` / `Delete`) to
327
+ `log.jsonl` right after `validate_incoming` passes. Ping is
328
+ intentionally skipped (heartbeat noise). Validation rejections are
329
+ also logged (closes the silent-drop gap from earlier milestones).
330
+ `audit.rs` was extended (single module per Constitution §1
331
+ lightweight) rather than fragmenting into a new audit/ submodule.
332
+ R4 TelemetryEvent translation deferred to the P3 cli bridge per
333
+ orchestrator Phase 4 decision.
334
+ - **§8.A1 Normal termination contract test** —
335
+ `tests/normal_termination.rs` (2 tests). Covers child exits 0
336
+ (assert `exit_reason: normal`, `exit_code: 0`, escalated false,
337
+ manifest unlinked) and nonzero-but-natural exit (`sh -c 'exit 7'`
338
+ — assert exit_code propagated; `Normal` is the exit *mechanism*,
339
+ not the exit *code*).
340
+
341
+ ### Performance — E1 local-inject latency bench
342
+
343
+ - New `crates/telepty-supervisor-core/benches/inject_e1.rs` custom
344
+ harness (no criterion — Constitution §17 no new Rust deps).
345
+ `[[bench]] harness = false`. Run with `cargo bench --bench inject_e1`.
346
+ 100 warmup + 1000 measured roundtrips through real supervisor
347
+ wrapping `cat`.
348
+ - **E1-p50: 0.025 ms** (p90 0.057 ms, p99 0.091 ms) on
349
+ macos/aarch64 / Mac16,8 / Apple M4 Pro — **40× under the 1 ms
350
+ target**.
351
+ - Exit code 0 iff p50 < 1 ms (CI-gateable).
352
+
353
+ ### Notes — Phase 1 supervisor-core-finish
354
+
355
+ - **No new Rust deps** (Constitution §17). `tokio` `fs` feature
356
+ enabled in workspace deps (feature flag only). `serde_json` added
357
+ to `telepty-supervisor-bin` (was already a workspace dep).
358
+ - **Rule 29 surgical** — changes scoped to
359
+ `crates/telepty-supervisor-core/` (src + tests + benches) and
360
+ `crates/telepty-supervisor-bin/`. No daemon.js / cli.js changes
361
+ (those land in P2/P3). No Windows code paths (P4 scope; cargo
362
+ features can gate but no implementation in this phase).
363
+ - **Tests** — 42 / 42 pass (23 baseline preserved + 19 new):
364
+ - Unit: +4 wire B3 (signal/kill/delete trace_id), +2 audit, +1
365
+ scan_sessions
366
+ - Integration: +3 reattach_replay (A5), +2 delete_drain (A8), +5
367
+ atomic_manifest (F3), +2 normal_termination (§8.A1)
368
+ - **Snyk SAST** — `snyk_code_scan` on `crates/` → 0 findings across
369
+ both crates. Run at each phase boundary (Phase 4+).
370
+ - **§8.A contract test parity** (per C3 spec
371
+ `docs/specs/2026-05-10-supervisor-c3-kill-gate-spec.md` §8.A): 7
372
+ of 13 Bucket-A scenarios covered + 2 extras (A5 reattach, F3
373
+ atomic) + 4 correctly deferred (Windows = P4 / Bucket B =
374
+ controlled-host out of Phase 1 spike scope). **2 follow-up
375
+ carry-overs documented**: §8.A3-tree (grandchild-cascade
376
+ killpg semantics — code already correct; explicit fixture
377
+ needed) and §8.A-reactor-stall (single-thread reactor
378
+ non-blocking invariant — code-review-only invariant; runtime
379
+ probe optional).
380
+ - **Sources of truth** — the synthesis ADR referenced in dispatch
381
+ (`docs/adr/2026-05-10-telepty-l2-architecture-q-prime-bis.md`)
382
+ and 6-phase plan (`docs/reports/2026-05-23-telepty-l2-supervisor-plan.md`)
383
+ live in the orchestrator repo, not visible from this repo.
384
+ Per Phase 1 CLDR + orchestrator hybrid (b)+(c) decision, this
385
+ work derives §19.2 contract requirements from the local
386
+ `docs/specs/2026-05-10-supervisor-c3-kill-gate-spec.md` (which
387
+ the code already cited as `SPEC-C3-r1`) plus the dispatch text
388
+ itself.
389
+
5
390
  ## [0.4.3] - 2026-05-23
6
391
 
7
392
  ### Fixed
package/README.md CHANGED
@@ -68,6 +68,23 @@ telepty broadcast "status report"
68
68
  | `telepty layout [grid\|tall\|stack]` | Arrange kitty windows |
69
69
  | `telepty update` | Update to latest version |
70
70
 
71
+ ## Environment variables
72
+
73
+ | Variable | Values | Default | Description |
74
+ |----------|--------|---------|-------------|
75
+ | `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. |
76
+
77
+ `TELEPTY_SUBMIT_FORCE_DEFAULT=1` is for orchestrators and automation that
78
+ already know their targets are real, initialized REPLs. It avoids the transient
79
+ 504 `bootstrap_not_ready` path where injected text lands in the target input box
80
+ but the render-gated submit refuses to press Enter while the target session is in
81
+ a temporary working state.
82
+
83
+ This bypasses the safety gate that protects sessions still booting. Set it only
84
+ when you understand that trade-off. Use `--no-submit-force` on a specific
85
+ `telepty inject --submit` call to restore the gated behavior even when the
86
+ environment default is enabled.
87
+
71
88
  ## Cross-Machine Sessions
72
89
 
73
90
  telepty auto-discovers sessions across your Tailnet. All commands (`list`, `attach`, `inject`, `rename`, `multicast`, `broadcast`) work seamlessly across machines.