Problem
A successful pty send proves that terminal input was accepted, not that Codex submitted or queued the message. st2 ding currently collapses those two facts into Delivered.
Two outcomes were observed on the same Codex seat minutes apart:
- the notice was queued correctly and appeared under
Messages to be submitted after next tool call;
- the exact notice remained stranded in the composer under
tab to queue message and was never acted on until an external key:return was sent.
The second case is a false delivery receipt: the message reached the input line, but not the agent.
Current boundary
The production path builds one terminal command containing bracketed paste, a fixed 0.5 second delay, and key:return:
|
/// Recovery delivery: one bounded PTY transaction containing paste and Return. |
|
pub fn pty_delivery_args(session: &str, text: &str) -> Vec<String> { |
|
vec![ |
|
"send".into(), |
|
session.into(), |
|
"--with-delay".into(), |
|
"0.5".into(), |
|
"--seq".into(), |
|
bracketed_paste(text), |
|
"--seq".into(), |
|
"key:return".into(), |
|
] |
PtyPoker::poke_with returns PokeOutcome::Delivered after that command exits successfully; it performs no post-Return observation:
|
/// Central production safety path shared by inbox DING and any caller that needs to record an |
|
/// attempt immediately before the only command containing Return. |
|
pub fn poke_with( |
|
&self, |
|
text: &str, |
|
before_submit: &mut dyn FnMut() -> anyhow::Result<()>, |
|
) -> anyhow::Result<PokeOutcome> { |
|
// Recovery contract: PTY-alive delivery is transport-first. Keep one owned payload, |
|
// stage it once, wait a bounded interval, then submit exactly once; pane heuristics remain |
|
// diagnostic only and must not silently suppress messaging. |
|
self.run(pty_delivery_args(&self.session, text), "send") |
|
.map_err(|error| anyhow::anyhow!("staging DING payload: {error}"))?; |
|
before_submit()?; |
|
Ok(PokeOutcome::Delivered) |
|
} |
|
} |
|
|
|
impl Poker for PtyPoker { |
|
fn poke(&self, text: &str) -> anyhow::Result<PokeOutcome> { |
|
self.poke_with(text, &mut || Ok(())) |
|
} |
|
|
|
fn retry_staged(&self, text: &str) -> anyhow::Result<PokeOutcome> { |
|
let _ = text; |
|
self.run(pty_submit_args(&self.session), "send") |
|
.map_err(|error| anyhow::anyhow!("submitting staged DING payload: {error}"))?; |
|
Ok(PokeOutcome::Delivered) |
The code already recognizes Messages to be submitted after next tool call as an active/blocked Codex screen shape, but the production transport-first path does not use that observation to establish receipt:
|
fn interaction_blocked(plain: &str) -> bool { |
|
inherited_progress_line(plain) |
|
|| plain.contains("Working (") |
|
|| plain.contains("esc to interrupt") |
|
|| plain.contains("Esc to interrupt") |
|
|| plain.contains("ctrl+c to interrupt") |
|
|| plain.contains("Messages to be submitted after next tool call") |
|
|| plain.contains("press esc to interrupt and send") |
|
|| plain.contains("Our systems are thinking a bit more") |
|
|| plain.contains("Retry with a faster model") |
|
|| plain.contains("Create a plan?") |
|
|| looks_like_choice_menu(plain) |
|
} |
Trigger characterization
This does not reduce to “mid-tool-call versus idle prompt.” Both observed surfaces are active-turn Codex states: the successful case showed the queued-message banner, while the stranded case showed the queue hint beside a populated composer.
The remaining discriminant is a finer Codex input-mode or timing transition that st2 does not observe. That is precisely why PTY process success cannot serve as the delivery receipt.
Expected contract
One of these boundaries should be explicit:
Delivered requires a post-Return observation that the exact notice left the composer and became submitted or queued; or
- the transport result is named something like
InputSent, while the notice remains pending until a separate receipt is observed.
If the exact notice remains in the composer, st2 should retain staged ownership and retry submission by inspection, without re-pasting or treating the inbox message as delivered.
Related but distinct
Versions
- st2
0.1.0+6f1fe48
- source
6f1fe484b517406b80d06f335997859b1a53f609
- Codex CLI
0.145.0
- pty
0.12.0
- Linux
Problem
A successful
pty sendproves that terminal input was accepted, not that Codex submitted or queued the message.st2 dingcurrently collapses those two facts intoDelivered.Two outcomes were observed on the same Codex seat minutes apart:
Messages to be submitted after next tool call;tab to queue messageand was never acted on until an externalkey:returnwas sent.The second case is a false delivery receipt: the message reached the input line, but not the agent.
Current boundary
The production path builds one terminal command containing bracketed paste, a fixed 0.5 second delay, and
key:return:st2/src/ding/mod.rs
Lines 131 to 142 in 6f1fe48
PtyPoker::poke_withreturnsPokeOutcome::Deliveredafter that command exits successfully; it performs no post-Return observation:st2/src/ding/mod.rs
Lines 210 to 236 in 6f1fe48
The code already recognizes
Messages to be submitted after next tool callas an active/blocked Codex screen shape, but the production transport-first path does not use that observation to establish receipt:st2/src/ding/harness/codex.rs
Lines 202 to 214 in 6f1fe48
Trigger characterization
This does not reduce to “mid-tool-call versus idle prompt.” Both observed surfaces are active-turn Codex states: the successful case showed the queued-message banner, while the stranded case showed the queue hint beside a populated composer.
The remaining discriminant is a finer Codex input-mode or timing transition that st2 does not observe. That is precisely why PTY process success cannot serve as the delivery receipt.
Expected contract
One of these boundaries should be explicit:
Deliveredrequires a post-Return observation that the exact notice left the composer and became submitted or queued; orInputSent, while the notice remains pending until a separate receipt is observed.If the exact notice remains in the composer, st2 should retain staged ownership and retry submission by inspection, without re-pasting or treating the inbox message as delivered.
Related but distinct
Versions
0.1.0+6f1fe486f1fe484b517406b80d06f335997859b1a53f6090.145.00.12.0