A `compact` command that errors, or sends nothing, used to leave the
session as full as before: `compacted` stayed false, so every following
turn ran the checkpoint and another `/compact` again, each one waiting
out the 600s turn idle window when the command was silent.
- The `/compact` turn gets its own idle bound, COMPACT_IDLE (3 min, or
the turn's idle window if shorter), through the turn's existing
watchdog, so a silent command is cancelled (or killed) like any
stalled turn.
- When the command fails or hits that bound, compaction falls back to
the no-command path: the session is archived and the next turn starts
a new one. The checkpoint turn runs there only if it has not already
run in this compaction.
- Compaction returns early when attaching produced a new session (a
failed `session/load` or a never-answered first prompt): there is
nothing in it to compact.
Refs #4391
An ACP agent's session is now compacted like a claude one: proactively
once a turn crosses the percent-of-window watermark, and on the
operator's /compact or the agent's compact tool. Before, the ACP
backend's compact returned Unsupported and no watermark applied to it.
- AcpRuntime takes the same CompactionPolicy as ClaudeRuntime;
make_session builds one PercentPolicy (with CHECKPOINT_PROMPT) and hands
it to whichever backend runs.
- The runtime keeps the commands each session advertises in
available_commands_update. If `compact` is among them, compaction sends
the prompt `/compact` on the same session, which is how the ACP spec
runs an advertised command. A proactive compaction runs the checkpoint
turn first, as InfiniteSession does.
- With no compact command, the checkpoint turn runs, the session is
archived, and the next turn starts a new one with the system prompt.
- Error::Unsupported had no producer left, so it and drive_turn's
"/compact skipped" arm are gone.
Refs #4391
`Runtime` gets a fourth operation, `canceller()`: a handle that stops the
turn in flight from outside `run`. The ACP backend returns one; claude
returns `None`, because the harness stops a claude turn by signalling the
`claude` process, and that path is unchanged.
Both stops send the agent `session/cancel`:
- `Canceller::cancel()`, when asked from outside. The turn then ends
normally, reported with stop reason `cancelled` whatever reason the agent
gives. opencode 1.15.10, for one, answers a cancelled prompt with
`end_turn` (`acp/agent.ts` `prompt()` always returns `end_turn`).
- The idle watchdog, once no `session/update` has arrived for
`Config::idle_timeout`, the same field claude's watchdog reads. The turn
fails with `AcpError::IdleTimeout`.
An agent that has not answered the prompt 10s after `session/cancel` is
killed (`IdleKilled` / `CancelIgnored`), and the next turn respawns it.
The watchdog is also what ends a turn stuck on a provider HTTP 429.
opencode 1.15.10 retries a retryable provider error with no attempt limit
(`session/retry.ts` `policy`, `session/processor.ts` `Effect.retry`) and
forwards neither `session.status` nor `session.error` over ACP (its
`handleEvent` only handles `permission.asked` and `message.part.*`). So the
ACP client sees nothing at all until the provider recovers. The
`IdleTimeout` message says a silently retried provider error looks like
this.
Refs #4391
A new session is only recorded once its first prompt is answered, so a
failed first prompt made the next turn run `session/new` again and left the
first session behind in the agent: in the same process, and after a restart.
The new session's id is now kept in `<session file>.pending` until that
prompt is answered. The next turn attaches it (in-process, or through
`session/load` after a restart) and still prepends the system prompt, since
the session has not had an answered prompt yet. Recording the session
removes the pending file, and `archive` drops it.
Raised by argus in the #4812 review.
Refs #4391
A permission request counted as an MCP tool call whenever its title
looked like `<server>_<tool>`, whatever its kind, and acp_permits
allowed MCP calls before looking at the kind. So an `execute` request
titled e.g. `hyperhive_x`, or a `fetch` without web_tools, was allowed.
MCP tool calls come with kind `other` (opencode's toToolKind maps every
tool it doesn't name, MCP tools included, to "other"). The runtime now
sets PermissionAsk::mcp_server only for kind `other`, and acp_permits
allows an MCP server's tool only under the `other` arm.
Refs #4391
The session id was written to the session file right after session/new,
but the system prompt rides on the first prompt only. If that prompt
failed, the next turn (or the next harness start) resumed the recorded
session as not-new and the system prompt never reached it.
The id is now written after the first session/prompt gets its reply.
A failed first prompt leaves nothing recorded, so the next turn starts
a new session and sends the system prompt again. Chosen over a separate
"system prompt delivered" marker: one file, and "recorded" already means
"usable".
Tests drive AcpRuntime against a scripted sh agent that fails the first
prompt: in-process and across a restart, the retry is a new session
carrying the system prompt.
Also: PermissionPolicy now sees a PermissionAsk (kind plus the MCP
server the tool belongs to, matched by name against the servers handed
to the session), so a caller can tell MCP tool calls from other `other`
requests.
Refs #4391
A `Runtime` trait (run / compact / archive) with two backends:
- claude: a pass-through to hive_claude's InfiniteSession and
SessionStore, so a claude turn is the same spawn, session handling
and errors as before.
- acp: a generic Agent Client Protocol client. It spawns the command,
args and env from RuntimeSpec (HIVE_RUNTIME / HIVE_ACP_COMMAND /
HIVE_ACP_ARGS / HIVE_ACP_ENV), refuses an agent whose
mcpCapabilities.http is not true, passes the claude --mcp-config
servers as ACP mcpServers, keeps one session id in a file
(session/load after a restart, session/new otherwise), and maps
session/update into claude stream-json events plus usage_update into
Telemetry. Permission requests are answered by a caller-supplied
policy on the ACP tool kind. compact returns Unsupported for now.
The crate depends on no hyperhive binary crate, so the subagent
daemon can move onto it without pulling in hive-agent.
Refs #4391