|
|
@@ -1014,3 +1014,88 @@ def run(ctx):
|
|
|
"standalone resumes carry no plan section")
|
|
|
check(wait_task_done(auth, probe_id, timeout=60) is not None,
|
|
|
"the probe task completes after its resume")
|
|
|
+
|
|
|
+ print("== goal 4 t1: force wake — the human resume for a parked agent ==")
|
|
|
+ # The goal-2 stall class: the parked run has NO pending wake (a
|
|
|
+ # draft task never enqueued → no terminal transition → parked
|
|
|
+ # forever). Force wake is the human recovery lever.
|
|
|
+ fake.goal_script = [("suspend",)]
|
|
|
+ check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
|
|
|
+ or False, timeout=60, what="agent parks for force wake") is not None,
|
|
|
+ "the goal agent parks before the force-wake scenario")
|
|
|
+ # The stall fixture: an agent-created draft that was never enqueued
|
|
|
+ # (typo'd enqueue) — statuses must name it as NOT enqueued.
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
|
|
|
+ "session_id": agent_session, "title": "QA sweep",
|
|
|
+ "mandate": "Run the QA sweep.", "mode": "test"})
|
|
|
+ check(status == 200 and body.get("ok") is True,
|
|
|
+ "the stall fixture draft task creates (%s)" % body.get("error"))
|
|
|
+ stall_draft = int(body["task_id"])
|
|
|
+ goal_page = http_get_page(auth, "/goals/%d" % goal1)
|
|
|
+ check('stm-action="goal.force_wake"' in goal_page,
|
|
|
+ "the goal page authors the Force wake button while the run is parked")
|
|
|
+ check('stm-if="goal.force_wake != null"' in goal_page,
|
|
|
+ "the Force wake button is gated on the action being offered")
|
|
|
+ # No wake without the press: parked stays parked.
|
|
|
+ parked_runs = len(fake.goal_assignments)
|
|
|
+ check(quiet_window(quiet_seconds(),
|
|
|
+ lambda: len(fake.goal_assignments) == parked_runs,
|
|
|
+ "no press, no wake"),
|
|
|
+ "without a press the parked agent never wakes")
|
|
|
+ state = goal_state(goal1)
|
|
|
+ check(state.get("force_wake") is not None,
|
|
|
+ "the goal state exposes the offered force-wake action")
|
|
|
+ act(auth, state["force_wake"], {})
|
|
|
+ check(wait_for(lambda: len(fake.goal_assignments) > parked_runs or False,
|
|
|
+ timeout=30, what="force-woke agent resumes") is not None,
|
|
|
+ "the force-woke agent resumes immediately (no coalesce wait)")
|
|
|
+ woke_notice = fake.goal_assignments[-1].get("resume_notice") or ""
|
|
|
+ check("force-woke this goal's supervision" in woke_notice
|
|
|
+ and "no pending wake" in woke_notice,
|
|
|
+ "the force-wake resume leads with the explicit wake line")
|
|
|
+ check(("draft (NOT enqueued" in woke_notice)
|
|
|
+ and ("saddle task enqueue %d" % stall_draft) in woke_notice,
|
|
|
+ "the statuses list names the never-enqueued draft with the enqueue fix")
|
|
|
+ check("Task statuses" in woke_notice,
|
|
|
+ "the force-wake notice still carries the standard batched report")
|
|
|
+ # The flag is consumed: re-park (script), no second wake fires.
|
|
|
+ check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
|
|
|
+ or False, timeout=60, what="woken agent re-parks") is not None,
|
|
|
+ "the woken agent parks again (script)")
|
|
|
+ woke_runs = len(fake.goal_assignments)
|
|
|
+ check(quiet_window(quiet_seconds(),
|
|
|
+ lambda: len(fake.goal_assignments) == woke_runs,
|
|
|
+ "consumed flag never re-fires"),
|
|
|
+ "the consumed flag does not re-wake (one wake per press)")
|
|
|
+ # Guards: a paused goal refuses the wake (Resume is its lever).
|
|
|
+ state = goal_state(goal1)
|
|
|
+ act(auth, state["pause"], {})
|
|
|
+ conn = db()
|
|
|
+ paused_flag = conn.execute("select force_wake from goals where id = ?",
|
|
|
+ (goal1,)).fetchone()
|
|
|
+ conn.close()
|
|
|
+ check(paused_flag is not None and paused_flag[0] in (0, False),
|
|
|
+ "pausing does not set the flag")
|
|
|
+ state = goal_state(goal1)
|
|
|
+ check(state.get("force_wake") is None,
|
|
|
+ "a paused goal does not author the Force wake action")
|
|
|
+ act(auth, state["resume"], {})
|
|
|
+ check(wait_for(lambda: goal_state(goal1)["status"] == "active" or False,
|
|
|
+ timeout=30, what="goal reactivates") is not None,
|
|
|
+ "the goal reactivates for the guard checks")
|
|
|
+ # Non-suspended run refuses: run is parked again (script parked it
|
|
|
+ # above), wake it, let it park, then a second press while parked is
|
|
|
+ # fine — the 400 path needs a NON-suspended run. Start there: wake
|
|
|
+ # once more and press while it is working.
|
|
|
+ state = goal_state(goal1)
|
|
|
+ act(auth, state["force_wake"], {})
|
|
|
+ check(wait_for(lambda: len(fake.goal_assignments) > woke_runs or False,
|
|
|
+ timeout=30, what="second wake fires") is not None,
|
|
|
+ "a second press wakes again (re-press is allowed)")
|
|
|
+ # While it is mid-script (not yet parked), the action is hidden.
|
|
|
+ check(goal_state(goal1).get("force_wake") is None
|
|
|
+ or task_state(auth, run_task)["status"] != "suspended",
|
|
|
+ "the wake action hides while the run is not parked")
|
|
|
+ check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
|
|
|
+ or False, timeout=60, what="run parks for cleanup") is not None,
|
|
|
+ "the run parks again")
|