@dmsdc-ai/aigentry-telepty 0.1.98 → 0.3.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.
Files changed (44) hide show
  1. package/AGENTS.md +23 -0
  2. package/CHANGELOG.md +436 -0
  3. package/CLAUDE.md +5 -1
  4. package/README.md +70 -1
  5. package/cli.js +232 -53
  6. package/cross-machine.js +132 -0
  7. package/daemon.js +399 -39
  8. package/docs/reports/2026-05-05-issue-8-claude-review.md +194 -0
  9. package/docs/specs/2026-05-05-issue-8-telepty-init.md +477 -0
  10. package/docs/superpowers/specs/2026-04-26-inject-submit-enter-reliability.md +447 -0
  11. package/docs/superpowers/specs/2026-04-26-prompt-symbol-render-gate.md +571 -0
  12. package/docs/superpowers/specs/2026-04-26-submit-gate-fixes-v2.md +608 -0
  13. package/docs/superpowers/specs/2026-05-02-submit-force-and-retry.md +139 -0
  14. package/host-spec.js +60 -0
  15. package/mcp-server/index.mjs +24 -3
  16. package/package.json +6 -5
  17. package/scripts/regen-snippet-fixtures.js +42 -0
  18. package/skill-installer.js +42 -6
  19. package/skills/telepty/SKILL.md +1 -1
  20. package/skills/telepty-allow/SKILL.md +1 -1
  21. package/skills/telepty-attach/SKILL.md +1 -1
  22. package/skills/telepty-broadcast/SKILL.md +1 -1
  23. package/skills/telepty-daemon/SKILL.md +1 -1
  24. package/skills/telepty-inject/SKILL.md +76 -4
  25. package/skills/telepty-list/SKILL.md +1 -1
  26. package/skills/telepty-listen/SKILL.md +1 -1
  27. package/skills/telepty-rename/SKILL.md +1 -1
  28. package/skills/telepty-session/SKILL.md +1 -1
  29. package/specs/enforce-report-spec.md +237 -0
  30. package/src/init/print-snippet.js +114 -0
  31. package/src/init/snippets/agents.md +15 -0
  32. package/src/init/snippets/claude.md +15 -0
  33. package/src/init/snippets/gemini.md +15 -0
  34. package/src/prompt-symbol-registry.js +97 -0
  35. package/src/report-enforcement.js +86 -0
  36. package/src/submit-gate.js +269 -0
  37. package/tests/snippet-protocol/v1/golden-agents.json +1 -0
  38. package/tests/snippet-protocol/v1/golden-agents.md +17 -0
  39. package/tests/snippet-protocol/v1/golden-all.json +3 -0
  40. package/tests/snippet-protocol/v1/golden-all.md +53 -0
  41. package/tests/snippet-protocol/v1/golden-claude.json +1 -0
  42. package/tests/snippet-protocol/v1/golden-claude.md +17 -0
  43. package/tests/snippet-protocol/v1/golden-gemini.json +1 -0
  44. package/tests/snippet-protocol/v1/golden-gemini.md +17 -0
package/AGENTS.md CHANGED
@@ -72,3 +72,26 @@ telepty inject --ref --from aigentry-telepty-{cli} aigentry-orchestrator-claude
72
72
  - **Evidence-Based**: 추측 금지. 데이터/로그/테스트 결과 기반 판단.
73
73
  - **Fail Fast**: 에러 즉시 보고. 숨기지 않음.
74
74
  - **Constitution**: ~/projects/aigentry/docs/CONSTITUTION.md 준수.
