Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **The drive root can interrupt its own Orca lane.** `orca_send_ends_session` blocked every
`orca terminal send --interrupt`, so a root could not stop a lane mid-turn and redirect it;
ordinary text only queued until the lane's turn ended. A send with `--interrupt` and one literal
`--terminal` now passes when the caller is the main session whose `ORCA_TERMINAL_HANDLE`
coordinates the Run of that terminal's dispatch. Lanes, subagents, other Runs, handles named at
run time, and exit text such as `exit` or `/quit` stay blocked.

- **The drive root can stop a done Orca lane in its own Run.** `orca_ends_session`
blocked every `orca orchestration worker-stop`, so a root that had decided a lane was
done had to message the lane to call `worker_done`. One literal `worker-stop --dispatch <ctx>`
Expand Down
9 changes: 7 additions & 2 deletions captain_hook/builtin_packs/general/hooks/_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ def inline_workers(*handles: str) -> str:
return json.dumps({"ok": True, "result": {"workers": rows, "page": {}, "scope": {"source": "all"}}})


def inline_worker(handle: str, status: str, stage: str = "settled") -> str:
def inline_worker(handle: str, status: str, stage: str = "settled", run: str = "run_inline") -> str:
row = {
"dispatchId": "ctx_settled",
"runId": "run_inline",
"runId": run,
"dispatchStatus": status,
"agentTerminalHandle": handle,
"projection": {"stage": {"detail": stage}},
Expand Down Expand Up @@ -1216,6 +1216,11 @@ def own_dispatch(evt: BaseHookEvent, dispatch: str) -> bool:
return isinstance(run, str) and coordinates(evt, run)


def own_lane(evt: BaseHookEvent, handle: str) -> bool:
worker = worker_of(handle)
return isinstance(worker, dict) and coordinates(evt, worker.get("runId"))


def still_settled(action: Proposal) -> bool:
worker = settled_worker(action.payload["terminal"])
return worker is not None and worker["dispatchId"] == action.payload["dispatch"]
Expand Down
58 changes: 53 additions & 5 deletions captain_hook/builtin_packs/general/hooks/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
lone_pane,
nested,
own_dispatch,
own_lane,
owner_lift,
pid_verdict,
process_class,
Expand Down Expand Up @@ -216,6 +217,15 @@
settling = partial(guarded, env={"ORCA_TERMINAL_HANDLE": "term_root"})
ORCA_GC = ".agents/skills/orca/scripts/orca-gc"
STOPPED = "ctx_a0f447e7e42c"
INTERRUPTED = "term_4137f7d4-c749-445f-bb84-7dbd162da898"
OWN_LANE = {**SETTLED_COMMANDS, "orca orchestration worker-list": inline_worker(INTERRUPTED, "running", "working")}
ROLLBACK_STOP = (
f"orca terminal send --terminal {INTERRUPTED} --text 'STOP THE ROLLBACK. OWNER 8:25 AM: do not roll back "
"executor, fix forward only. Evidence #28631: not executor; one team queue at its concurrency cap 55/55 with "
"executors idle. Re-read the OWNER OVERRIDE at the end of your brief.' --interrupt 2>&1 | tail -2; "
"~/.local/bin/cci post --drive release-v3 --lane root --kind hold --to merge-walker-r2,incident-run-backlog-0711 "
"--text 'OWNER 8:25 AM: NO executor rollback.' 2>&1 | tail -1"
)


@guard(
Expand Down Expand Up @@ -1263,19 +1273,27 @@ def orca_ends_session(evt: ToolRewriteEvent) -> HookResult | None:
return block_first(evt, (orca_ending_verdict(call, scan, evt) for call in scan.literal_calls))


def orca_send_verdict(call: Call) -> str | None:
def interrupts_own_lane(call: Call, scan: Scan, evt: ToolRewriteEvent, arguments: Arguments) -> bool:
handle = lone_target(call, arguments, ("terminal", "send"), "terminal")
return handle is not None and spelled(call, scan) and own_lane(evt, handle)


def orca_send_verdict(call: Call, scan: Scan, evt: ToolRewriteEvent) -> str | None:
if call.name != "orca" or orca_command(arguments := ORCA.bind(call)) != ("terminal", "send"):
return None
spelling = spell(call)
values, words = arguments.values, arguments.words
text = values.get("text", ())
if "interrupt" in values or any(
str(payload).strip().casefold() in END_OF_SESSION for payload in text if payload is not None
):
if any(str(payload).strip().casefold() in END_OF_SESSION for payload in text if payload is not None):
return (
f"BLOCKED: `{spelling}` interrupts or exits the agent in another terminal, which ends that session. Send "
"only ordinary text, and leave interrupts and exits to the owner."
)
if "interrupt" in values and not interrupts_own_lane(call, scan, evt, arguments):
return (
f"BLOCKED: `{spelling}` interrupts or exits the agent in another terminal, which ends that session. Send "
"only ordinary text; only the root coordinating that lane's Orca Run may `--interrupt` it."
)
if None in text:
return (
f"BLOCKED: `{spelling}` sends text built at run time "
Expand Down Expand Up @@ -1362,10 +1380,40 @@ def orca_send_verdict(call: Call) -> str | None:
guarded(command='orca terminal send --terminal "$ORCA_TERMINAL_HANDLE" --text "note to self" --enter'): Allow(),
guarded(command='orca terminal send --worktree "$w" --text hi --enter'): Allow(),
guarded(command='orca terminal send --terminal t --text "ship it" --enter'): Allow(),
settling(command=ROLLBACK_STOP, commands=OWN_LANE): Allow(),
settling(command=f"orca terminal send --terminal {INTERRUPTED} --interrupt", commands=OWN_LANE): Allow(),
guarded(command=ROLLBACK_STOP, commands=OWN_LANE): Block(pattern="only the root coordinating"),
settling(command=ROLLBACK_STOP, commands=OWN_LANE, env={"ORCA_TERMINAL_HANDLE": "term_lane"}): Block(
pattern="only the root coordinating"
),
settling(command=ROLLBACK_STOP, commands=OWN_LANE, agent_id="sibling-lane"): Block(
pattern="only the root coordinating"
),
settling(
command=ROLLBACK_STOP,
commands={
**SETTLED_COMMANDS,
"orca orchestration worker-list": inline_worker(INTERRUPTED, "running", "working", "run_other"),
},
): Block(pattern="only the root coordinating"),
settling(command=ROLLBACK_STOP, commands=SETTLED_COMMANDS): Block(pattern="only the root coordinating"),
settling(command=f"orca terminal send --terminal {INTERRUPTED} --text exit --interrupt", commands=OWN_LANE): (
Block(pattern="leave interrupts and exits to the owner")
),
settling(command=f"orca terminal send --terminal {INTERRUPTED} --text /quit --enter", commands=OWN_LANE): (
Block(pattern="leave interrupts and exits to the owner")
),
settling(command='orca terminal send --terminal "$T" --text hi --interrupt', commands=OWN_LANE): Block(
pattern="only the root coordinating"
),
settling(
command=f'orca terminal send --terminal {INTERRUPTED} --text "$MSG" --interrupt', commands=OWN_LANE
): Block(pattern="interrupts or exits"),
}
)
def orca_send_ends_session(evt: ToolRewriteEvent) -> HookResult | None:
return block_first(evt, map(orca_send_verdict, Scan.of(evt).literal_calls))
scan = Scan.of(evt)
return block_first(evt, (orca_send_verdict(call, scan, evt) for call in scan.literal_calls))


def ends_session_key(key: str) -> bool:
Expand Down
Loading