mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Fix: print JSON on live-poll --reply success (#800)
Successful --reply was exit 0 with empty stdout, so agents could not tell delivery from a hang. Prepared with AI assistance. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
Abdul Wahab
co-authored by
Cursor
parent
3bdb9ff06c
commit
67d018fe05
@@ -596,6 +596,18 @@ fn write_carbonize_banner(event: &Map<String, Value>, io: &mut Io) {
|
||||
}
|
||||
}
|
||||
|
||||
fn reply_ack_json(reply: &Reply) -> Value {
|
||||
let mut m = Map::new();
|
||||
m.insert("ok".into(), json!(true));
|
||||
m.insert("id".into(), json!(reply.id));
|
||||
m.insert("status".into(), json!(reply.ty));
|
||||
if let Some(f) = &reply.file {
|
||||
m.insert("file".into(), json!(f));
|
||||
}
|
||||
m.insert("_instructions".into(), json!("Poll again now."));
|
||||
Value::Object(m)
|
||||
}
|
||||
|
||||
/// JS: printPollEvent(event) — a wire-supplied `_instructions` must never
|
||||
/// win over the locally generated one (#488).
|
||||
fn print_poll_event(event: &mut Value, io: &mut Io) {
|
||||
@@ -718,7 +730,13 @@ pub fn run(args: &[String], io: &mut Io) -> i32 {
|
||||
}
|
||||
};
|
||||
return match post_reply(&base, &token, &reply) {
|
||||
Ok(()) => 0,
|
||||
Ok(()) => {
|
||||
println(
|
||||
io,
|
||||
&serde_json::to_string(&reply_ack_json(&reply)).unwrap_or_default(),
|
||||
);
|
||||
0
|
||||
}
|
||||
Err(PollError::ConnRefused) => {
|
||||
io.err(&format!(
|
||||
"Live server not running. Start one with: {}\n",
|
||||
@@ -839,4 +857,40 @@ mod tests {
|
||||
}));
|
||||
assert!(parsed.get("_instructions").is_none(), "{}", parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_ack_json_includes_file_when_present() {
|
||||
let reply = Reply {
|
||||
id: "ab12cd34".into(),
|
||||
ty: "done".into(),
|
||||
message: None,
|
||||
file: Some("index.html".into()),
|
||||
data: None,
|
||||
source_event_type: None,
|
||||
};
|
||||
let parsed = reply_ack_json(&reply);
|
||||
assert_eq!(parsed["ok"], json!(true));
|
||||
assert_eq!(parsed["id"], json!("ab12cd34"));
|
||||
assert_eq!(parsed["status"], json!("done"));
|
||||
assert_eq!(parsed["file"], json!("index.html"));
|
||||
assert_eq!(parsed["_instructions"], json!("Poll again now."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_ack_json_omits_file_when_absent() {
|
||||
let reply = Reply {
|
||||
id: "ab12cd34".into(),
|
||||
ty: "steer_done".into(),
|
||||
message: None,
|
||||
file: None,
|
||||
data: None,
|
||||
source_event_type: None,
|
||||
};
|
||||
let parsed = reply_ack_json(&reply);
|
||||
assert_eq!(parsed["ok"], json!(true));
|
||||
assert_eq!(parsed["id"], json!("ab12cd34"));
|
||||
assert_eq!(parsed["status"], json!("steer_done"));
|
||||
assert!(parsed.get("file").is_none(), "{}", parsed);
|
||||
assert_eq!(parsed["_instructions"], json!("Poll again now."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1740,7 +1740,7 @@ Conventions: every script's "run directly" guard is `process.argv[1]` ending wit
|
||||
|
||||
#### `live-poll.mjs` -> `impeccable poll`
|
||||
- Invoked from live.md poll loop; `--reply` forms quoted in `_instructions` (see instructions.mjs strings in 6.3/below).
|
||||
- Args: `--stream`, `--timeout=MS` (one-shot total, default 600000), `--types=A,B`, `--ack-timeout=MS` (stream, default 600000), `--reply <id> <status> [--file PATH] [--data JSON] [message]`, `--help`. `--reply` errors (stderr, exit 1): `Usage: node "<abs>/live-poll.mjs" --reply <id> <status> [--file path] [--data '<json>'] [message]` + `Missing event id after --reply.` / `The value after --reply must be the event id, not the status "done". Use --reply EVENT_ID done.` / `Missing reply status after event id "X".`; `--data must be valid JSON: <err>`.
|
||||
- Args: `--stream`, `--timeout=MS` (one-shot total, default 600000), `--types=A,B`, `--ack-timeout=MS` (stream, default 600000), `--reply <id> <status> [--file PATH] [--data JSON] [message]`, `--help`. `--reply` success (stdout, exit 0): one compact JSON line `{ok:true,id,status,file? (only when --file was passed),_instructions:'Poll again now.'}`. `--reply` errors (stderr, exit 1): `Usage: node "<abs>/live-poll.mjs" --reply <id> <status> [--file path] [--data '<json>'] [message]` + `Missing event id after --reply.` / `The value after --reply must be the event id, not the status "done". Use --reply EVENT_ID done.` / `Missing reply status after event id "X".`; `--data must be valid JSON: <err>`.
|
||||
- Needs `server.json`; else stderr `No running live server found. Start one with: node "<abs>/live.mjs"` exit 1.
|
||||
- One-shot: loops `GET /poll?token&timeout=<slice ≤270000>&leaseMs=600000[&types]` until an event or total deadline; prints one JSON line (`console.log(JSON.stringify(event))`) with `_instructions` added by `instructionsForEvent` (unless already present). For `accept`/`discard`: spawns `node live-accept.mjs --id ID (--discard | --variant N) [--page-url U] [--param-values JSON]` (30 s), sets `event._acceptResult` (parse failure/throw → `{handled:false, mode:'error', error}`), then POSTs completion `{id, type: completionType, sourceEventType: event.type, message: _acceptResult.error, file: _acceptResult.file, data: {carbonize:true}?}` where completionType = discard: `discarded` if handled else `error`; accept: `agent_done` if handled&carbonize, `complete` if handled, `error` if mode error or (svelte-component unhandled), else `agent_done`; sets `event._completionAck = {ok:true, type}` (+ `final:false, requiresComplete:true, nextCommand:'live-complete.mjs --id <id>', message:'Carbonize cleanup must be verified, then the session must be completed explicitly before polling again.'` for carbonize) or `{ok:false, error}`. Stderr banners: manual_edit_apply → 4-line banner starting `Manual Apply action required: edit source, then reply with \`live-poll.mjs --reply <id> done --data '<json>'\`.`; carbonize → `⚠ Carbonize cleanup REQUIRED before next poll. After cleanup, run live-complete.mjs --id <id>. See reference/live.md "Required after accept".`
|
||||
- Stream: stderr `[impeccable-poll] stream mode: one JSON object per line on stdout; use --reply while this process stays running`; after each reply-needing event waits (poll `/status` every 400 ms) until the id leaves `pendingEvents` (else `Timed out waiting for --reply on event <id>` exit 1); returns on `exit`.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"stdout": "",
|
||||
"stdout": "{\"ok\":true,\"id\":\"ab12cd34\",\"status\":\"done\",\"file\":\"index.html\",\"_instructions\":\"Poll again now.\"}\n",
|
||||
"stderr": "",
|
||||
"exit": 0,
|
||||
"signal": null
|
||||
@@ -44,7 +44,7 @@
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"stdout": "",
|
||||
"stdout": "{\"ok\":true,\"id\":\"ab12cd34\",\"status\":\"steer_done\",\"_instructions\":\"Poll again now.\"}\n",
|
||||
"stderr": "",
|
||||
"exit": 0,
|
||||
"signal": null
|
||||
|
||||
Reference in New Issue
Block a user