Plugin audit entries written outside a request used the literal "system"
as user_id. That column references users.id, so the insert was refused
(Postgres logs it as an FK violation) and the entry was silently dropped.
Write null instead and keep "system" / plugin:<id> in username.
* feat(ai): add host coding agent sessions with configured providers
* Wait for agent initialization and shutdown before resuming sessions
* Keep Pi transcripts outside its configuration migration directory
* Cover Pi session directory separation
* Update core permission catalog expectations for remote agents
* Install remote agent runtimes from verified official sources
* Wait for the identity panel credentials request in its test
* Add scoped SSH forwarding setup for remote agents
* Verify forwarding setup through a fresh SSH connection
* Bound the SSH forwarding verification timeout
* Return the forwarding verification callback result
* fix(ai): support agent send and interrupt keyboard shortcuts
* feat(ai): add agent queues attachments worktrees and session recovery
* fix(ai): handle deleted workspace paths and duplicate status events
* fix(ai): accept file references in root workspaces
* fix(ai): refresh paused queues after agent errors
* fix(ai): store agent sessions in plugin tables and lock down OpenCode
Agent sessions lived in a single ctx.kv value holding up to 2000 events,
which passed the 256 KB kv limit within minutes of a normal run. Every
save after that failed, so history, nativeId, queue and archive changes
were lost and every event wrote a kv audit row.
Sessions, events and queued prompts now have their own tables. A running
agent appends events in batches at most once a second with a lazy save;
session and queue changes save immediately. Prompts, drafts and stored
event text are capped in UTF-8 bytes to fit MySQL TEXT. kv:own is no
longer requested.
The OpenCode server now requires a per-session password, so other local
accounts on the host cannot drive the agent through its loopback port.
* test(upgrade): include agent session tables in the reviewed upgrade SQL
* fix(database): yield to the event loop between database saves
Each SQLite save serializes and encrypts the whole database into new
buffers, which V8 only releases once the event loop turns. Boot
migrations and bulk host import write plugin settings per host and per
key in an awaited loop, so hundreds of full copies piled up and the
container ran out of memory.
* fix(database): save once per bulk host write instead of once per row
On SQLite every repository write force-saves the whole database. Boot
plugin data migrations, host defaults materialization and the bulk
host routes write one row per host per setting, so they serialized and
encrypted the full database hundreds of times in a row.
DatabaseSaveTrigger.batched wraps a function so force saves made while
it runs collapse into a single save when it finishes. Nested scopes fold
into the outer one, and work that outlives its scope saves normally.
The column holds the text "true"/"false", and the resolver passed it
through as-is. The string "false" is truthy, so the password provider
treated every saved host as forced keyboard-interactive and left the
password out. Jump hops are built straight from the resolved host, so a
password hop whose server doesn't offer keyboard-interactive failed with
"All configured authentication methods failed".