75
+
76
+ ## Legacy exception — `skill-installer.js`
77
+
78
+ `skill-installer.js` is a **named legacy exception** grandfathered by
79
+ **ADR 2026-05-05-telepty-devkit-boundary §6.2.1**. It is the single
80
+ exception to the telepty/devkit boundary rule and is NOT precedent for
81
+ new placements.
82
+
83
+ - **Scope**: bugfixes, security patches, dependency upgrades only.
84
+ - **No new feature expansion**: net-new functionality (new CLI detection,
85
+ new skill types, new install paths, new flags) MUST land in
86
+ `@dmsdc-ai/aigentry-devkit`. At migration time devkit will introduce
87
+ `aigentry scaffold install-skills`.
88
+ - **Reviewer enforcement**: PR reviewers cite ADR 2026-05-05 §6.2.1 when
89
+ rejecting feature-expansion PRs against `skill-installer.js`.
90
+ - **Migration triggers** (per ADR §6.2.1): ≥2 PRs in 60 days attempting
91
+ net-new features; devkit feature requires functionality only in
92
+ `skill-installer.js`; breaking change to its interface.
93
+
94
+ Per-CLI hook installation, `CLAUDE.md`/`AGENTS.md`/`GEMINI.md` scaffolding,
95
+ project-file generation, and cross-cutting installable skills are
96
+ **devkit-owned** per ADR 2026-05-05 §3.3 — not telepty's responsibility.
97
+ See `@dmsdc-ai/aigentry-devkit` (`aigentry scaffold …`).
package/CHANGELOG.md ADDED
@@ -0,0 +1,436 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
4
+
5
+ ## [0.3.5] — 2026-05-05
6
+
7
+ ### Added — `telepty init --print-snippet` (Issue #8)
8
+
9
+ New subcommand that emits the canonical telepty-baseline snippet to stdout for
10
+ graceful integration into per-CLI agent files. **Mechanism only** — telepty
11
+ emits the versioned snippet text; downstream tooling (`aigentry-devkit
12
+ scaffold --integrate-telepty`) owns idempotent insertion into
13
+ `~/CLAUDE.md` / `~/AGENTS.md` / `~/GEMINI.md`. Boundary contract per ADR
14
+ `2026-05-05-telepty-devkit-boundary` (commit `e4b072b`).
15
+
16
+ ```
17
+ telepty init --print-snippet [--target {claude|agents|gemini|all}] [--format {markdown|json}]
18
+ ```
19
+
20
+ - **argv-only**: never consumes stdin (safe in scripted pipelines).
21
+ - **zero file I/O**: pure stdout emission; nothing read from or written to disk.
22
+ - **deterministic**: byte-identical output for a given (target, format) pair —
23
+ fixtures can be hashed for verification.
24
+ - **LF-only bodies**: no CRLF leakage on cross-platform consumers.
25
+ - **stderr clean**: success path emits no warnings.
26
+
27
+ Spec: `docs/specs/2026-05-05-issue-8-telepty-init.md` (commit `8d2dc94`).
28
+ Implementation: `f5c6bad`. Protocol SSOT: `aigentry-ssot/contracts/telepty-snippet-v1.md`
29
+ (commit `f4ff0cd`). 15 conformance fixtures shipped at `tests/snippet-protocol/v1/`
30
+ covering markdown envelopes (claude, agents, gemini, all), JSON records,
31
+ shell-hazard guards, deterministic LF output, default targeting, unsupported-target
32
+ rejection, internal-failure exit codes, stdin-pipe ignore, devkit-free invocation,
33
+ and the snippet golden fixtures themselves.
34
+
35
+ ### Docs — G7/G8/G9 M0 audit gate closure (commit `d7b8b21`)
36
+
37
+ Per ADR `2026-05-05-telepty-devkit-boundary` §3.1.2 (devkit owns content
38
+ placement; telepty owns mechanism), three gates closed:
39
+
40
+ - **G7 — `README.md`**: removed reference to the rejected `telepty install
41
+ hooks` subcommand. Per ADR §3.1.2, that responsibility lives in devkit.
42
+ - **G8 — `AGENTS.md`**: added Legacy exception subsection documenting the
43
+ remaining devkit-shaped legacy surface.
44
+ - **G9 — `skill-installer.js`**: top-of-file LEGACY header per ADR §6.2.1
45
+ marking the module as legacy-track (devkit migration pending).
46
+
47
+ ### Internal
48
+
49
+ - Cross-LLM review pattern applied: Codex implemented the `init` subcommand
50
+ + fixtures; Claude reviewed and ACCEPTed (commit `d06e1e9`).
51
+ - `test/enforce-report.test.js` version assertion bumped to track release
52
+ (commit `d0f4495`).
53
+
54
+ ### Tests
55
+
56
+ - `test/init.test.js` — full coverage of the new subcommand (snippet
57
+ emission, target/format permutations, stdin-ignore, error exits, devkit-free
58
+ invocation).
59
+ - `tests/snippet-protocol/v1/` — golden fixtures for protocol conformance;
60
+ `npm test` runs `git diff --exit-code` against them so any drift fails CI.
61
+
62
+ ### Invariants preserved
63
+
64
+ - Daemon code unchanged. No new dependencies. No `bin` field changes.
65
+ - Existing CLI subcommands (`allow`, `inject`, `list`, `tui`, `daemon`, …)
66
+ unchanged.
67
+ - Cross-host inject path (0.3.4) unchanged.
68
+
69
+ ## [0.3.4] — 2026-05-05
70
+
71
+ ### Added — Cross-host inject (`<id>@<host>` syntax)
72
+
73
+ Enables `telepty inject <id>@<host> "msg"` to deliver to a remote daemon
74
+ without SSH wrapping, by resolving `<host>` against the peer registry and
75
+ issuing direct HTTP `POST /api/sessions/<id>/inject`. Closes the gap that
76
+ forced operators to either pre-shell into the host or pipe through SSH.
77
+
78
+ - **`connect-http` peer mode** (commit `a92cacc`) — new HTTP-only peer
79
+ registration path that does not require a reverse PTY tunnel; suitable
80
+ for daemons reachable via Tailscale / private DNS.
81
+ - **`TELEPTY_HOST` env parser fix** (commit `a92cacc`) — `<id>@<host>` now
82
+ parses correctly when the host segment contains a port or non-default
83
+ scheme; prior parser dropped the host portion silently.
84
+ - **Peer registry HTTP-only mode** — registry entries can be marked
85
+ HTTP-only so the daemon does not attempt PTY fan-out for them.
86
+
87
+ ### Added — Skill installer auto-detect (`486bc1e`)
88
+
89
+ `telepty install` now auto-detects which AI CLIs are present
90
+ (`claude`, `codex`, `gemini`) and only installs the corresponding skill
91
+ files. Reduces noisy "skipped" log lines and prevents stub installs
92
+ on machines that don't have the target CLI yet.
93
+
94
+ ### Fixed — Node 18 ESM regression (`fc7ff9a`)
95
+
96
+ Pinned `uuid@9` (was floating to v10, which is ESM-only and caused
97
+ `ERR_REQUIRE_ESM` under Node 18 CommonJS consumers).
98
+
99
+ ### Docs
100
+
101
+ - Cross-host inject `<id>@<host>` syntax documented (commit `c8b9bbb`).
102
+ - `[context-ref]` inject protocol standardized across docs (commit `8986a96`).
103
+ - REPORT pattern + orchestrator-id runtime resolution documented in skills
104
+ (commit `658f712`).
105
+ - Korean trigger keywords added to skill `SKILL.md` descriptions for
106
+ cross-locale activation (commit `57f46e1`).
107
+
108
+ ### Note — never published to npm
109
+
110
+ `0.3.4` was version-bumped locally but never reached the registry; this
111
+ entry is added retrospectively alongside the `0.3.5` publish so the
112
+ changelog history matches the git log. Registry consumers go directly
113
+ from `0.3.3` → `0.3.5`.
114
+
115
+ ## [0.3.3] — 2026-05-02
116
+
117
+ ### Added — `inject --submit-force` + idempotent client retry (spec: `docs/superpowers/specs/2026-05-02-submit-force-and-retry.md`)
118
+
119
+ Closes task #347. Two opt-in CLI knobs on `telepty inject` for cases where
120
+ the 0.3.2 prompt-symbol gate has a transient render mismatch (autocomplete
121
+ dropdown open, cursor moved, mid-paste race) and the 504 fall-through
122
+ forces the human user to press Enter manually.
123
+
124
+ - **`--submit-force`** — passes `force: true` to `POST /submit`. Skips
125
+ both Layer 3 (prompt-symbol) and Layer 1 (state-gate) and dispatches
126
+ Enter once via the existing `terminalLevelSubmit` chain (kitty → cmux
127
+ → PTY). Daemon-side `force` semantics already shipped in 0.3.1 for
128
+ `telepty send-key`; this just plumbs the flag through inject.
129
+ - **`--submit-retry N`** (default 1, clamp [0, 3]) — on a 504 response
130
+ with a retry-safe reason, wait 300 ms and retry the same `/submit`
131
+ request up to N times. Retry-safe reasons (idempotent re-fire is
132
+ guaranteed because the body is verifiably still in the input box):
133
+
134
+ | Reason | Source |
135
+ |---|---|
136
+ | `gated_dispatch_unconsumed` | `daemon.js:1680` (verify said body still visible after best-effort dispatch) |
137
+ | `gate_timeout` | reserved (Layer 1 plain timeout — falls through to dispatch in 0.3.1+, not currently a 504 source) |
138
+ | `no_prompt_symbol_seen` | reserved (Layer 3 timeout — currently never emits 504) |
139
+
140
+ Hard-fail reasons (`session_dead`, `session_error`, `session_restarting`,
141
+ `no_state`, `no_state_manager`) and any non-504 status (4xx) **never**
142
+ trigger client-side retry — re-firing won't recover.
143
+
144
+ - **Default behavior preserved**: a bare `telepty inject --submit ...`
145
+ call now retries once on a retry-safe 504. This is a strict improvement
146
+ over 0.3.2 (which surfaced a warning and required manual `send-key`)
147
+ and remains backward-compatible because retry only fires when the
148
+ server tells the client the dispatch demonstrably did not land.
149
+
150
+ ### Tests
151
+
152
+ - `test/inject-submit-flags.test.js` (NEW, 9 tests) — mock-daemon
153
+ coverage:
154
+ - `--submit-force` adds `force:true` to `/submit` body; success line
155
+ renders `[forced]` tag.
156
+ - bare `--submit` does NOT add `force` to body.
157
+ - default `--submit-retry 1` retries once on `gated_dispatch_unconsumed`
158
+ 504 then succeeds; output contains `[retry 1/1]`.
159
+ - `--submit-retry 2` exhausts to 3 calls then prints
160
+ `Submit gated-timeout … after 3 attempts`.
161
+ - `--submit-retry 0` makes exactly 1 call, no `[retry`.
162
+ - `session_dead` 504 → no retry even with `--submit-retry 3`.
163
+ - `no_state` 504 → no retry even with `--submit-retry 3`.
164
+ - `--submit-force --submit-retry 2` preserves `force:true` across retries.
165
+ - 500 error → no retry, prints to stderr.
166
+ - `test/enforce-report.test.js` — version assertion 0.2.0 → 0.3.3.
167
+ - All 174 existing tests pass unchanged.
168
+
169
+ ### Invariants preserved
170
+
171
+ - Daemon code unchanged. `force:true` and the gate layers behave exactly
172
+ as in 0.3.2.
173
+ - `telepty send-key` unchanged.
174
+ - `telepty enter` unchanged.
175
+ - `telepty inject --ref` (no `--submit`) unchanged.
176
+ - Cross-machine remote inject path unchanged (the SSH branch in `cli.js`
177
+ bypasses the new flags by design — remote daemons handle their own
178
+ submit semantics).
179
+ - Exit code on soft failure (504) remains 0; orchestrator scripts that
180
+ check for non-zero exits are unaffected.
181
+
182
+ ## [0.3.2] — 2026-04-26
183
+
184
+ ### Added — Layer 3 prompt-symbol render gate (spec: `docs/superpowers/specs/2026-04-26-prompt-symbol-render-gate.md`)
185
+
186
+ Strictly additive layer above the 0.3.1 `sessionStateManager` gate. Closes
187
+ the recurring "Enter not applied on freshly-spawned `claude`/`codex`" trap
188
+ by directly observing the rendered terminal screen for a per-CLI prompt
189
+ symbol — the only deterministic ready-signal these TUIs expose to external
190
+ automation (no OSC 133, no exit-on-prompt, no socket signal).
191
+
192
+ - **`src/prompt-symbol-registry.js`** (NEW) — per-CLI prompt-symbol catalog:
193
+
194
+ | CLI | Symbol | Codepoint | UTF-8 | Geometry sanity |
195
+ |---|---|---|---|---|
196
+ | `claude` | `❯` | U+276F | `E2 9D AF` | sandwiched between U+2500 (`─`) horizontal-rule borders |
197
+ | `codex` | `›` | U+203A | `E2 80 BA` | model footer (`gpt-N…`) within 2 lines below |
198
+ | `gemini` | `*` | U+002A | `2A` | bracketed by U+2580 (`▀`) above / U+2584 (`▄`) below |
199
+
200
+ `lookup(command)` normalizes path + args (`/usr/local/bin/claude --resume`
201
+ → claude entry; `codex resume` → codex entry). Unknown CLIs return `null`,
202
+ causing the gate to skip cleanly via `unknown_cli`.
203
+
204
+ - **`src/submit-gate.js` `awaitPromptSymbol(session, opts)`** (NEW) — polls
205
+ `cmux read-screen --workspace <id> --lines <n>` (default 30) every
206
+ `pollIntervalMs` (default 150 ms) and resolves only when the symbol has
207
+ been stably detected for ≥ `stabilityMs` (default 200 ms). Bounded by
208
+ `timeoutMs` (default 8000 ms; clamp [500, 30000]). Resolves cleanly with
209
+ one of:
210
+ - `{ ready: true, last_seen_at, waited_ms }`
211
+ - `{ ready: false, reason: 'no_screen_primitive', waited_ms: 0 }` (non-cmux backend)
212
+ - `{ ready: false, reason: 'unknown_cli', waited_ms: 0 }`
213
+ - `{ ready: false, reason: 'no_prompt_symbol_seen', waited_ms }` (timeout, fall through)
214
+ Pure helper: `now`/`sleep`/`readScreen`/`registry` are all injectable for
215
+ deterministic tests (fakeClock harness from `verifyBodyConsumed`).
216
+
217
+ - **`daemon.js` POST /submit** — Layer 3 runs immediately before Layer 1
218
+ on the gated path. Result threaded into success and 504 response bodies
219
+ as optional `prompt_symbol: { found, waited_ms, [reason], [last_seen_at] }`.
220
+ **Never emits its own 504** — best-effort fall-through to Layer 1, which
221
+ retains all existing 0.3.1 outcomes (success / `gated_dispatch_unconsumed`
222
+ / hard-fail). Per-request bypass via `{ "prompt_symbol_gate": false }`
223
+ (Layer 3 only); `force:true` and `TELEPTY_SUBMIT_GATE=off` continue to
224
+ bypass BOTH layers.
225
+
226
+ ### Tests
227
+
228
+ - `test/prompt-symbol-registry.test.js` (NEW) — registry coverage with
229
+ inline cmux read-screen fixtures: claude/codex/gemini detect on idle
230
+ screens, banner-stage rejection (no border geometry), history-echo
231
+ disambiguation (LAST occurrence anchored), `lookup()` path/args
232
+ normalization + case-insensitivity + unknown/null inputs, `byteSeq`
233
+ matches `Buffer.from(symbol, 'utf8')`.
234
+ - `test/submit-gate.test.js` (extended) — `awaitPromptSymbol` covers:
235
+ non-cmux → `no_screen_primitive`; missing workspace → same; unknown CLI
236
+ → `unknown_cli`; stable claude/codex screen → ready after `stabilityMs`;
237
+ empty `readScreen` returns → `no_prompt_symbol_seen` after `timeoutMs`;
238
+ symbol-then-disappear → stability streak resets; injected registry
239
+ override is honored; `readScreen` receives `(workspaceId, tailLines)`.
240
+
241
+ ### Invariants preserved
242
+
243
+ - All 32 existing `test/submit-gate.test.js` tests pass unchanged.
244
+ - `force: true` and `TELEPTY_SUBMIT_GATE=off` bypass BOTH layers.
245
+ - Layer 1 hard-fail short-circuits (`session_dead`/`error`/`restarting`/
246
+ `no_state`/`no_state_manager`) still emit 504; Layer 3 never adds a new
247
+ 504 source.
248
+ - `inject --ref` (no `--submit`) path unchanged.
249
+ - aterm / non-cmux backends skip Layer 3 cleanly via `no_screen_primitive`.
250
+ - Cross-machine remote inject unchanged: Layer 3 runs only on the daemon
251
+ with cmux access; remote daemons fall through.
252
+ - Response shape additive — `prompt_symbol` is an optional field; existing
253
+ callers ignore unknown JSON keys.
254
+
255
+ ## [0.3.1] — 2026-04-26
256
+
257
+ ### Fixed — submit-gate regression cluster (spec: `docs/superpowers/specs/2026-04-26-submit-gate-fixes-v2.md`)
258
+
259
+ Three regressions surfaced post-`0.3.0` against fresh-spawned `claude`/`codex`
260
+ sessions where the gate's strict thresholds and timeout-abandon path made the
261
+ new `/submit` endpoint less reliable than the pre-`0.3.0` blind retry on cold
262
+ REPLs. All three fixes ship in this single patch.
263
+
264
+ - **δ-fix-2 — `send-key` bypass (P0).** `POST /api/sessions/:id/submit` now
265
+ accepts `{ "force": true }` to skip the render-readiness gate and verify
266
+ step, dispatching once via the existing kitty/cmux/PTY chain. `cli.js`
267
+ `send-key` always sets `force:true`, restoring the manual Enter override.
268
+ Response shape additive (`forced:true`); existing callers unaffected.
269
+ - **δ-fix-3 — gate threshold relaxed 0.85 → 0.5 (P1).** `sessionStateManager`
270
+ emits IDLE `confidence=0.6` when neither OSC 133 nor a shell-prompt pattern
271
+ matches (`session-state.js:380`) — the dominant case for AI-CLI TUIs whose
272
+ Unicode-box input line bypasses `PROMPT_PATTERNS`. Default `minConfidence`
273
+ lowered to `0.5` (below the 0.6 silence-fallback with margin); per-request
274
+ override `min_confidence` body field accepted (clamped `[0, 1]`).
275
+ - **δ-fix-4 — timeout extension + best-effort dispatch on timeout (P1).**
276
+ Default `gate_timeout_ms` raised `5000 → 10000` (upper clamp `15000 →
277
+ 30000`) to cover empirical `claude` ready window (3-6 s on fresh spawn).
278
+ On a plain `timeout` reason, `/submit` now dispatches anyway and verifies
279
+ body consumption — the pre-`0.3.0` blind dispatch is restored as a fallback
280
+ while keeping the new honesty signal: 504 only fires when
281
+ `verifyBodyConsumed` confirms the body is still in the input box (new
282
+ `reason: 'gated_dispatch_unconsumed'`). Dispatch-on-timeout success path
283
+ adds `gated_dispatch_after_timeout: true` (additive).
284
+ Hard-fail reasons (`session_dead`/`error`/`restarting`/`no_state`) still
285
+ short-circuit to 504 immediately.
286
+
287
+ ### Invariants preserved
288
+
289
+ - `inject --submit` warm-session reliability ≥99% target (gate short-circuits
290
+ at conf≥0.85 still passes after default drops to 0.5).
291
+ - 504 still emitted in true-fail case (after best-effort dispatch + verify
292
+ reports `still_visible`).
293
+ - `TELEPTY_SUBMIT_GATE=off` daemon-wide escape hatch preserved.
294
+ - `inject --ref` (no `--submit`) path unchanged.
295
+ - 22/23 existing `test/submit-gate.test.js` tests pass unchanged; one test
296
+ (line 185-193) updated to preserve the below-threshold-rejection semantic
297
+ with literals shifted away from the new 0.5 default.
298
+
299
+ ## [0.3.0] — 2026-04-26
300
+
301
+ ### Added — render-gated submit (specs: `docs/superpowers/specs/2026-04-26-inject-submit-enter-reliability.md`)
302
+
303
+ - **`src/submit-gate.js`** — pure helpers exported for unit tests:
304
+ - `awaitReplReady(sessionId, stateManager, opts)` — waits for the target REPL
305
+ to reach an input-ready state (`idle` or `waiting`) with confidence ≥ 0.85
306
+ before Enter is fired. Bounded by `timeoutMs` (default 5000).
307
+ - `verifyBodyConsumed(session, bodyText, opts)` — polls the session's
308
+ `outputRing` for the inject body to disappear from the input box,
309
+ confirming Enter was actually consumed by the REPL (default 1500 ms,
310
+ 200 ms interval). Optimistic when body never visible (ANSI/wrap edge).
311
+ - `isReady`, `isFailed`, `READY_STATES`, `FAIL_STATES` — test surface.
312
+ - **POST `/api/sessions/:id/submit`** rewritten to use the gate by default.
313
+ Flow: gate on REPL readiness → dispatch via existing kitty/cmux/PTY chain →
314
+ verify consumption (when caller passes `injected_body`) → bounded retry.
315
+ Response now includes `gated`, `gate_wait_ms`, `verify` (when applicable).
316
+ - **HTTP `504 gate_timeout` response** on `/api/sessions/:id/submit` when the
317
+ REPL never readies for input within `gate_timeout_ms` (default 5000).
318
+ This is **why this is a minor bump** — consumers may need to handle the new
319
+ status code. 504 (Gateway Timeout) is the correct semantic versus 408 or
320
+ reused 503 — the daemon acted as a gateway to the upstream REPL and the
321
+ upstream did not respond in time.
322
+ - **CLI `inject --submit`** now passes `injected_body` to the daemon for
323
+ consumption verification, removed the legacy 500 ms blind sleep
324
+ (gate handles timing), and treats 504 as a soft failure (logs a clear
325
+ remediation hint, exits 0 — orchestrator scripts depend on exit 0 for
326
+ recoverable conditions).
327
+ - New body fields accepted by `/submit`: `injected_body`, `gate_timeout_ms`,
328
+ `verify_timeout_ms`. Existing `pre_delay_ms` / `retries` / `retry_delay_ms`
329
+ remain accepted for back-compat.
330
+ - **`TELEPTY_SUBMIT_GATE=off`** env var — escape hatch to revert to the 0.2.x
331
+ blind retry path for parity testing or rollback.
332
+
333
+ ### Changed
334
+
335
+ - POST `/api/sessions/:id/submit` is no longer open-loop. Default behavior
336
+ is gated; legacy blind retry preserved only behind `TELEPTY_SUBMIT_GATE=off`.
337
+ - CLI `✅ Submitted via <strategy>` line now optionally appends
338
+ `[gate <N>ms]` when the gate had to wait. Default-on; pre-existing
339
+ format preserved when gate fast-paths (warm sessions).
340
+ - `bus` event `submit` now carries optional fields `gated`, `gate_wait_ms`,
341
+ `verify` (additive — consumers ignore unknown fields).
342
+
343
+ ### Fixed
344
+
345
+ - Root cause: `/submit` previously fired Enter open-loop with a ~2.1 s
346
+ blind retry budget while a fresh `claude` REPL needed 3–6 s to render
347
+ (welcome banner, trust dialog, prompt setup). The legacy retry loop
348
+ also discarded `terminalLevelSubmit`'s return value, so the reported
349
+ `(N attempts)` count did not reflect verified dispatches. The new
350
+ gate observes the existing `sessionStateManager` (`idle` / `waiting`
351
+ with confidence ≥ 0.85) before dispatch, eliminating the race.
352
+ - Recurring orchestrator UX trap (parallel to #329 Track E27) where
353
+ every `inject --submit` required a manual `sleep N && telepty send-key
354
+ <id> enter` follow-up. Spec target: ≥ 99% on a 100× spawn-and-inject
355
+ E2E harness (current baseline ~0%); E2E harness execution is dispatched
356
+ to the builder (out of scope for this commit).
357
+
358
+ ### Tests
359
+
360
+ - `test/submit-gate.test.js` — 23 new unit tests (all pass) covering
361
+ `awaitReplReady` fast-paths, transition resolution, timeout, fail-state
362
+ short-circuits; `verifyBodyConsumed` happy-path / optimistic / timeout /
363
+ empty / no-ring / whitespace normalization / ANSI strip / injectable
364
+ clock for deterministic timing.
365
+ - Pre-existing test suite is unmodified; integration coverage of the new
366
+ endpoint behavior is delegated to the builder per SAWP scope.
367
+
368
+ ### Compatibility / migration
369
+
370
+ - **Default behavior changes** for callers of `/api/sessions/:id/submit`:
371
+ responses now succeed only when the REPL reaches readiness within
372
+ `gate_timeout_ms`. Most callers will see equivalent or better behavior;
373
+ callers that depended on "best effort fire-and-forget" can opt out via
374
+ `TELEPTY_SUBMIT_GATE=off`.
375
+ - `inject --ref` (without `--submit`), `telepty allow`, `telepty list`,
376
+ and `telepty send-key` semantics unchanged.
377
+ - Aterm sessions unaffected (gate is bypassed via existing
378
+ `session.type === 'aterm'` guards).
379
+ - No new external dependencies (Rule 17). No schema, persistence, or
380
+ state-machine changes (gate is read-only on `sessionStateManager`).
381
+
382
+ ## [0.2.0] — 2026-04-15
383
+
384
+ ### Added — REPORT enforcement (specs/enforce-report-spec.md)
385
+
386
+ - **New bus event types** for observable REPORT lifecycle:
387
+ - `TASK_IDLE_NO_REPORT` — fires once on idle transition for inject-driven sessions
388
+ - `TASK_COMPLETE_WITH_REPORT` — fires when matching REPORT inject detected via reverse-match
389
+ - `TASK_BLOCKED_WITH_REASON` — fires on `STATUS: blocked` reply inject
390
+ - `TASK_DISMISSED` — fires on `STATUS: dismissed` inject OR via DELETE endpoint
391
+ - `TASK_DEAD_NO_REPORT` — fires when session dies with pending report (attaches `auto_summary`)
392
+ - **New HTTP endpoints** on daemon:
393
+ - `GET /api/pendingReports/:id` — inspect pending report entry + optional auto_summary
394
+ - `DELETE /api/pendingReports/:id` — orchestrator-side dismissal; fires `TASK_DISMISSED`
395
+ - **New module** `src/report-enforcement.js` exports pure helpers:
396
+ - `classifyReportPrompt(prompt)` — classify inject prompt by prefix
397
+ - `buildAutoSummary(session, opts)` — scrape last N non-blank lines from outputRing with ANSI stripping and secret redaction
398
+ - **REPORT detection via reverse-match** in POST `/api/sessions/:id/inject`:
399
+ - An inject with `from=X` whose prompt starts with a REPORT prefix (`REPORT:`, `STATUS:`, `SPEC:`, `OWNER-DIAGNOSIS:`, `ENFORCE-SPEC:`, `ENFORCE-IMPLEMENTED:`, `LOG-FIX-SPEC:`, `LOG-FIX-IMPLEMENTED:`, `FIX-SPEC:`, `FIX-IMPLEMENTED:`, `SPEC-SYNC:`, `DIAGNOSIS:`) and whose recipient matches `pendingReports[X].source` fires the matching enforcement event.
400
+ - Prevents false positives: prefix alone is NOT enough; reverse-match to originating inject required.
401
+ - **Auto-summary with secret redaction**:
402
+ - Strips ANSI via shared regex
403
+ - Filters blank lines
404
+ - Caps at `DELIBERATION_REPORT_AUTO_SUMMARY_LINES` (default 40) + `DELIBERATION_REPORT_AUTO_SUMMARY_MAX_BYTES` (default 4096)
405
+ - Redacts `api_key`, `password`, `token`, `secret` assignment patterns → `[REDACTED]`
406
+ - Attached to `TASK_DEAD_NO_REPORT` events and GET query responses
407
+
408
+ ### Changed
409
+
410
+ - `sessionStateManager.onTransition` handler now fires the enforcement events above. Legacy `TASK_COMPLETE:` text-inject to source session is preserved during 0.2.x grandfather period.
411
+ - Legacy auto-report paths (health-poll idle threshold + ready-WS signal) now coordinate via `pendingReports[id].idleNotified` flag to prevent double-fire.
412
+ - `pendingReports[id]` schema extended with `awaitingReport: true`, `idleNotified: bool`, `idleAt: ISO8601`. Entry is now cleared only when REPORT arrives, session dies, or orchestrator dismisses.
413
+ - Duplicate pendingReports overwrite now emits `[AUTO-REPORT] overwritten pending` warning.
414
+
415
+ ### Configuration (new env vars)
416
+
417
+ - `DELIBERATION_REPORT_AUTO_SUMMARY_ON_QUERY` — bool, default `true`. Gates auto_summary on GET pendingReports.
418
+ - `DELIBERATION_REPORT_AUTO_SUMMARY_LINES` — int, default 40. Max lines in auto_summary.
419
+ - `DELIBERATION_REPORT_AUTO_SUMMARY_MAX_BYTES` — int, default 4096. Byte cap on auto_summary.
420
+
421
+ ### Deprecated
422
+
423
+ - `reportTimeoutSecs` env var — emits deprecation warning if set. Removed in 0.3.x. Evidence (7.5s–649s task range) showed a default timer is arbitrary and prone to false timeouts; replaced with event-driven detection (idle + dead + explicit query).
424
+
425
+ ### Tests
426
+
427
+ - `test/report-enforcement.test.js` — 28 new unit tests for `classifyReportPrompt`, `buildAutoSummary`, regex exports
428
+ - `test/enforce-report.test.js` — 11 new integration tests for bus events and endpoints
429
+ - Full suite: **170/170 passing** (131 pre-existing + 39 new)
430
+
431
+ ### Migration notes
432
+
433
+ - **No orchestrator-side changes required** to benefit. New bus events flow passively; legacy `TASK_COMPLETE:` text-inject still fires.
434
+ - Consumers that subscribe to the bus now see richer event types — optional to consume.
435
+ - Orchestrators wanting to dismiss a pending report can use `DELETE /api/pendingReports/{id}`.
436
+ - Orchestrators wanting on-demand summary can use `GET /api/pendingReports/{id}` (honors `DELIBERATION_REPORT_AUTO_SUMMARY_ON_QUERY`).
package/CLAUDE.md CHANGED
@@ -65,10 +65,14 @@ npm version patch --no-git-tag-version && npm publish --access public
65
65
  - inject 시 발신자 session ID (`--from`)를 항상 포함
66
66
  - PTY `\r` 직접 의존 금지
67
67
 
68
- ## 최근 주요 변경 (v0.1.58–0.1.62)
68
+ ## 최근 주요 변경 (v0.1.58–0.3.3)
69
69
 
70
70
  | 버전 | 변경 |
71
71
  |------|------|
72
+ | 0.3.3 | inject `--submit-force` (gate bypass) + idempotent `--submit-retry` (default 1, retry-safe 504만). 클라이언트 측 변경, daemon 무수정. task #347. |
73
+ | 0.3.2 | Layer 3 prompt-symbol 렌더 게이트 — claude/codex/gemini 별 prompt symbol을 cmux read-screen으로 polling. |
74
+ | 0.3.1 | 게이트 임계값 0.85 → 0.5 완화 + dispatch-on-timeout best-effort + send-key force=true 우회 추가. |
75
+ | 0.3.0 | Render-gated submit (sessionStateManager 기반). Enter 송신 전 REPL ready 검증. |
72
76
  | 0.1.62 | TUI 태스크 추적 — bus 이벤트에서 [태그] 자동 파싱, 세션별 상태 표시 |
73
77
  | 0.1.61 | reconnect 시 resize/\x0c 제거 (멀티터미널 깜빡임 수정) |
74
78
  | 0.1.60 | TUI P1 — s=start, k=kill, p=purge stale |
package/README.md CHANGED
@@ -53,6 +53,9 @@ telepty broadcast "status report"
53
53
  | `telepty list [--json]` | List sessions across all discovered hosts |
54
54
  | `telepty attach [id[@host]]` | Attach to a session (interactive picker if no ID) |
55
55
  | `telepty inject <id[@host]> "text"` | Inject text into a session |
56
+ | `telepty inject --submit <id> "text"` | Inject text and press Enter (render-gated, retries once on safe gate-timeout) |
57
+ | `telepty inject --submit --submit-force <id> "text"` | As above, but bypass the gate (skip Layer 1/3 detection — opt-in escape hatch) |
58
+ | `telepty inject --submit --submit-retry N <id> "text"` | Override retry count [0–3] on safe 504 (default 1) |
56
59
  | `telepty enter <id[@host]>` | Send Enter/Return to a session |
57
60
  | `telepty multicast <id1,id2> "text"` | Inject into multiple sessions |
58
61
  | `telepty broadcast "text"` | Inject into ALL sessions |
@@ -69,13 +72,31 @@ telepty broadcast "status report"
69
72
 
70
73
  telepty auto-discovers sessions across your Tailnet. All commands (`list`, `attach`, `inject`, `rename`, `multicast`, `broadcast`) work seamlessly across machines.
71
74
 
72
- When the same session ID exists on multiple hosts, disambiguate with `session_id@host`:
75
+ ### `<id>@<host>` syntax
76
+
77
+ To target a specific host (when the same session ID exists on multiple hosts,
78
+ or when there is no Tailnet auto-discovery), append `@<host>` to the session
79
+ ID. `<host>` can be a hostname, LAN IP, or Tailnet name.
73
80
 
74
81
  ```bash
82
+ # Hostname / Tailnet name
75
83
  telepty inject my-session@macbook "hello"
76
84
  telepty attach worker@server-01
85
+
86
+ # LAN IP — useful when no Tailnet is configured
87
+ telepty inject orchestrator-claude@172.28.4.165 "ping"
88
+ telepty read-screen build-runner@10.0.0.42 --lines 50
77
89
  ```
78
90
 
91
+ **Requirements**:
92
+ - The remote daemon must be reachable on port **3848** from the calling host
93
+ (LAN routing, firewall rules, or Tailscale).
94
+ - No SSH or `sshd` is required on either side — the call hits the remote
95
+ daemon's HTTP API directly. This is the recommended path for laptop
96
+ daemons that don't run sshd.
97
+ - The `@<host>` qualifier works for `inject`, `attach`, `read-screen`,
98
+ `enter`, `multicast`, and `rename`.
99
+
79
100
  ## How It Works
80
101
 
81
102
  ```
@@ -89,6 +110,54 @@ CLI (telepty) ──> HTTP/WS ──> Daemon (:3848)
89
110
  - **`inject`** delivers text via the fastest available path: kitty terminal API, WebSocket, or UDS (Unix Domain Socket for embedded integrations)
90
111
  - **`submit`** is handled separately from text injection for reliability across all AI CLIs
91
112
 
113
+ ## `[context-ref]` Protocol — long payloads via shared file
114
+
115
+ When a sender uses `telepty inject --ref <file> <target> "<message>"`, telepty
116
+ stores the payload in a shared file under `~/.telepty/shared/<sha256>.md` and
117
+ injects only a short pointer prompt of the form:
118
+
119
+ ```
120
+ [context-ref] Read ~/.telepty/shared/<sha256>.md and use it as the source of truth for this task.
121
+ <inline message>
122
+ ```
123
+
124
+ This avoids prompt rot in the receiving session (and in the orchestrator's
125
+ window when the reply is small).
126
+
127
+ ### Receiver contract
128
+
129
+ The receiving AI session is expected to:
130
+ 1. Detect the `[context-ref]` prefix on the first line.
131
+ 2. Read the file at the absolute path.
132
+ 3. Treat the file contents as the **authoritative payload** for the task — the
133
+ inline message is supplementary (topic / hint), not the source of truth.
134
+
135
+ ### Storage location
136
+
137
+ - File path: `~/.telepty/shared/<sha256>.md` (sha256 of payload body)
138
+ - Created with mode `0600`; readable only by the local user
139
+ - Persists across sessions; not garbage-collected automatically (run
140
+ `telepty clean --shared` to prune)
141
+
142
+ ### When to use `--ref`
143
+
144
+ - Payload exceeds ~1KB or contains structured content (code, logs, tables).
145
+ - You want the receiver to load the payload deterministically rather than
146
+ paraphrase it from the inject prompt.
147
+ - You're orchestrating a multi-hop conversation where the orchestrator should
148
+ not see the full payload in its own context window.
149
+
150
+ ### Integration scope
151
+
152
+ Per-agent receiver integrations (auto-loading the file via Claude Code
153
+ `UserPromptSubmit` hooks, Codex `AGENTS.md` directives, etc.) are **out of
154
+ scope for telepty core** — they live in the agent's own configuration.
155
+ Per-CLI hook installation lives in devkit: run `aigentry scaffold
156
+ install-hooks {claude|codex|gemini}` after installing
157
+ `@dmsdc-ai/aigentry-devkit`. (Older drafts proposed a receiver-side
158
+ `telepty install` subcommand for this; that direction is rejected per ADR
159
+ 2026-05-05-telepty-devkit-boundary §3.1.2 / §3.4 row 2.)
160
+
92
161
  ## Inject Delivery Paths
93
162
 
94
163
  | Priority | Method | When |