512 Commits

Author SHA1 Message Date
Peter Steinberger 053b89d80f improve(ui): open short session links without extra lookup (#128778)
* perf(ui): remove short session route waterfall

* docs: clarify short link gateway requirement
2026-08-24 09:33:20 -07:00
Peter Steinberger b145e25fea fix(ui): resume session starts after Gateway reconnects (#128661)
* fix(ui): resume session creation after reconnects

* improve(ui): remove redundant appearance reset buttons

* fix(ui): align session protocol and appearance validation

* fix(ui): retain promoted placement session ownership

* fix(gateway): isolate session creation capacity by owner

* test(ui): stabilize hovercard bridge pointer movement
2026-08-24 06:36:51 -07:00
Peter Steinberger 6178409d9a fix(dashboard): preserve widget content ownership (#128489) 2026-08-23 21:53:39 -07:00
Peter Steinberger 0978d55b05 feat(nodes): automatic device placement for sessions.dispatch (#128421)
* feat(nodes): automatic device placement for sessions.dispatch

sessions.dispatch gains autoDevice: true — the gateway selects the eligible
session-host node with the most available worker slots (deterministic
tie-break), retries up to three candidates when a node churns at the
pre-provisioning eligibility fence, and reports the chosen device in the
placement runner projection. Control UI offers Any available node with
actionable disabled reasons. No-eligible-host failures state why.

* fix(ui): break draft-place-state/draft-session-placement import cycle

resolveDraftSessionPlacement only needs four scalar fields; a structural
param type replaces the Pick<DraftPlaceState,...> import that created the
madge cycle.

* fix(ui): keep the devices section hidden when no devices are paired

The Any available node row lives inside the Your devices section; rendering
it with zero paired devices resurrected the section on gateway-only setups.
Gate it on device presence — Connect a machine remains the discoverability
path — and cover both the empty and non-hostable cases.

* fix(gateway): project dispatch runner state through the canonical reader

The dispatch reply no longer synthesizes an available device runner; it uses
the fenced workerPlacementRunnerAvailabilityReader (and disk-space reader)
exactly like session reads, so a node lost after durable provisioning
projects offline consistently. Documents placement.runner.deviceId in the
protocol reference.
2026-08-23 21:08:33 -07:00
Peter Steinberger 9284e23cdd feat(control-ui): show client IP and time zone on the activity identity card (#128438)
* feat(control-ui): show client IP and time zone on the activity identity card

The Activity identity card showed only host and platform, so an operator
looking at a teammate could not tell where that person was connecting from.
Presence already carried a best-effort `ip`, and it was simply not rendered.

Add the client's self-reported IANA time zone to the connect handshake and
presence entry, and render both `ip` and `timeZone` on the device row. The
time zone matters because the connecting address is frequently unusable for
location: connect handling omits `ip` for loopback clients, and tunneled or
Tailscale clients land in private/CGNAT ranges. A browser knows its own zone
regardless of how it reached the gateway.

Both protocol additions are optional fields, so no version bump is needed.

* build(protocol): regenerate Swift models for presence timeZone
2026-08-23 20:07:06 -07:00
Peter Steinberger 1be9b56d57 refactor(protocol): rely on synthesized Swift coding keys (#128334) 2026-08-23 13:35:51 -07:00
ClawSweeper a77fbdef33 fix(ui): recover queued follow-ups from settled runs (#126180)
* fix(ui): recover queued follow-ups from settled runs

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>

* fix(ui): preserve terminal client run ownership

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>

* fix(sessions): persist recovered terminal client runs

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>

* test(gateway): publish configured reply runtime

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>

* fix(protocol): refresh Swift session row

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>

---------

Co-authored-by: Ian Moog <ianmoog42@gmail.com>
Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Co-authored-by: Roboclaw <roboclaw-bot@users.noreply.github.com>
2026-08-23 06:55:21 -07:00
Ayaan Zaidi 500c9b4ea6 fix(outbound): prevent remote gateway duplicate sends (#128202)
When the agent runtime talks to a remote Gateway (gateway.mode "remote" or a
gatewayUrl/gatewayToken override), the message tool withholds the runtime
identity, so a proven-not-sent outbound failure kept its durable retry row
and also surfaced as an error the model would answer by resending — a
duplicate once the queue replayed the row.

- deliver-queue-execute: mark the thrown error recoveryOwnedRetry when the
  proven-not-sent row stays replay-eligible (one retry owner per row)
- message.action: additive UNAVAILABLE detail code OUTBOUND_DELIVERY_QUEUED
- message tool: project that error into a non-throwing delivery_queued
  result ("queued, will retry automatically, do not resend") and keep the
  autogenerated idempotency-key mapping so an identical resend collapses

Validated with unit tests and a real-Telegram remote-gateway E2E
(affected on main: duplicate; fixed: single send, delivery_queued).

Closes #124279

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-23 17:44:48 +05:30
Vincent Koc 9046ecea73 fix(talk): isolate cancellation ownership contract (#127186)
* fix(talk): isolate cancellation ownership contract

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): frame realtime relay output audio

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): confirm turn-bound provider cancellation

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): bind legacy iOS output cancellation

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): close cancellation ownership gaps

Drain dynamically arriving forced-consult results before terminal completion and validate legacy iOS cancellation responses against the active lifecycle.

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): require Android output identity for cancellation

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): bind Android cancellation to action turn

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* test(talk): align relay checks with current main

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

* fix(talk): fence stale iOS output clear

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>

---------

Co-authored-by: Zhilong Zheng <zhengzhilong1115@gmail.com>
2026-08-23 03:58:00 -07:00
ClawSweeper 7d95cff39d fix: macOS onboarding waits for Gateway restart (#127713)
* fix(onboarding): wait for inference gateway restart

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* fix(onboarding): preserve custodian handoff after restart

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* refactor(macos): share activation restart finalization

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* style(macos): format restart reconciliation

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* fix(macos): compile restart finalization

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* test(macos): sequence onboarding restart proof

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* test(macos): finish onboarding after activation

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>

* test(macos): reuse managed restart proof

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

Co-authored-by: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com>

* test(macos): assert receipt before handoff cleanup

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

Co-authored-by: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com>

* fix(onboarding): keep restart verification bounded

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

Co-authored-by: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com>

---------

Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>
2026-08-22 15:51:04 -07:00
Ayaan Zaidi 041938bc2f feat(ui): add a Claude CLI 200K/1M context-window switch to the model picker (#127951)
Adds a generic plugin-declared selectable-context-window surface mirroring thinkingLevels: ModelCatalogEntry.contextWindows + contextWindowDefault through catalog normalization and the gateway protocol, session validation on sessions.create/patch, and a 200K/1M switch inside the Control UI model picker for Claude CLI 5-series models. The Anthropic plugin owns the option mapping: explicit 1m → `[1m]` argv suffix, 200k → bare id + CLAUDE_CODE_DISABLE_1M_CONTEXT=1, omitted → bare id (shipped default argv). Run budgets follow the selection on both CLI and native paths, so a 200K session gets a matching auto-compact window instead of a silent 1M budget.

Review fixes landed in this PR: run-owner prepared-fact plumbing so ordinary replies honor the selection; atomic catalog overlay merge and normalization for the options/default tuple; one-owner tuple reads in the picker; sessions.create key-presence patch semantics; native-run budget capping.

Feature direction and in-picker switch shape by @obviyus (maintainer review).
2026-08-22 23:25:23 +05:30
Peter Steinberger 1353ce0995 feat: run Codex sessions on approved paired devices (#127202)
* feat(codex): execute paired-device sessions over node carrier

* fix(node-host): preserve approved invocation session identity

* fix(codex): observe paired-node execution leases before handshake

* fix: fence paired-device placement and Codex execution owners

* fix: satisfy paired-device placement CI ownership guards

* fix(codex): reject credentialed paired-node URL parameters

* fix(codex): fence nested remote HTTP session credentials

* fix(codex): scrub node process URLs and preserve plaintext HTTP

* test(codex): republish node inventory after capability approval
2026-08-21 12:50:26 -07:00
Josh Avant ccbfa6c3a3 feat(ui): explain decision receipts in Activity (#126007)
* fix(audit): project safe decision receipt displays

* docs(agents): preserve audit display privacy

* fix(ci): satisfy audit receipt guardrails
2026-08-21 11:33:32 -07:00
Peter Steinberger e7cfff2167 feat(control-ui): stream live draft previews in the typing indicator (#126994)
* feat(control-ui): stream live draft previews in the typing indicator

Multi-identity sessions now show what a teammate is typing, not just that
they are typing: the composer's per-keystroke session.typing sends carry a
bounded tail of the draft (optional preview field, 400 code points max),
the gateway throttle re-emits on changed payloads at 250ms (boolean-only
stays at 1s, trailing edge keeps the latest draft), and the transcript
renders a per-actor bubble with the live text plus a blinking caret.
Actors without preview data keep the three-dot bubble.

Previews are ephemeral presence: never persisted, never part of the
session transcript or model context, excluded from aria-live regions, and
gated by the existing >=2-live-viewers, sharing-role, and incognito
checks. No new config surface.

* chore(protocol): regenerate Swift gateway models for typing preview

* fix(gateway): aggregate typing previews across same-actor connections

A boolean-only session.typing update from a second connection of the same
actor (another tab or device) erased their live draft preview, because
typing liveness aggregated per actor while the broadcast preview came only
from the latest request. Preview aggregation now lives with the connection
aggregation owner: updateTypingConnections tracks per-connection previews
and returns the newest non-empty preview among live connections, so the
broadcast keeps the active draft until its connection stops or expires.

Regression fails pre-fix (event lost its preview field).
2026-08-20 22:30:41 -07:00
Peter Steinberger bbbd70542b feat(sessions): recover offline device placements (#126284)
* feat(sessions): recover offline device placements

* chore(protocol): refresh session placement models

* perf(ui): lazy-load session placement recovery

* test(ui): remove dropdown timing assertion

* test(ui): await committed cloud recovery route

* fix(sessions): complete explicit abandonment locally

* fix(sessions): fence lists by runner availability

* fix(ui): preserve canonical session freshness

* fix(sessions): preserve recovery contracts after rebase

* test(ui): await durable cloud recovery entry

* test(gateway): complete current runner fixtures

* fix(gateway): publish runner availability edges

* fix(ui): preserve shared session freshness

* fix(ui): preserve canonical sidebar session state

* fix(sessions): preserve abandoned partials and run-owned replies

* fix(sessions): resume durable abandonment retries

* test(gateway): compose provisioning replay with runner availability

* fix(sessions): publish recovered move transitions
2026-08-20 09:59:59 -07:00
ClawSweeper 267ffc4754 feat(ui): manage automation condition triggers (#126534)
* feat(ui): manage cron condition triggers

* test(models): isolate ambient CLI availability

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

---------

Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-20 07:20:01 -07:00
Peter Steinberger c6ecfd26e5 fix(outbound): preserve first-reply behavior through durable delivery (#126205)
* fix(outbound): preserve reply facts through durable delivery

* fix(protocol): regenerate message action models

* fix(outbound): complete reply fact custody
2026-08-19 17:52:12 -07:00
Peter Steinberger 0a86702241 feat(github): authorize agent identities from Settings (#126474)
* feat(github): add device authorization lifecycle

* fix(github): harden device authorization lifecycle

* fix(github): refresh native tool display snapshot

* fix(github): refresh config and UI baselines

* refactor(github): break OAuth identity import cycle

* test: make gateway retry deadline assertion scheduler-safe
2026-08-19 17:41:58 -07:00
Peter Steinberger 0606e31d0e feat(gateway): broker GitHub publication (#126306)
* feat(gateway): broker GitHub publication

* refactor(gateway): split publication owners

* fix(gateway): enforce publication branch authority

* fix(gateway): bind publication to remote identity

* fix(gateway): bind publication recovery to remote state

* fix(gateway): preserve publication git state

* fix(gateway): retain publication recovery authority

* fix(gateway): commit publication index atomically

* fix(gateway): narrow publication index errors

* refactor(gateway): keep publication CAS errors private

* fix(gateway): recover publication index transactions

* fix(agents): describe GitHub publication tool

* fix(gateway): harden publication base fetch

* fix(gateway): reject publication filter semantics

* fix(gateway): verify publication creation base

* refactor(agents): align publication tool options

* fix(gateway): isolate publication object lineage

* refactor(gateway): use shared table probe

* test(gateway): keep publication helpers in routed suite

* perf(ui): lazy-load GitHub publication request

* fix(gateway): preserve publication support contracts

* fix(gateway): recover publication before authority checks

* fix(gateway): fence local publication snapshots

* fix(android): format generated protocol models

* fix(gateway): harden publication recovery

* fix(gateway): fence publication recovery

* fix(ui): reset completed publication cycles
2026-08-19 11:05:12 -07:00
Peter Steinberger 51599041bc fix: explain preserved session worktrees accurately (#126347)
* fix(sessions): report worktree preservation reasons

* fix(sessions): align preservation checks with current main

* perf(ui): keep preservation copy within startup budget

* perf(ui): reuse localized preservation copy

* test(ui): match preserved worktree confirmation copy

* test(ui): await committed raw config state
2026-08-19 10:59:42 -07:00
Peter Steinberger 3e0c980aaf fix(models): honor per-agent model metadata (#126194)
* fix(models): honor per-agent model metadata

Resolve per-agent aliases, bare providers, fallbacks, and catalog tags consistently across runtime, CLI, Gateway, sessions, and the Control UI.

* fix(models): preserve projection ownership

* fix(models): carry agent scope through fallbacks

* test(models): complete compaction fallback mock

* test(models): complete startup fallback mock

* test(agents): isolate recovery id expectations

* refactor(protocol): split public schema barrel

* test(cron): await child readiness events

* fix(models): scope native catalogs to session agent

* perf(ui): tighten agent model option projection

* perf(ui): reduce agent model projection overhead
2026-08-19 06:04:24 -07:00
Peter Steinberger 94eb34fa78 fix(skills): require re-review when proposals change (#126156)
* fix(skills): bind workshop decisions to reviewed revisions

* chore(i18n): refresh native source inventory

* test(skills): align revision proof with inspect projection

* test: align skill workshop regression fixtures

* fix(ui): align workshop revision admission proof

* fix(ui): keep revision errors out of startup
2026-08-19 01:52:11 -07:00
Peter Steinberger 2bcc06cc22 fix(cron): required delivery failures no longer report success (#126164)
* fix(cron): preserve required delivery completion

Record durable completion independently from payload execution so required delivery failure cannot delete one-shots or report successful waits.\n\nCloses #126163

* fix(cron): keep completion contracts acyclic

* fix(cron): keep delivery predicate private
2026-08-18 22:44:33 -07:00
Vyctor H. Brzezowski 2a469c1cb2 fix(ui): dismiss completed progress cards (#126102)
* test(ui): cover completed progress card dismissal

* test(ui): capture both completed card themes

* fix(ui): dismiss completed progress cards

* style: format progress card dismissal

* test(ui): assert visible dismissal lifecycle

* test(ui): wait for reloaded composer proof

* refactor(ui): reuse progress card action styles

* chore(protocol): refresh Swift progress card params

* style(ui): format progress card test

* fix(ui): hide progress dismissal from viewers

* test(ui): use Vitest viewer assertion
2026-08-18 23:39:31 -03:00
Peter Steinberger ef22410985 refactor(protocol): remove beta-only expectedRunId from chat.send (#125921)
The Gateway owns start-or-steer at admission (6515f6a255) and no
client produces expectedRunId anymore (d84a910fc8). The field shipped
only in v2026.8.1-beta.2 - never a stable tag - so it is removed rather
than deprecated. Steer sends resolve the selected session's current
operation; the exact-match branch, the operation|run target identity
discriminator, run_mismatch rejection, and the suggestion producers'
active-run-id selection (with its ambiguity failure) are deleted.
Provider-native turn fencing (Codex expectedTurnId) is unchanged:
the backend-captured runId on the injection target remains.
2026-08-18 11:28:36 -07:00
Peter Steinberger 2bed8caf9e feat(ui): channel conversation avatars in the sidebar (Discord + Slack) (#125668)
* feat(gateway): proxy channel conversation avatars

* feat(discord): capture conversation avatars

* feat(slack): capture DM sender avatars

* test(discord): bind guild avatar mock

* feat(ui): render channel conversation avatars

* fix(ui): align sidebar owner fixtures

* fix(gateway): version channel-avatar routes by media revision

A stable per-session URL let AuthenticatedAvatarRouteLoader's blob and
sticky-404 caches pin a mounted row to a stale or blank avatar after the
backing media changed. Append an opaque digest of the media reference so
replacement and 404-recovery change the route identity.

* test(ui): align sidebar owner facet

* fix(ui): keep owner chip until channel avatar loads

A session with a channelAvatarUrl suppressed its owner chip even while the
blob was loading, auth was not ready, or the route 404ed, leaving an empty
lead slot. The chip now rides as fallback content inside the avatar element
and yields only to a usable image. Covers 404 and auth-pending states;
avatar rows keep renderedOwnerId unset so an owner-viewer stays visible in
the facepile.

* perf(ui): keep channel avatar fallback within budget

* perf(ui): lazy-load the channel avatar element

The avatar element and its authenticated blob loader rode the startup
bundle through session-leading-indicator, pushing startup JS 51 B over the
CI gzip budget. Channel avatars are not startup-critical: register the
element on the first avatar row; the owner-chip fallback covers the
one-time upgrade window. Startup JS returns ~1 KiB under the ceiling.

* build(ui): raise startup baseline for channel avatars

CI-measured startup JS is 344379 B against a 343289 B baseline (+1090 B).
The avatar element and blob loader are code-split out of startup (previous
commit); the residual is the sidebar lead-slot render branch and row
plumbing, which cannot be deferred. Baseline updated via
check-control-ui-performance --update-baseline with CI bytes per the
script's contract; well inside the 4096 B ratchet step and 358400 B
ceiling.
2026-08-18 09:55:50 -07:00
Peter Steinberger 5f65ef8138 feat: add A2UI widgets to session boards (#125803)
* feat: add A2UI board widgets

* fix: harden A2UI board widget integration

* fix: repair A2UI CI contracts

* fix(canvas): keep native A2UI pushes on v0.8

* fix(canvas): support board-only widgets without file host
2026-08-18 09:08:45 -07:00
Peter Steinberger bdbb0444ee fix: share Control UI terminals with chat agents (#125758)
* fix: share Control UI terminals with chat agents

* fix: terminate canceled shared terminal opens

* perf(ui): keep terminal binding behind lazy boundary

* perf(ui): reuse terminal session scope for bottom dock

* perf(ui): reuse session route decision

* fix: keep shared terminal cleanup server-owned

* fix: preserve shared terminal session identity
2026-08-18 07:51:48 -07:00
Peter Steinberger efaa867d93 feat(nodes): publish exact runner slots (#125708)
* feat(nodes): publish exact runner slots

* docs(runners): link slot inventory PR

* fix(ui): keep session menus clickable under previews

* perf(protocol): keep runner slot schema off broad barrel

* test(codex): tolerate elapsed media startup budget

* test(codex): type media client factory mocks

* fix(protocol): reject impossible runner slot counts

* test(ci): stabilize loaded lifecycle checks

* test(ui): allow loaded bulk-delete scheduling

* test(ui): stabilize loaded interaction checks

* test(ui): isolate loaded update notice checks

* test(ui): colocate bulk-delete lifecycle coverage

* test(gateway): publish session config before async setup
2026-08-18 07:08:06 -07:00
Peter Steinberger 01811fb107 fix: show effective exec approval policy in Devices (#125756) 2026-08-18 03:22:31 -07:00
Peter Steinberger 079a874159 feat: revalidate cached chat history with cursor deltas (#125606)
* feat(gateway): add cursor catch-up to chat history

* feat(ui): revalidate cached transcripts via history cursor

* fix(ui): stop append-miss stubs clobbering persisted snapshots

* fix(gateway): fence history delta cursor to projected events

* fix: repair cursor delta landing gates
2026-08-18 00:54:58 -07:00
Peter Steinberger 0d74bc21b0 fix: keep non-person principals out of session owners (#125579)
* fix: keep non-person principals out of session owners

* fix(sessions): preserve creator filter semantics
2026-08-17 22:55:01 -07:00
Peter Steinberger 6e458d84bf feat(agents): configure per-agent GitHub identities (#125199)
* feat(agents): add managed GitHub identities

* fix(agents): use opaque GitHub setup handles

* style(codex): format managed shell environment call

* refactor(agents): own managed GitHub process identity

* chore(config): refresh baseline after rebase

* fix(ci): satisfy managed GitHub identity gates

* fix(ci): repair managed GitHub identity checks

* test(agents): align GitHub identity CI coverage

* fix(codex): scope login shell isolation

* fix(agents): let managed gh profiles authenticate

* fix(agents): harden GitHub identity setup

* test(gateway): align method suffix counts

* fix(ui): serialize GitHub identity mutations

* fix(protocol): generate GitHub configure requests

* test(ui): restore timers after identity tests

* fix(ui): preserve GitHub identity mutation ownership

* fix(agents): preserve native GitHub CLI auth
2026-08-17 15:54:03 -07:00
Peter Steinberger 57e862980b feat(gateway): carry machine class through session moves (#125292)
* feat(gateway): carry machine class through session moves

Profile move targets accept an optional machineClass with new-session
parity: validated in the protocol, persisted on the durable move intent
(bare nullable column, lazy same-version ALTER), replayed exactly by
restart recovery, and selectable from the Control UI move dialog.
Moving to the current profile with a different class resizes the
session's worker.

* fix(gateway): project placement facts on chat startup

chat.startup and chat.history built sessionInfo without the placement
projection sessions.list applies, so clients merging that row erased a
live worker placement and its move intent — the Runs on Cloud chip
disappeared after any turn until a full reload.

Reuse the canonical ensureColumn helper for the move table's additive
column instead of a hand-rolled PRAGMA read.
2026-08-17 14:25:13 -07:00
Peter Steinberger 7170a6231a feat(agents): unify agent status into a durable progress_card (#125125)
* feat(agents): unify agent status into a durable progress_card

Replace the write-only update_plan to-do tool and the fragmented plan
rendering with one durable status artifact per session: progress_card
({plan?, markdown?}, replace-on-write, 8 KiB markdown / 50-step caps).
Cards persist in a lazy-additive session_progress_cards table in the
per-agent DB (no schema-version bump), broadcast progressCard.changed,
and render from the store with exactly one live placement per view
(session rail when visible, else the composer-adjacent bar); transcripts
collapse to one-line receipts, and the sidebar hovercard shows other
sessions' cards inline (markdown + <progress>, DOMPurify allowlist, no
iframes). The three stream-derived plan renderers and their dedup
heuristics are deleted.

Codex runs disable the native plan tool per thread
(tools.update_plan.enabled=false) and receive progress_card via the
dynamic-tool bridge; compaction restore now reinjects the card (steps +
bounded markdown). Card writes still emit the legacy plan stream event so
native apps and channels keep working until their per-platform
migrations. Policy names map update_plan -> progress_card; the shipped
tools.updatePlan=false kill switch is honored.

Net -277 production LOC; -480 test LOC.

* test(agents): regenerate Codex prompt snapshots for update_plan thread-config disable

* chore(protocol): allowlist progressCard.changed for native apps pending card migration

* fix(ci): repair progress card integration checks

* fix(codex): canonicalize native progress cards

* test(gateway): reconcile progress card method order

* test(codex): stabilize native approval fixture
2026-08-17 09:44:04 -07:00
Peter Steinberger e6616fdc4c fix(sessions): surface cleanup failures and purge owned artifacts (#125217) 2026-08-17 04:43:33 -07:00
Peter Steinberger 4b0d573437 feat: session permission modes with worktree-scoped defaults (#124909)
* feat(gateway): add session permission modes

* feat(agents): enforce session permission policies

* test: cover session permission modes

* docs: explain session permission modes

* fix(agents): preserve apply patch default root

* feat(codex): map session permission modes

* feat(ui): add session permission controls

* test: cover session permission surfaces

* fix(agents): enforce session permissions from entry

* fix(codex): pass params object to requirements reader

* fix(codex): honor session permission modes

* fix(codex): preserve exec approval floors

* chore: revert changelog edit (release-owned)

* test: align full-mode promotion with upstream semantics

* chore(protocol): regenerate session permission models

* docs: register session permission modes

* fix(ui): use shared permission dropdown

* fix(sessions): restore permission mode contracts

* fix(ci): record session permission startup JS growth in the budget baseline

* refactor(agents): prepare session exec mode at dispatch

Host dispatch owns permissionMode→exec override preparation; the codex
plugin consumes prepared facts and the SDK barrel export is removed,
keeping the plugin-sdk surface at its recorded budget.

* test: type exec mode fixtures as ExecMode

* fix(codex): require admin scope for yolo permissions

* test: isolate codex permission command state

* ci: refresh PR checks against current main

* ci: refresh checks after gateway test repair

* test: fix main-fallout CI suites

* chore: refresh CI merge ref

* fix(worker): fence permission-mode cloud placements

* fix(sessions): clear worktree policy on detach

* chore(protocol): generate session permission mode enum

* test(gateway): type dispatch fixtures from session entries

* fix(ui): allow clearing session permission mode

* chore: refresh CI merge ref

* fix(ci): record permission picker startup growth in the budget baseline

* fix(gateway): gate session root projections
2026-08-17 04:37:35 -07:00
Peter Steinberger 55ce95fac8 feat(sessions): three-layer session ownership — agent attribution, assignable owner, participants (#125057)
* feat(sessions): stamp agent identity on spawned sessions and return spawn receipts

Agent-spawned sessions recorded the requesting session key as createdActor.id,
so the Control UI creator chip rendered an opaque key. Spawn producers now stamp
the canonical requester agent id; parent-authority validation moves to a new
trusted requesterSessionKey field. projectSessionActor enriches agent actors
with configured identity name/avatar at read time, and visible sessions_spawn
returns a sessionUrl + owner receipt with URL-first acknowledgement guidance.

* feat(sessions): assignable session ownership with owner facet and menus

GitHub-assignee-style ownership: sessions get a mutable owner (defaulting to
the immutable createdActor) stored in additive bare-nullable SQLite columns
with first-use lazy ensure. New operator.write sessions.assignOwner validates
targets, requires an identified caller, authorizes by session visibility, and
records assignedBy/assignedAt inside the write transaction. The sessions agent
tool gains assign_owner; the Control UI adds Assign-to-me/Assign-to menus in
sidebar rows and chat headers, renders the effective owner chip, and the
creator facet/filter now keys on effective owner. Sharing authority stays
anchored on createdActor.

* feat(sessions): record session participants and stack them in the owner chip

Records every distinct external prompter (human profile/channel sender, or a
requesting agent) per session in an additive session_participants table at the
turn-admission boundary — best-effort, deferred, never blocking the turn; the
session's own agent and viewers are never recorded, capped at 32 per session.
The session row projects a bounded participants list (owner excluded) plus a
total count with the same actor enrichment as owner/createdActor. The sidebar
chip becomes a pair-stack when others have prompted (owner front, one peeking
participant or +N behind), the chat header shows the full facepile, and an
authenticated involvingMe list filter adds an Involving-me sidebar predicate.
Participant projection is excluded from logical-session CAS equality so display
history never invalidates session writes.

* fix(sessions): identify built-in agent tool callers for owner assignment

The sessions tool's assign_owner dispatched through the in-process synthetic
client, which carries neither a signed agent-runtime identity nor a human
profile, so agent-initiated reassignment always failed with FORBIDDEN. The
tool now captures its trusted requester agent identity and carries it across
in-process dispatch as internal client state (never wire params); the handler
derives assignedBy as signed runtime identity, then trusted agent-tool caller,
then authenticated human. Live-verified end-to-end on a dev gateway.

* fix(ci): split oversized session modules and refresh prompt snapshots

Split the max-lines offenders at concept boundaries for session equality, tool overrides, and protocol owner schemas. Remove the redundant Number conversion from the node:sqlite participant count. Refresh prompt snapshots after drift from the sessions and sessions_spawn tool description updates.

* fix(ci): restore solo-mode chip suppression and conform new method descriptors

Solo-mode root cause: owner-assignment submenu options reused the permanent owner-chip custom element, so hidden menu avatars were counted as attribution chrome. Menus now use viewer avatars while gateway-gated owner chips remain exclusive to collaborative sessions.

Conform sessions.assignOwner to the 2026.8 descriptor and append-only advertised-method inventories, and regenerate the Swift and Kotlin protocol surfaces.

Keep historical v15/v14 fixtures frozen by stripping the new owner columns; the existing range already excludes the participant table. Replace the new raw SQLite schema probes with synchronous Kysely queries.

Clear max-lines by splitting the organizer host contract, pure agent-navigation projections, and ownership/filtering sidebar cases at their concept boundaries.

* fix(ci): integrate ownership series with latest main surfaces

Wire the sessions-page assign-owner action, merge capability imports, narrow the navigation export scope, and apply sessions-create formatting.

The owner-presence regression came from hidden assign-owner menu avatars emitting data-viewer-id, so owner and menu chrome now opt out of presence markers while real facepiles retain them.

* fix(sessions): scope the involving-me filter to profile-backed participants

Session participant history mixed channel-native sender ids with authenticated Gateway profile ids, so involving-me missed real sessions and could accept numeric collisions.

Record the actor_source namespace at each producer, carry it through the internal SQLite projection, and match authenticated viewers only against profile-backed human participants. Legacy NULL sources fail closed for filtering, while channel ids remain available for display.

* build(ui): raise startup budget baseline for session ownership surfaces

Ownership chips, assignment menus, and the participant stack add ~0.7 KiB
gzip to the startup path; CI compression landed just over the previous
baseline+tolerance. Hard cap (350 KiB) unchanged.

* refactor(sessions): drop raw NULL projection for the lazy actor_source column

The Kysely guardrail rejects typed raw sql snippets outside allowlisted
boundaries; select the lazily-ensured column only when present and let the
row projection treat its absence as unknown/legacy.

* build(ui): refresh combined startup baseline
2026-08-17 02:35:20 -07:00
Peter Steinberger f59e945013 feat(gateway): move sessions between Gateway and runners (#125036)
* chore: preserve session placement move work

* feat(gateway): move sessions between runners

* test(gateway): align recovery fixture with move barriers

* fix(protocol): format literal-only Swift initializers

* test(ui): prove session move terminal state

* refactor(gateway): narrow session move target fence

* fix(gateway): preserve pending result recovery ownership

* test(workers): migrate reclaim lifecycle fixtures

* refactor(gateway): keep move internals private

* test(gateway): align split bootstrap fixture with moves

* fix(gateway): recover session moves through exact intent

* refactor(gateway): derive move placement subtypes locally
2026-08-17 00:23:13 -07:00
Peter Steinberger bf9b25ab7e fix(gateway): prevent control-plane polling stalls (#124891)
* fix(gateway): avoid repeated control-plane scans

* fix(tooling): allow concurrent worktree validation

* fix(ci): refresh protocol and runner inputs

* perf(ui): defer hidden session refreshes

* fix(ui): resolve session refresh lint failure

* fix(update): preserve pre-cache update channel

* fix(update): normalize cached update channel

* fix(gateway): lifecycle-cache update install identity

* fix(ui): preserve manual history retry after layout scroll

* test(codex): repair side-question tool schema fixture
2026-08-16 20:58:27 -07:00
Peter Steinberger 022be60015 feat(agents): show creation hierarchy in Control UI (#124967)
* feat(agents): show creation hierarchy in Control UI

* chore(protocol): regenerate Swift agent model
2026-08-16 19:31:40 -07:00
Peter Steinberger 78beccf053 feat: add cloud worker profiles and machine selection (#124864)
* feat(workers): add per-dispatch machine classes

* feat(ui): add cloud worker machine picker
2026-08-16 18:37:00 -07:00
Peter Steinberger 03cf9851d8 fix(protocol): model cron run delivery trace (#124856)
* fix(protocol): model cron run delivery trace

* chore(protocol): refresh generated cron run model
2026-08-16 15:34:56 -07:00
Peter Steinberger 0c6040eb14 fix(runners): align reclaim results with runtime (#124791)
* refactor(runners): prepare placement move targets

* fix(runners): preserve normalized dispatch targets

* style(protocol): format reclaim result type
2026-08-16 13:46:00 -07:00
Peter Steinberger 4d872fbfbc feat(nodes): expose installed worker bundle status (#124640)
* feat(nodes): expose installed worker bundle status

* perf(nodes): defer bundle status validation

* fix(nodes): tighten bundle status type contracts

* docs(gateway): document node worker bundle status

* refactor(gateway): split runner inventory runtime

* test(ui): keep healthy device status quiet

* fix(ui): preserve steer target ordering

* test(ui): isolate catalog handoff lifecycle
2026-08-16 10:04:46 -07:00
Peter Steinberger bdcc6836e0 refactor(gateway): retire the Control UI device-auth migration window (#124667)
* refactor(gateway): delete the retired Control UI device-auth migration window

The one-time remediation window for the retired
gateway.controlUi.dangerouslyDisableDeviceAuth break-glass threaded a pending
state machine through the connect pipeline, startup bootstrap, lifecycle,
request context, device management, security audit, the hello frame, and a
Control UI banner flow — steady-state runtime carrying a retired-shape shim
the architecture reserves for doctor. The window is closed: the retired key
is now fully inert, doctor still detects and removes it, and a browser that
never completed the migration pairs through the normal device flow (one
approval, no data loss).

Deleted with it: the config-machine-state import of the legacy flag, the
hello deviceAuthMigration field (optional; clients validate envelopes only,
so older gateways emitting it stay compatible), the migration-bound device
management authz states, the requireNoPairingCapableOperator approval mode,
the effective-operator pairing event emitter whose only subscriber was the
migration completion, and the Control UI banner, loader, overlay wiring,
i18n strings, and e2e scenario support. Swift and Kotlin protocol models
regenerated.

Live-verified on an isolated gateway with the retired key present in config:
clean boot with no migration warning, silent CLI pairing and silent local
scope widening unaffected, and doctor reporting the key as retired-and-inert.

Net -1111 production LOC.

* fix(ci): finish migration-window cleanup surfaced by the gates

The doctor migration for the retired key kept its "Preserved for remediation"
change text and describe; both now state plain removal, with the retired
tests updated. peekStoredDeviceIdentityId lost its only production consumer
with the deleted banner loader and is removed with its test mock. Also fixes
the unrelated no-unnecessary-boolean-literal-compare lint break that #124636
landed on main in scripts/check-changed.mts (truthiness is equivalent for the
boolean-or-undefined TTY probe).

* fix(ui): drop the retired-key device-auth reader from the security summary

Review findings on the migration-window removal: the Control UI security
summary still derived its "Device auth" row from the retired
dangerouslyDisableDeviceAuth key, rendering device auth as disabled on
configurations that merely retain the inert key. Device auth is now
unconditionally enforced, so the row and its derivation are removed rather
than pinned to a constant. The build-admission test also tracks and removes
its temporary device-identity databases after each run.
2026-08-16 09:39:15 -07:00
Peter Steinberger fa29984096 fix(cron): missing automations show recovery guidance (#124663)
* fix(cron): report missing automations as not found

* refactor(protocol): group gateway error exports
2026-08-16 09:25:03 -07:00
Peter Steinberger f19f62cb77 perf(node-host): prewarm worker bundles (#124427)
* perf(node-host): prewarm worker bundles

* test(gateway): gate worker finalization responsiveness

* fix(node-host): negotiate bundle prewarming

* fix(protocol): refresh worker prewarm models

* fix(node-host): preserve prewarm upgrade compatibility
2026-08-16 00:41:42 -07:00
Peter Steinberger 78502eda6d feat(workers): run device sessions from Gateway bundles (#124037)
* feat(workers): run device sessions from Gateway bundles

Install the current Gateway bundle before a device environment becomes ready, verify it at attach and tunnel boundaries, launch only from the immutable namespaced bundle directory, and retire stale environments for idempotent reprovisioning. Remove the local execution mode and preserve the node-local build claim only as temporary inventory metadata for the final projection/cleanup slice.

* docs(runners): record Gateway bundle cutover

* test(ci): repair runner validation fixtures

# Conflicts:
#	src/scripts/test-projects.test.ts

* fix(workers): surface outdated node recovery

Keep legacy runner inventory diagnostic-only while exposing the update-and-reconnect action through node, environment, provider, placement, and Control UI surfaces.

* fix(workers): reject legacy inventory with recovery

* fix(workers): bundle worker deploy closure

* test(workers): close bundle cutover gates

* fix(workers): compose browser runtime at build

* fix(workers): satisfy bundle cutover gates

* fix(workers): route temp runtime through infra

* docs(workers): align bundle host guidance

* fix(ui): fence outdated session destinations
2026-08-15 17:46:44 -07:00
WhatsSkiLL 632581477f fix(session-catalog): preserve explicit agent ownership across UI and CLI (#123899)
* fix(session-catalog): preserve explicit agent ownership across UI and CLI

* fix(session-catalog): repair owner routing regressions

* Fix-session-catalog-CI-checks

* test(ui): split chat catalog pane coverage

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>

* refactor(session-catalog): bind Codex homes per request

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>

* refactor(session-catalog): centralize owner resolution

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>

* fix(codex): resolve session fork against the adopted home

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>

* refactor(ui): extract chat session discussion

* test(sessions): stabilize archive worker responsiveness check

---------

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-15 04:47:28 -07:00