4958 Commits
Author SHA1 Message Date
Mike Schwarz a0f410e161 fix(transport): read the input box for drafts the pane classifier misses (#2653)
For an ordinary Claude Code or Codex send, when the transport has the transport.draft_delivery reader and the send carries no input rule of its own (managed compaction, a prompt override), SessionTransport reads the input box once just before the paste, within the input-box read deadline. If the box reads typed and the setting is refuse, the send returns target_needs_input with activity prompt_draft before any input, as a recognized draft does, and the queue's existing retry applies. If the setting is deliver, the result's advisory says the message was pasted into the draft and submitted with it, said once per send. An unreadable box and a transport without the reader keep today's behaviour. rig config help names the refuse-mode retry limit and that the draft's own activity wins under --wait-for-idle.
2026-11-07 05:14:13 -08:00
billbenetti a34cb55ad7 fix(cli): add --full to rig workflow trace so a long trail comes back complete (#2656) (#2667)
rig workflow trace gains --full, which passes ?full=true to the daemon's existing GET /api/workflow/<id>/trace route; there is no new daemon route. Without the flag the request has no query, as before. --json prints the daemon payload unchanged. Human output adds one line naming the --full command when the trail came back with trailCompleteness recent. docs/as-built/cli-reference.md lists the option.
2026-11-07 05:04:32 -08:00
Mike Schwarz 642be39e5b feat(cli): read immutable context reconstructions locally (#2207)
Adds rig context reconstruction inspect|view <revision>, which reads exact immutable v0 base records, replays deltas and follows pinned compositions from local stores, with no daemon call, skill install, context projection or remote fetch. The catalog project and work home are resolved through catalog-selection logic extracted from work-install, so both use the same selection. Inspect shows source manifests, purpose, depth, dates and approximate size; view keeps constraints, authority, gaps and transitive dependencies, shows actual omissions apart from authored coverage, and keeps inherited observation dates. A missing record or reference stays an explicit incomplete result. A store that exists on this machine is read at its original path; a store that moved needs a read-time mapping, and immutable pointers and revision IDs are never rewritten. Canonical hashes are Python-compatible for safe integer JSON values, and unsupported numeric representations are rejected. Every byte read is charged against a 32 MiB budget before it is decoded or parsed, and later reads stop once it is spent. docs: reconstructions.md and project-workspace.md describe the commands.
2026-11-07 05:00:30 -08:00
Mike Schwarz 8edc076d81 fix(daemon): retire a Claude seat's earlier copies of openrig-core's skills at its next launch (#2660)
A shared retirement in seat skill storage removes an automatic core copy only when its ownership and source-root records identify it as a copy from the directly loaded core's real path, its tree still matches the recorded digest, core still supplies the skill, no explicit selection names it, and no skill-projections record or following entry names it. The copy is moved through the existing bounded staging with rollback, and its id is removed from every topology owner's selection so restore and saved-loadout apply can't recreate it. Edited, linked, unreadable, unrecorded and projection-recorded copies are kept and named, and seats without direct core are untouched. It runs before the harness reads its plugin roots: in launchNode before the session for a fresh, restore or launch start, and after takeOverPane succeeds for a kept-pane relaunch; in a handover after the predecessor is proven gone and before respawn; and in set-runtime only when the runtime actually changes. Saved-loadout apply is unchanged from main. Its notes reach the launch, fresh-launch, handover and switch results as warnings, and no new refusal is added.
2026-11-07 04:58:30 -08:00
Mike Schwarz b1bed9f8c2 fix(daemon): don't compare a resumed seat's predecessor model with its pin (#2664)
CurrentModelReader.readEffectiveModel(), which the divergence monitor uses, now reads the newest model record's model and time for Claude and Codex and applies observe()'s rule: a record at or before the current occupant's tenure boot reads pending ("no model record from the current occupant yet"), and a record whose time or boot can't be placed reads pending too. Pending is the monitor's existing no-verdict state, so it checks again at the next poll and never settles a verdict on the predecessor's record. A resumed conversation keeps its predecessor's records in the same file, which is how the false verdict arose. Pi's reader is unchanged. A Codex seat with no registered occupant tenure now reads pending, with the monitor's existing unchecked warning, where it was compared before.
2026-11-07 04:48:33 -08:00
Mike Schwarz 772fa5c5fb feat(plugins): an openrig plugin for a person's own coding agent (#2647)
Adds packages/daemon/assets/plugins/openrig beside openrig-core, shipped in the npm package. Its Claude Code manifest runs rig mcp serve --front-door with OPENRIG_SESSION_NAME from the plugin's seat setting, the app's address on the person's team as a hosted member; there is no root .mcp.json and no hooks. A marketplace manifest named openrig-app installs it from the package already on the machine, and a Codex manifest connects it with one command. One new skill, working-with-an-openrig-team, describes the loop around the existing MCP tools; its home is the spec copy, and mirror-skills generates the canonical and plugin copies and routes it in the skills index. rig mcp serve --front-door gives the plugin's seat front-door instructions (the person's questions and words, with no member work-loop authority), and rig_whoami answers a declared hosted seat with no session when its rig exists; the default instruction modes and tool exposure are unchanged.
2026-11-07 04:44:19 -08:00
NIKHIL c26af88dc7 fix(transport): read Claude's own-row "Restart to update" banner as a status line (#2662)
CLAUDE_STATUS_WARNINGS, the exact rows that may sit under Claude's mode footer, gains one alternative: "✔ Update installed · Restart to update", the wording Claude 2.1.319 prints on its own row in a narrow pane. The pattern stays anchored at both ends and still matches "/restart to apply" and "Restart to apply" exactly, so the footer search reaches the mode footer and the draft-aware reader, activity classification and send readiness read the prompt as before; a near miss still reads unknown. The versioned banner and the /rc row were already handled. Tests add the row to the warning-footer readiness matrix and an empty, typed and near-miss composer case.
2026-11-07 04:40:13 -08:00
Mike Schwarz 6fa0ad5c6e feat(cli): rig acp serve asks the person the team's questions and reopens sessions (#2651)
rig acp serve's agent server asks the person: during an open turn it polls the caller's open asks and puts one to the app as session/request_permission only when its tags carry this chat's exact acp-session:<id>; an untagged ask from a seat the turn's work reached is named, never asked. A choice posts the existing Mission Control answer verb, so it is recorded by the same answer contract Slack and the TUI use, with the actor taken from the caller's session header; a refused answer is shown with the daemon's reason. On cancel the turn waits up to 2 s for questions the app is settling, then finishes them before returning cancelled. Sessions are the caller's acp-serve rows tagged acp-session:<id>: session/list groups them, session/load replays each root through the same chain follower a live turn uses, and a later prompt carries the session tag and names the earlier roots. session/load of an unknown session returns JSON-RPC -32602. The answer POST sends OPENRIG_AUTH_BEARER_TOKEN only as its Authorization header. docs: acp-agents.md describes questions and sessions.
2026-11-07 04:37:10 -08:00
Mike Schwarz f5b62726ff feat(transport): transport.draft_delivery lets ordinary sends reach a seat with a draft (#2645)
Adds transport.draft_delivery (deliver or refuse; default deliver) to the CLI config store, the daemon's settings store and the TUI config model, with OPENRIG_TRANSPORT_DRAFT_DELIVERY taken first when it is exactly deliver or refuse. The daemon reads it at each send that meets a person's draft: only a readable refuse opts out, and a value or read that fails falls back to deliver. Under deliver, SessionTransport pastes the message at the current cursor into the draft and sends Enter, so the combined input is submitted. Under refuse, the send is refused as before and a work notice is retried once the prompt is clear. Menus, permission prompts and hook-reported requests still refuse, a per-seat typing guard still holds, and managed compaction still waits for an empty prompt. rig config help describes both values and that a draft OpenRig can't detect isn't mentioned in the result.
2026-11-07 03:26:18 -08:00
Samuel 30b9556355 feat(gateway): add authenticated loopback text relay (#2624)
Adds a relay connector to the gateway: an HTTP loopback transport to an operator-owned bridge process, configured per connector from an absolute secrets file (OPENRIG_RELAY_URL, OPENRIG_RELAY_OUTBOUND_TOKEN, OPENRIG_RELAY_INBOUND_TOKEN), with no environment fallback. Outbound, each decision freezes its complete relayEnvelope on its DispatchBuffer entry before the first attempt and posts it whole, once per attempt, with deliveryId set to the decision ID; a 2xx records relay-owner-notification-accepted and drains the entry, a failure records relay-owner-notification-transport-failed and keeps it for replay of the same bytes, and a stop mid-attempt records neither. Inbound, POST /api/gateway/relay/inbound requires the connector's bearer token from a direct loopback socket peer (forwarding headers are ignored), attributes the reply by sender.handle against that connector's registered people, and commits the answer or resolve with its audit record through the Mission Control write contract, deduplicating an exact replay of the same eventId. Inbound size limits are a documented protocol contract, outbound references are fixed-width digests that the inbound contract accepts whole, and a ledger whose state can't be read returns a scoped retryable 503. Readiness warns, rather than going unready, on group or other bits on a readable secrets file and on one token used for both directions within a connector. docs/reference/gateway-relay.md describes the topology, custody, configuration and wire protocol.
2026-11-07 03:14:50 -08:00
Mike Schwarz bfbe416861 fix: qualify bare external attach names with their rig (#2641)
An external (non-tmux) self-attach resolves its session name in SelfAttachService.resolveContext. The explicit name (an external_cli context's sessionName, or else --display-name) is kept exactly when it already contains @ or is already saved for the same node in sessions; any other explicit bare name becomes name@rig. With no explicit name, the derived default stays logicalId-with-dashes@rig, as before. A tmux context passes through unchanged. The attach command's --display-name help says new bare names gain @rig. A member already bound under a bare name keeps it on an ordinary re-attach, and so stays unaddressable by the queue until an explicit repair.
2026-11-07 02:49:24 -08:00
Wu Shuwen 00cb932c0f fix(cli): report masked configuration overrides (#2547)
A whole-file rig config reset now names the keys whose effective value still comes from an environment variable, and keeps the existing message when there are none. If reading the settings back after the reset fails, the reset is still reported as successful (exit 0) and the read-back problem is noted on stderr. Single-key reset is unchanged. Contributed by dajiaohuang; reported in #2528.
2026-11-07 02:11:39 -08:00
Mike Schwarz 73638fcddd feat(cli): rig acp serve, OpenRig as an agent in ACP apps (#2648)
rig acp serve runs OpenRig as an ACP agent over stdio. A prompt becomes a queue row to the receiver named by --to, attributed to the caller (--as, the seat environment, or the default person) with the session's follow-up lineage in its body; the turn follows the row and its handoffs, including handoff-and-complete's done/handed_off_to form, and ends with the team's result, naming the closer from the row's closing transition. The row ID is chosen before the create: when a create gets no answer, a readback that finds the row carries on, and one that doesn't keeps the ID as unconfirmed in the session's lineage; an answered 4xx is a plain refusal and a 5xx stays unknown. The JSON-RPC peer splits into the existing client end and a new stream end over stdin and stdout, with no protocol SDK dependency. The queue is the only store, and a 2 s poll follows open turns.
2026-11-07 02:09:44 -08:00
Mike Schwarz 0c57bdb38c fix(terminal): preserve styling in snapshots and scroll-back (#2654)
The terminal broker's attach snapshot and scroll-back window now request tmux captures with escape sequences, so colour, bold and dim survive the first paint. The painter keeps tmux's carried styling across rows instead of resetting each row, resets once before clearing the screen and once after the last painted row, and replays the styling of rows above the first painted row when a height crop or the scroll-back window drops them. The captures feed only the websocket painter; activity, input-box and prompt detection keep their own plain captures. Reported by niklasbaldi in #2598.
2026-11-07 02:07:43 -08:00
Wu Shuwen 9b1d94d6f0 docs(reference): clarify bearer and browser boundary (#2652)
daemon-http-clients.md no longer says a token never gets past the browser boundary's 403 refusals. A valid configured bearer token in the Authorization header can pass the target-name check for a well-formed but unaccepted Host; it cannot pass a malformed Host or a refused Origin. The sentence links Browser access for the full rules. Docs only. Contributed by dajiaohuang; refs #2486, where the correction was reported.
2026-11-07 01:39:01 -08:00
Seán McNamara 378d25301d fix(cli): avoid cold compile-cache writes for piped bare usage (#2643)
bin-wrapper enables Node's compile cache except for a bare invocation with stdin or stdout piped or redirected, which only prints usage. That case loaded the command tree only to print usage, and with a cold cache the deferred cache writes near exit could push it past a caller's deadline after the usage was already printed. Any script argument, including piped --help and --version, and a bare run with both streams on a terminal, keep the existing cache attempt, matching the front door's own predicate. A new bin-wrapper test covers the piped and terminal cases. Contributed by seanmcn; reported in #2642.
2026-11-07 01:07:22 -08:00
Seán McNamara b1065870f6 feat(pi): switch accounts and preserve selection through the seat lifecycle (#2630)
rig provider switch --account-home and --clear-account-home select or clear an existing Pi seat's account home through a confirmed successor with a fresh conversation and handover; an unconfirmed or failed switch leaves the old account and state intact, and the selection survives restart, resume, restore and daemon restart (migration 151 keeps switch history and creates or copies no credentials). The chosen directory must already hold a readable auth.json, which the runner links into the seat's managed agent directory; env-only accounts and wrappers that create credentials later are outside this path, and an existing seat-local login or unmanaged auth link is preserved through the confirmed switch. Selected accounts load a Node preload that points only their auth-lock operations at the chosen account's auth.json.lock pathname, even when that auth.json is a symlink, and removes its own NODE_OPTIONS entry before Pi starts tools; outside the managed target it does nothing. A Node-hosted launch wrapper that execs Pi can consume the preload, so that Pi gets no shared-auth lock and keeps its native per-seat lock; compiled Pi and wrappers that discard NODE_OPTIONS are likewise not covered. Contributed by seanmcn; requested in #2629.
2026-11-06 23:52:50 -08:00
Mike Schwarz 892eb55b5e fix(transport): leave a pane's mode through the seat's address (#2644)
leavePaneMode passes the seat's session name to cancelPaneMode, as the text and Enter writes do. The guard resolves the seat and writes to its bound pane; the mode reads stay on the bound pane. Before this, the cancel addressed the raw pane ID, and when a stale binding in another rig named the same reused ID, resolveGuardTarget matched two bindings and refused the cancel (guard_target_unknown), so the send failed pane_in_mode with no text sent. A new session-transport cell covers the stale same-pane binding, asserting delivery and submission, the cancel's target, the reads on the pane, and resolveGuardTarget on both forms. Not changed: reconciling stale bindings, and a person scrolling again between leaving the mode and the Enter.
2026-11-06 23:48:10 -08:00
Seán McNamara cfbe5372db fix(claude): pass short permission-hook policy names (#2640)
Claude kernel and team launches pass the named policy kernel or team to the PreToolUse hook instead of the base64 policy, shrinking the argument from 988 to 6 characters for kernel seats and from 1,640 to 4 for team seats. The three policy lists move unchanged into a shared CommonJS asset with TypeScript declarations, read by both the launcher and the hook, so every allow and ask decision is unchanged; legacy encoded arguments still decode, and the missing-hook fallback is kept. rig-spec.md documents that named-policy sessions read the installed lists on each later Bash hook call while native inline allowances stay as launched, and that rolling back in place to a pre-name hook gives no hook decision, so the lifecycle asks can disappear under the native Bash(rig:*) allowance until affected Claude seats are relaunched. Contributed by seanmcn; requested in #2639.
2026-11-06 23:37:50 -08:00
Mike Schwarz 09781df0ae fix(codex): keep OpenRig's command folder on managed seats' tool PATH (#2613)
Managed Codex seats now pass Codex -c allow_login_shell=false, so tool commands run in a non-login shell and inherit the environment their pane's shell set up, OpenRig's command folder included; plain rig works however OpenRig was installed, with a Codex that supports allow_login_shell. Inherited setup isn't re-run for each command, so setup that lives only in login or rc files, and shell functions and aliases, may be missing from tool commands, and a model request for login: true gets Codex's own refusal. rig config set launch.codex_login_shell true omits OpenRig's override, deferring to Codex's own setting, at each seat's next launch; a restart in place keeps the running choice. Fresh launch, fork, resume, legacy resume, restart in place and the launch-wrapper path each pass the setting exactly once, or not at all when it's on. getting-started.md and the config help describe the trade-off.
2026-11-06 22:50:43 -08:00
Wu Shuwen 36b0ae6464 fix(cli): refuse daemon start when a probe is blocked (#2502)
When OS policy prevents a reliable process or port check, daemon startup now reports the uncertainty instead of treating a blocked probe as proof that an address is free. Missing state and preflight cases warn. The auto-start stop is narrowed to the shared PID path for a reused or uninspectable PID. Stop refuses unknown process liveness before probing or signalling and preserves the state file. A denied signal probe yields unknown even when the process state is readable, and a confirmed zombie remains dead. A blocked explicit non-loopback OPENRIG_URL stays on the blocked-status path instead of starting a local daemon (rig up and rig start); loopback targets, including localhost, [::1] and 127.x addresses, keep the existing startup behaviour. Refs #275 and #1650. Contributed by dajiaohuang.
2026-11-06 22:18:20 -08:00
Mike Schwarz 126c52ab50 fix(mcp): route first tasks to a trusted configured receiver (#2638)
Adds --task-destination <member@rig> to rig mcp serve --http --grant-service. For an external caller, rig_queue_create's destination becomes optional; an omitted destination takes the operator-configured receiver. The receiver comes only from that flag, never from caller input, topology, names or history. It is checked against the request's grant with the same team predicate as an explicit destination, before any lookup, and re-resolved on each omitted call to exactly one existing local node; a missing, ambiguous, removed or out-of-grant receiver returns task_destination_unavailable with no write. Explicit destinations, and local and member calls, keep their existing behaviour and still require a destination where they did. The external instructions now teach the create, show, revise and artifact loop. The new option is refused only when misused: it needs --http --grant-service, and takes one local member@rig seat.
2026-11-06 22:06:19 -08:00
Seán McNamara 46ce2bd860 feat: refresh opted-in seat skills on restore for Claude and Codex (#2626)
Adds an opt-in on_restore policy to resources.skills[] entries in agent specs. keep_saved, the default and what an absent policy means, keeps #2337's behaviour: a restored seat keeps its saved skill copy. follow_source refreshes the seat's copy of that skill from its source when the seat is restored, for Claude and Codex seats, keeping the native conversation. Copies edited, linked or not owned by OpenRig in the seat keep their existing protections. An unrecognized on_restore value adds an advisory that names the resource, the value and the two supported spellings, and is normalized to keep_saved rather than invalidating the spec. On a no-op restore a keep_saved skill reads only the projection record, and the following bookkeeping is best effort, so it can't fail a launch. Documented in docs/reference/agent-spec.md. Requested by seanmcn in #2625.
2026-11-06 21:42:35 -08:00
Mike Schwarz 6a46172df1 fix(terminal): keep Herdr views from earlier releases after upgrading (#2634)
Kept Herdr viewer journal entries and follow-view saved tiles store the whole tile attach argv. #2614 changed the writable tile script, and #2464 changed both scripts before it, so views kept from 0.7.5-0.7.9 failed the strict comparison after upgrading: a kept viewer read unconfirmed and a kept follow view stopped adding and closing tiles. earlierTileAttachment accepts a kept argv only when it exactly equals what 0.7.9's or 0.7.5-0.7.8's generator makes for the current session, tmux, server and recorded read-only intent; the entry then moves to the current command. A different target, a changed read-only intent, or any other script (including one sharing the old prefix) still mismatches as before. The earlier generators live in src/domain/terminal/earlier-tile-attach.ts, shared with the tests. The approach follows natefaerber's #2467. No new refusal, error code or setting.
2026-11-06 20:37:10 -08:00
Seán McNamara 5049550203 test: stabilize daemon fixtures under startup contention (#2628)
Makes the daemon test fixtures hold on macOS paths and under startup contention, without weakening any assertion: readiness waits instead of fixed sleeps, temp-root resolution through the real path, a script-file shell for long commands, serialized mutations where tests raced, a long-path command, and the shared hermetic test setup. The refocus-overlap cell still asserts the lost pendingOn race, now through a deterministic handshake (the time budget only scales timeouts). The existing Linux CI job that covers /proc seat environments also runs pi-shell-launch-native.test.ts, and the Linux long-command session tokens stay within their limit. Test and setup only; no product code changes. Reported by seanmcn in #2627.
2026-11-06 20:15:53 -08:00
Seán McNamara d1688735d5 fix(daemon): canonicalize new inherited tmux homes before recording (#2633)
On the first inherited tmux server selection with persistence on, tmux-server-selection creates the home directory (recursively, best effort) before writing the record, so the recorded home is the canonical path a later boot or a later switch to isolation reads it back by, even when the home sits under a symlinked parent. If the home can't be created, inheritance carries on without it as before; inheritance has no writable-home prerequisite. Homes already recorded are unchanged. Reported by seanmcn in #2632. No new refusal, error code or setting.
2026-11-06 20:04:40 -08:00
w4rc0n c271299f55 feat(ui): answer an ask's questions on its For You card (#2612)
For You cards for local asks with humanQuestions show the questions with the card's existing controls: one option per question, or an answer in the person's own words, recorded through POST /api/mission-control/action with the ask's revision, then sent once every question has an answer. The card names who OpenRig records as answering (human@host (unattributed), as the TUI does). Live attention keeps priority in the 50-item window, with answered receipts using spare room, and saved session snapshots are bounded with it. A receipt says "answered and returned" only on the answer outcome or reply evidence; a plain close shows as done with the recorded choices, and no closing-note text is read as an answer. Finished asks without questions move to Shipped again. The shared action hook keeps remote_action_failed detail and status. Cards from a paired remote host keep the TUI hint; CHAT, asks without questions and runner permission asks are unchanged.
2026-11-06 16:38:42 -08:00
Mike Schwarz 9a9205da2a docs(roadmap): name 0.7.9 as the current release (#2623)
ROADMAP.md's released section moves from 0.7.8 to 0.7.9: the heading, the release link, and the published 0.7.9 notes' first two sentences with "OpenRig 0.7.9" written as "it", as the 0.7.8 update did. The pointer to the release notes for what shipped, known issues, upgrade instructions and contributor credits is unchanged. Text only.
2026-11-06 16:16:38 -08:00
Mike Schwarz ce6b1670b0 docs(queue): name cross-host command in custody advisory (#2621)
The two unverified-custody messages a manual close gets (a successor named on another host, or no local row with the source's lineage) now name the route that makes the handoff verifiable: rig queue handoff or handoff-and-complete with --to <seat@rig>, and --host <registered-host> for a successor on another host. That route creates the successor there first and closes with the derived key the stuck sweep already trusts. The sweep, its custody rule and the CLI command definition are unchanged: a registered-host name alone is still not proof that the work was handed on. No new refusal, error code or setting.
2026-11-06 15:24:11 -08:00
Samuel abd515021f feat: add local Pi and Codex launch wrappers (#2609)
Adds an optional member-level launch_wrapper for local built-in Pi and Codex seats: an argv prefix applied at native launch and carried through restore, handover, restart and runtime switch (migration 150 stores it). For Pi the wrapper wraps the child process the runner spawns, inside the unwrapped runner; flat specs carry the field. Once selected, a wrapper is never silently skipped. rig-spec.md says retention applies to existing seats, not newly imported nodes. Native cells cover the wrapped paths with an rc PATH export, a readiness marker and wrapper execution markers. Sandbox, memory and tooling remain the operator's choice. Reported by smota in D#2328.
2026-11-06 15:06:15 -08:00
Mike Schwarz 4697977a17 fix(daemon): stop replaying failed wakes to seats with no session at start (#2620)
drainPendingWakeIntents skips a failed or indeterminate wake intent while its seat has no receiver: the destination is unroutable, or the seat's current session, resolved through physicalSessionName, is exited or detached. The check runs at every claim, including grouped and correlated claims, so a seat that comes back during the pass gets its later wakes, and only notifications nothing in the pass attempted are counted as waiting. A never-tried intent always gets its first attempt; running, unknown and non-pane-bound seats take the old path, and an error in the check fails open. The skipped rows stay pending and owed, with no write, and the start-up log line adds waiting=N and names the seats. The wake ladder and stuck sweep are unchanged. No new refusal, error code or setting.
2026-11-06 13:54:50 -08:00
Mike Schwarz 1ff76d5954 fix(cli): name the safe retry when rig stream emit times out (#2619)
A timed-out or unreadable rig stream emit can still land late. Its error now names the retry that can't add a second item: with --id, repeat the same emit with that --id (the daemon keeps one item per stream item ID) or check first with rig stream show <id>; without --id, check with rig stream list --source <recorded seat> --since <five minutes before the send>, using the seat the daemon records, and pass a stable --id on later emits. queue create is unchanged; its existing --id already makes a retry safe, and the openrig-user skill now says so for both in one paragraph (skill digest updated). No new refusal, error code or setting.
2026-11-06 13:22:01 -08:00
Mike Schwarz d539277a5f fix(terminal): let writable tiles follow their visible size (#2614)
tileAttachArgv chose ignore-size for a writable tile whenever another normal client was attached to the session, and under window-size latest such a tile could never size the pane, so the pane kept an older client's dimensions. Writable initial, fallback and replacement attaches, and the writable recovery command, now attach without ignore-size; read-only attaches keep -r and are byte-identical to before. This covers writable rig, pod and seat views, saved writable members through composeView, and interactive task viewers, locally, over inherited SSH and through the remote helper. The tmux control client's no-output,ignore-size and the CLI's attach-flag recognizer are unchanged. A real tmux test with a competing older client fails on the previous code (14x18 where 101x32 is expected) and passes now (101x32, then 121x43 on resize), with a larger read-only observer keeping its size. Limits: different simultaneous sizes on one shared pane follow tmux's latest-client rule; clients attached before this change keep their flags until re-attached.
2026-11-06 12:38:33 -08:00
Mike Schwarz bf5e8d8c21 fix(daemon): warn about unknown agent profile uses keys (#2618)
validateAgentSpec adds an advisory for any key under a profile's uses that isn't one of the five known categories, naming them, on the existing advisory channel, so it reaches the person at rig up, restore, retry and rig agent validate. It never makes a spec invalid, and hooks stays a validation error with no second advisory. The advisories for shapes that normalization drops now end "; ignored", and a non-string or blank list entry, which is kept and fails resolution when its profile is selected, now ends "; a member using this profile fails to launch". The agent spec reference says so in one sentence. No new validation error, refusal or setting.
2026-11-06 12:21:35 -08:00
Mike Schwarz b75ab56552 docs(changelog): carry the published 0.7.9 section onto main (#2616)
CHANGELOG.md gains the [0.7.9] section from the v0.7.9 tag (ddb9d25b), placed above [0.7.8], with the status line "Published 2026-11-06 as @openrig/[email protected], built from the commit tagged v0.7.9 (ddb9d25b)." Where the tag's section and the published release notes differ, it uses the published wording: the summary (including its credit to the people who reported or contributed), the OpenShell worker section, and the Known limits, including "What we ran end to end under the runner contract". The OpenShell heading is "The OpenShell worker in 0.7.9" so its anchor is unique in the file. Nothing outside [0.7.9] changes; whats-new.md stays as it is, since main's package test pins its heading to main's package version.
2026-11-06 11:43:34 -08:00
Wu Shuwen 033587c042 fix: validate agent profile resource selections (#2585)
A profile's uses.<category> written as a non-blank scalar validated but was silently dropped, so an agent could start without the selected skill. normalizeUses now turns a non-blank string into a one-item list for all five categories, and validation adds an advisory suggesting list syntax. A uses value that isn't an object, a category that isn't an array, and a blank or non-string entry are advisories, not new validation errors, so a malformed unused profile can't stop a spec; arrays reach resolution unchanged. When a profile is selected, its coerced scalar resolves exactly like a one-item list: a resource the pool doesn't hold fails that member's resolution with "not found in resource pool", and a non-string skill ID now returns "has a non-string skill ID" before discovery instead of throwing. The advisories travel with the spec's existing advisory channel. Limits: no trimming of scalar values (as with list entries); unknown uses keys are not yet advisories.
2026-11-06 11:31:36 -08:00
Mike Schwarz ab7f32e096 chore(release): bind the packaged-plugin baseline to published 0.7.9 and advance openrig-core to 0.1.26 (#2615)
scripts/fixtures/packaged-plugins-last-release.json now names release 0.7.9, its published archive (sha256 668b9305...) and the openrig-core 0.1.25 payload it shipped (f1838261...), byte-equal to a --snapshot of the published npm tarball and to the v0.7.9 tag's plugin tree. openrig-core moves from 0.1.25 to 0.1.26 in both harness manifests: its files changed on main after 0.7.9 (permission-stop descriptor, refocus and the trace script), and check-packaged-plugin-versions.mjs requires a greater version when packaged bytes change, so existing installs don't keep old content. The check fails at 0.1.25 and passes at 0.1.26; nothing else pins 0.1.25. No other file.
2026-11-06 11:17:20 -08:00
Mike Schwarz 829ddfccc7 docs(rigs): try a user-writable npm prefix before a project-local install (#2611)
The rigs skill's Install step offered only npm install -g @openrig/cli. When npm's global prefix isn't writable (often a system Node with prefix /usr), an installing agent improvised a project-local install, and Codex seats then reported rig: command not found. The step now has the agent first point npm's prefix at a folder the person owns whose bin is on their login PATH (for example npm config set prefix ~/.local when ~/.local/bin is on it), saying first that this changes their npm settings and, if needed, their shell profile; use the Node of a version manager such as nvm instead, since nvm refuses an npm prefix setting; and install inside the project only if neither is possible. It links getting-started's "Before you start" for setting and checking the login PATH. Both copies, skills/rigs/SKILL.md and packages/daemon/assets/skills/rigs/SKILL.md, stay identical. Text only; no code, command or gate. Limits: it makes the project-local fallback rarer but doesn't fix why managed Codex seats lose OpenRig's command folder after one (separate work); not run with a live agent install.
2026-11-06 09:38:15 -08:00
Mike Schwarz c97cd5c964 fix(events): page the notification drain (#2610)
The post-commit notification drain in EventBus read the whole remaining event tail into one array. It now reads ordered SQL pages of at most 500 rows (seq after the cursor, ordered by seq) and keeps paging until a read comes back empty. The per-row cursor, subscriber fanout, nested standalone and envelope emits, and poison recording are unchanged; replayAll(), SSE replay and client buffering, and event retention are untouched. One test commits a tail larger than two pages through real SQLite, with subscriber emits at page boundaries and a malformed row, and checks the same order for two subscribers, one poison notification, no redelivery, an uncapped replayAll(), and that no SQL read returns more than 500 rows. No new refusal, error code or setting. Limits: this bounds rows per query, not bytes; the drain still runs synchronously to quiescence; no latency change on a live install is claimed.
2026-11-06 08:20:25 -08:00
Mike Schwarz b571206ffd fix(daemon,cli): show agent spec advisories at launch and in rig agent validate (#2606)
validateAgentSpec already produced non-blocking advisories, but loadSpec dropped them, so only rig agent validate --json and the HTTP route ever showed them. loadSpec now carries them on ResolvedAgentSpec.advisories, rigPreflight turns each base or imported spec's advisory into a warning naming the member, the spec and its path, beside the existing import-collision warning, and those warnings ride the existing preflightWarnings that rig up prints. A recipe-backed fresh restore (launchExistingAgentMember) and a successful first-start retry now return the same advisories through their existing returned warnings, once per member, spec and advisory, without doubling where preflight warnings are already merged. Human rig agent validate prints one agent spec advisory line per item, valid or not; --json is unchanged. No new refusal, error code or setting. Limits: a native resume that uses saved context and doesn't reload a spec shows none; unknown uses keys are not yet advisories (a follow-up after #2585).
2026-11-06 06:55:58 -08:00
Samuel f4d1d962a9 docs(telemetry): define API contract and link external exporter (#2589)
docs/reference/telemetry.md gains a boundary: OpenRig's telemetry guarantee ends at the finite HTTP/CLI read envelope, and any projection of it is owned by an external consumer. It describes the separately maintained openrig-telemetry exporter's mapping and limits as that exporter's choices (sampling, observed state intervals, no native execution or trace attribution, downstream freshness), says arbitratedNeedsInput is true, false or null with null meaning no evidence either way, and links the exporter's repository, which documents its installation and supported versions; OpenRig doesn't test or guarantee it. It states OpenRig's own fact that the finite reads are versioned by schemaVersion 1 while the cached rig and node inventory endpoints are operational projections. finite-telemetry.test.ts adds one contract cell through the production telemetry routes: the envelope and limits, decimal IDs above JavaScript's safe-integer range across the archive and active tables, the note-free row shape, an opaque cursor that round-trips, page counters, the cap reason and a retained sequence gap. No exporter code, configuration or tests are included, and no daemon or CLI behaviour changes. Limits: the contract cell pins the envelope strictly, so an additive field under schemaVersion 1 needs the cell updated; the exporter itself wasn't reviewed or run here.
2026-11-06 05:59:17 -08:00
Wu Shuwen f68d28e417 docs: clarify Claude core plugin skill loading (#2584)
The agent-spec reference described plugin skills as always projected into the seat's skill folder. Since the duplicate-loading fix related to #2426, a Claude seat that selects the vendored openrig-core gets that plugin loaded directly, skills and hooks included, and resolveNodeConfig skips projecting its skills; other selected plugins, Codex seats and non-vendored copies still project. The skills section and the plugin table now say so, and that a seat that didn't select openrig-core doesn't get its hooks this way. Docs only. Limits: one sentence says the daemon loads the plugin through CLAUDE_CODE_PLUGIN_DIRS; the daemon sets that variable and Claude loads the plugin, as the table row says. The direct load depends on a Claude Code version that reads that variable, which refocus-channel.md states for the hooks.
2026-11-06 04:40:15 -08:00
Mike Schwarz ff49a2c362 docs: say a config fragment cannot install a Codex profile, and how to keep one (#2604)
agent-spec.md's fragment section now says a fragment only adds tables to config.toml, so it can't install a named Codex profile in either form: Codex reads a profile from its own <CODEX_HOME>/<name>.config.toml with top-level keys and won't load one while a legacy [profiles.<name>] table remains; it points to creating the file and selecting it with codex_config_profile, and notes that the profile preflight checks it before a launch or restore and names the file for a legacy table. getting-started.md says builtin:yolo drops the whole profile, not only its sandbox and approval settings, and how to keep a profile: full access inside the profile with permission_policy absent, or a workspace-write or read-only codex_permissions envelope, which keeps -p and adds its flags; full access isn't an envelope. rig-spec.md's codex_config_profile row says the same in one sentence. Docs only; no behaviour or contract change.
2026-11-06 02:57:33 -08:00
Mike Schwarz 06a0663b42 fix(openshell): use confirmed engine cleanup inventories (#2603)
Podman registers its native /libpod routes only under a version prefix, so the observer's unversioned /libpod container list and inspect calls got the router's plain-text Not Found, which the reader then failed to parse as JSON; an owned delete stopped unconfirmed at the cleanup inventory. The observer now uses the compatible /containers/json?all=true and /containers/<id>/json routes, which Podman serves with or without a prefix and whose Id, Names, Labels and Mounts fields the existing selection already reads. Volume absence no longer comes from a per-volume 404: one /volumes listing must be a 200 with an array of named Volumes and no Warnings, and presence is name membership in it; anything else is environment_cleanup_unconfirmed with the HTTP status. The reader parses JSON only for a 200, and malformed 200 inventory is a typed unconfirmed error. Socket identity, workload and supervisor selection, ownership and lifecycle are unchanged, and nothing is sent to the engine but GETs. Limits: container rows are still checked only for status and array shape, so a 200 of [{}] with an empty volume list would read as absent (unchanged here; Podman emits full records or an error); a thrown cleanup error still omits its stage and dispatch status; this is a snapshot, not an atomic inventory; maintained native owned-delete isn't requalified by this change.
2026-11-06 02:52:35 -08:00
Mike Schwarz 3a31311795 fix(cli): arm rig exec --wait's Ctrl-C before announcing the run (#2601)
rig exec run --wait installed its SIGINT listener only when polling began, after it had printed that the run had started, so a Ctrl-C landing between the two met the default action and killed the CLI with no exit code (the run itself, in its own session, was never affected). armWait now installs the listener before each path announces the run: a run the row already has, a caller that lost the claim to another, and a fresh start right after the connector is spawned, where the start-wait loop also stops when the waiter leaves. report removes the listener in a finally, and waitForEnd takes the waiter instead of arming its own. A tests-only afterAnnounce seam stalls the CLI just after the announcement; the new cell sends a Ctrl-C to the whole process group into that stall and expects exit 3, the stopped-waiting line and the run completing. Limits: a Ctrl-C before the connector is spawned still meets the default; an exception before report would skip the listener cleanup, though the CLI exits on it; the pre-announcement path is traced at source, not separately exercised; a run that has already ended still exits 0 or 2 by its outcome.
2026-11-06 02:32:50 -08:00
Mike Schwarz 6e55271447 fix(openshell): preserve supplied guest CA references (#2602)
The OpenShell guest's launch builds the Claude child's environment from scratch and copied only ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY, so a supervisor-supplied CA never reached the worker and its TLS failed with SELF_SIGNED_CERT_IN_CHAIN. The same explicit list now also carries NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE, each copied only if the guest's exec environment has it; when they're absent the launch is as before. No broad environment copy, no TLS bypass, no credential, and provider references, argv, latch and lifecycle are unchanged. Limits: NODE_EXTRA_CA_CERTS is the demonstrated repair for Claude; the other three are the CA selectors this exec supplies, kept for the agent's own OpenSSL-based tools, and no record isolates each as needed. This preserves the supplied references; it isn't a claim that the maintained OpenShell worker is qualified end to end (export, finite completion and exact restore are unproven).
2026-11-06 02:29:19 -08:00
Devulapalli Naga Sri Vaishnavi b12934a53f fix(cli): route self-qualified sends to the local host (#2600)
resolveCrossHostTarget returns localHost: true only from its self branch, when the qualifier is the daemon's own host ID, and only after the existing check that refuses an explicit --host naming a different host. rig send then leaves opts.host unset for that target, so a persisted remote selection no longer catches a send that names this host; the documented order (explicit --host, then target sugar, then the persisted selection) holds again. The early --context refusal now covers only fan-out, and the remote --context refusal runs after host resolution, so explicit, persisted and @remote sends with --context are still refused before any remote dispatch, now after the local health read. Limits: when the self-host ID can't be read, the qualifier isn't recognized as local and the selected host may still be used, as before; rig capture and rig transcript don't pass the self ID, so @<self> isn't stripped there (unchanged); the tests use a local HTTP fixture, not a real second host.
2026-11-06 01:27:20 -08:00
Mike Schwarz b79c76f0b6 feat(cli): rig exec runs a row's task on Claude Code or Codex in a detached connector (#2597)
rig exec runs a queue row's task on Claude Code or Codex through their maintained ACP adapters, detached: a hidden rig exec connector process owns the run and outlives the calling CLI, the run file under $OPENRIG_HOME/acp/runs records its process identity and typed state (requested, started, finished, interrupted), and the queue row keeps custody and acceptance and is never closed by the run. rig exec harnesses and run --plan show the adapter and native versions and how each harness answers. A row gets one run: claims are published once per generation with a create-exclusive hard link, a retry after an uncertain submit returns the existing run, and --again starts another only after it ended. Each run's start and end are separate slots, each published once, so a reconciler and the connector can't both settle the same transition. --wait only polls and survives Ctrl-C; its exit follows the outcome (0 completed, 2 otherwise, 3 if the waiter leaves). A dead or reused process, or a connector that never recorded itself, reads interrupted, outcome unknown. Cancel is acknowledged, not settled; usage and cost carry their source, and unavailable cost reads unknown. rig acp run stays synchronous and adds only a usage line. Limit: the crash and cancel guarantees are rig exec's; rig acp cancel given an exec run's id while it's requested can signal the requester rather than the connector, so use rig exec cancel.
2026-11-06 00:40:34 -08:00
Aummadour 430b676727 fix(queue): treat informational human updates as readable correspondence (#2586)
QueueRepository.isRegisteredHumanUpdate(row) is true only for a pending row with human_intent update whose destination resolves to a currently registered person; it reads false for an agent, an unregistered destination or an unavailable registry. The waiting view treats such a row as informational correspondence with no claim backstop, and the stuck sweep skips it as an unclaimed obligation, except when its quiet-hours hold has been orphaned (no live release job), which is still reported as before. The new resolution reason closes only the sweep's own unclaimed-obligation finding, never while the row is quiet-held or has a delivery outcome. A shared recovery for such an update can be relabelled to an undelivered human notice instead of being closed, so a later delivery failure stays visible. Posted and on-Steering rows keep today's paths. This covers the informational-update part of #514, not the rest of it.
2026-11-05 23:40:16 -08:00
Mike Schwarz 86c677837b fix(daemon): leave every tmux mode before a delivery (#2594)
With mouse mode on, scrolling a seat's pane puts it in copy mode, and tmux stacks modes: copy mode can sit under run-shell output (view mode), a chooser, customize or clock mode. A single send-keys -X cancel leaves only the top copy or view mode, so a pane with two modes stayed in a mode, and its sends and wakes were refused with pane_in_mode until someone left the mode by hand. cancelPaneMode now runs tmux copy-mode -q, which leaves every mode the pane is in and types nothing on a pane in no mode; when that fails it falls back to send-keys -X cancel, checking the input guard again first, as the Enter fallback does, so input that arrived meanwhile stops it. leavePaneMode reads the result and adds the reason to its refusal. Behaviour change: a send or wake now also leaves a chooser, customize or clock mode a person has open in a seat's pane, where it was refused before; #912 already leaves scrollback.
2026-11-05 23:28:05 -08:00