Commit graph

78035 commits

Author SHA1 Message Date
Vincent Koc
65d13cb80b
fix(ci): retry fresh artifact metadata reads (#121876) 2026-08-11 14:01:31 +08:00
Peter Steinberger
432356a684
refactor(gateway): migrate internal agent turn callers to the typed facade (#121715)
* refactor(gateway): migrate internal agent turn callers

* test(gateway): mock typed subagent turn facade

* fix(gateway): preserve typed turn authorization

* chore(plugin-sdk): refresh typed facade closure hashes

* fix(gateway): validate typed agent turn requests

* fix(gateway): preserve typed turn lifecycle parity
2026-08-10 22:59:28 -07:00
Peter Steinberger
1220a7609a
refactor: consolidate promise-like guards (#121861)
* feat(normalization-core): add promise-like guard

* refactor: consolidate promise-like guards

* fix(normalization-core): keep isPromiseLike non-throwing on hostile then getters

ClawSweeper finding on #121861: the diagnostics-path local guard caught throwing
then getters; the canonical guard must classify, never throw.

* test(normalization-core): annotate intentional hostile-thenable fixture
2026-08-10 22:50:48 -07:00
Peter Steinberger
069f6e1c34
test(plugins): reuse Vitest for doctor contracts (#121878)
Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03

Co-authored-by: Amp <amp@ampcode.com>
2026-08-10 22:49:40 -07:00
Peter Steinberger
eb28964522
test(plugins): remove provider family meta-test (#121875) 2026-08-10 22:46:34 -07:00
Vyctor H. Brzezowski
9178c476f6
improve(ui): stop marking pinned sidebar rows with a message bubble (#121719)
A pinned session led with a generic message-bubble glyph that no other
session row gets. renderSessionLeadingState fills each row's single
leading slot from a priority ladder, and the pinned branch outranked the
creator avatar chip, so a pinned row could never show who created it and
carried a glyph whose only claim was "this is a session".

Drop the pinned branch and its styling. Pinned rows already render
through the same renderSessionTree anatomy as the session list, so they
now fall through the same ladder as any other row: attention icon, owner
avatar, or nothing. The leading slot stays width-reserved, so titles do
not shift.
2026-08-11 02:44:39 -03:00
Peter Steinberger
17f1571556
improve(test): speed up session list performance proofs (#121853)
* test(gateway): trim ACP batch boundary proof

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): remove broad resolver warmup

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): account for default thinking resolution

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): isolate cache proof from runtime startup

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Revert "test(gateway): isolate cache proof from runtime startup"

This reverts commit ee80381876ff2cd0994ddd54089fd26c78b1e586.

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Revert "test(gateway): account for default thinking resolution"

This reverts commit 17ec9f8b1012d67d1de0ac02d7d1f009651ee444.

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Revert "test(gateway): remove broad resolver warmup"

This reverts commit 00aabcbf2e273fde83d3e60677b4dfa9011aa8e0.

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): isolate session cache proofs

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): stub unrelated harness policy

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): stub unrelated subagent snapshot

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): pin synthetic thinking defaults

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(gateway): complete cache proof fixture

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-11 05:29:52 +00:00
Peter Steinberger
36fbd869ed
refactor(agents): eliminate export name collisions (#121768)
* refactor(auth): consolidate profile helper owners

* refactor(agents): consolidate subagent registry reads

* refactor(agents): disambiguate runtime placement helpers

* refactor(agents): disambiguate helper exports

* chore(scripts): burn export collision debt

* test(agents): follow moved subagent read owner
2026-08-10 22:24:33 -07:00
Peter Steinberger
03f9f29a86
refactor(agents): centralize exec process cancellation (#121854) 2026-08-10 22:15:31 -07:00
Vyctor H. Brzezowski
3dc03f9265
fix(ui): confirm destructive sidebar session actions in-app (#121286)
* fix(ui): confirm destructive sidebar session actions in-app

The sessions sidebar still gated batch delete, single delete, cloud-worker
stop, and preserved-worktree removal on native window.confirm. In an
embedded WebView without a dialog bridge that call resolves false, so each
action returned early with no request, no error, and no visible outcome.

PR #118250 introduced showConfirmDialog and converted the Sessions,
Worktrees, and Nodes pages but not the sidebar operations layer, leaving
one product action with two confirmation mechanisms. Route all four sites
through the same helper and let the already-present post-decision scope
guards do their job, extracting the cloud-worker reclaim guard above the
await so the modal never opens for a stop this surface never performs.

Closes #121275

* feat(ui): let operators opt out of the session delete confirm

Session deletes are the repeatable, per-row destructive action in the
sidebar, so their confirm now offers "Don't ask me again". Stopping a cloud
worker and removing a preserved worktree deliberately get no opt-out: the
first is a rare shared-resource action, the second destroys the only copy
of uncommitted work. The checkbox exists only for callers that pass a skip
preference, so the serious confirms stay unskippable by construction.

The preference is device-local in UiSettings rather than a synced ui.prefs
key, so opting out on one browser cannot lower the bar on the operator's
other devices. Appearance -> Sidebar carries the matching toggle with the
standard reset affordance, which is how asking gets turned back on.

* fix(ui): scope the delete opt-out copy and refresh its settings view

Autoreview surfaced two preference-consistency defects.

The setting copy promised a general "session or a selection" policy, but
only the sidebar honours it; the Sessions page delete paths still prompt.
Extending the preference there would cross into another owner's in-flight
surface, so narrow the copy to what actually ships and keep the extension
as the recorded follow-up.

Persisting the opt-out also wrote local storage without notifying the
appearance subscription, so a mounted Settings -> Appearance kept showing
the toggle enabled while deletes already skipped the prompt. Publish the
refresh through the scope's theme capability after persisting.

* fix(ui): space the confirm opt-out away from the message copy

The checkbox rendered flush under the message with no separation, so it
read as a third line of the paragraph rather than a distinct control, and
the group crowded the action row.

Give it the 12px separation the details block already uses in this card,
keeping the 16px action gap below, plus the 10px control gap and 16px box
the config-form checkboxes use so the box and label align. Verified in
light and dark at desktop and mobile widths.

* fix(ui): use the cursor-action token on the confirm opt-out

The spacing fix hardcoded cursor: pointer, which the Control UI cursor
policy forbids outside link rules; controls consume var(--cursor-action)
so the hand stays configurable from one place.

Caught by ui/src/styles/cursor-policy.node.test.ts. Token resolves to the
same hand, so the published dialog captures are unaffected.

* fix(ui): keep the delete opt-out to the surface its setting names

deleteSession is shared: the chat-pane header menu calls it too. The
skip preference was applied inside the operation, so opting out from the
sidebar also silenced future header confirmations while the setting text
promised it applied to sidebar deletes only.

Make the opt-out opt-in per caller with a default that keeps asking, and
let the sidebar be the one caller that offers it. The header and any
future caller now match the copy without having to remember anything.

* refactor(ui): split the session group catalog out of the operations runtime

Adding the in-app group confirm on main pushed
session-organizer-operations.runtime.ts past the 700-line ceiling once
this branch's session confirms landed beside it.

Move the catalog and section-order operations into their own module. They
write the group catalog directly and never touch session rows, so the
dependency runs one way and no import cycle appears. The shared access
gate moves to the batch-mutations sibling both now import, and the
runtime keeps re-exporting the catalog names the controller loads as one
namespace.
2026-08-11 02:15:05 -03:00
Peter Steinberger
79dcf48434
test(macos): remove stale Canvas helper probes (#121866) 2026-08-10 22:12:49 -07:00
Peter Steinberger
3b828df2d9
test(logging): trim recovery duplicate coverage (#121864) 2026-08-10 22:10:26 -07:00
Peter Steinberger
10d3466c98
test(ui): remove duplicate dark contrast case (#121862) 2026-08-10 22:08:06 -07:00
Peter Steinberger
90ddeb4b40
refactor(agents): absorb overflow, retry, and matcher duplicates into the failover substrate (#121817)
* refactor(ai): centralize context overflow matching

* refactor(agents): add retry evidence and guarded status facets

* refactor(agents): absorb assistant retry and key matchers

* chore(plugin-sdk): refresh failover closure hashes

* fix(agents): consume retry evidence at altitude zero

* test(agents): narrow retry corpus provider fixtures

* test(agents): keep retry corpus sorting immutable
2026-08-10 22:07:21 -07:00
Peter Steinberger
aba8f10bc3
fix(agents): stop reporting undelivered subagent results as delivered (#121837)
* fix(agents): record intentional non-delivery truthfully

* fix(agents): stop reporting sinkless cron completions as delivered

The inactive-cron guard runs before requester-agent dispatch. Its regression now asserts the recorded intentional non-outcome before verifying that queue, gateway, and message sends remain untouched.

* test(agents): migrate announce outcome fixtures

* chore: drop changelog edit per repo policy
2026-08-10 22:04:29 -07:00
joshavant
8646c19a25
docs(agents): preserve execution identity audit invariants 2026-08-11 00:03:25 -05:00
Vyctor H. Brzezowski
9327c10d2b
feat(ui): edit a queued chat message in place (#121692)
* feat(ui): edit a queued chat message in place

The pencil on a queued row, or a double-click, lifts the message and its
attachments into the composer. The row stays where it is, marked Editing,
so the position the corrected message will take is visible rather than
promised. The composer shows an edit banner with an X to cancel.

Cancelling touches storage not at all, because the row never left the
queue. Sending retires the original and hands its position to the
replacement, which lands in the same slot. While an edit is open the
drain refuses that row, so a reconnect cannot deliver the text the
operator is visibly rewriting; the queue behind it waits, which is the
same contract the held position promises.

* fix(ui): retire an edited queued row only with its replacement

The send retired the source row first and admitted the replacement second,
so a rejected store write left the operator with neither: the original gone,
the replacement never persisted, and the attachments it dropped already
released. Admission now carries the source id and swaps both rows in one
store write, so a rejected write changes nothing and the edit stays open on
the row that is still there, which is what cancelling already promises.
Filtering the source before the cap check also keeps a replacement
admissible on a full queue.

The edit token recorded only the session key, but an outbox is scoped by
session and agent, and a raw global session keeps its key while the selected
agent changes underneath it. The token now carries the agent that owned the
row and every reader validates the whole scope, so a send after a switch
cannot retire a row in the outbox it left behind.

* fix(ui): hold an edited queued row across every pane

Panes share one outbox and one drain lane while composer state stays per
pane, so the hold that keeps a row from being delivered while it is rewritten
was invisible to the pane that drained it: a session event in a second split
pane resumed every stored outbox, saw no edit, and sent the original text.
The hold is now read across every pane subscribed to the outbox, which keeps
one source of truth — each pane's own scope-validated edit — and needs no
lifecycle of its own, so closing a pane or switching agent releases it by
simply no longer being an active edit.

The payloads an edit owns now travel with its token. The write that admits
the replacement retires the source row and tells every pane synchronously, so
by the time the send released the images the replacement had dropped, the row
holding them was already gone and nothing was released. That leaked the
payload and its object URL for the rest of the browser session.

* fix(ui): release attachments from cancelled queue edits
2026-08-11 01:59:56 -03:00
Peter Steinberger
e4a6ded2d3
fix(release): scope iOS version checks to native releases (#121863) 2026-08-10 21:57:02 -07:00
Ayaan Zaidi
45ec5f4662
feat(ui): hide system-created probe sessions from the sessions sidebar (#121855)
Machine-created probe/run sessions (health checks, internal effect sessions) previously surfaced in the Control UI sessions sidebar as raw first-message rows and buried real conversations. Classify them from recorded creation provenance only (system actor, or unnamed run/internal creation without a human actor) and hide them by default behind a persisted "Show system sessions" toggle, mirroring the automation-sessions toggle. Cron rows stay owned by the automation toggle; the selected session, the Sessions page, and the toggle keep hidden rows reachable. Legacy rows without provenance stay visible.

Closes #121851

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 04:53:15 +00:00
Vyctor H. Brzezowski
09c7cd98d9
fix(ui): align agent switcher rows on one leading column (#121705)
* fix(ui): align sidebar menu rows on one leading column

The sidebar dropdown family never received the Web Awesome shadow-row
corrections that .session-menu__item already documents, so the agent
switcher stacked our 8px flex gap on top of Web Awesome's forced
`margin-inline-end: 0.75em` slotted-icon margin, kept a layout-only
native checkmark it never shows, and mixed a 24px avatar column with a
16px icon column. Agent rows started their labels 19px right of the
command rows and their avatars 11px right of the command glyphs.

Give the family one leading column: drop the stacked gap, hide the
agent menu's unused native checkmark (selection lives in the trailing
details rail), widen command glyph boxes to the avatar's 24px, and pull
the section label, provenance note, and empty state onto the rows' 8px
inline edge. Command-row label positions are unchanged; the avatar rows
and the More menu's route rows move onto the shared gutter.

* test(ui): assert the sidebar menu's shared leading column

The alignment fix had no committed regression: the existing agent-menu
E2E opens and navigates the menu but never reads its geometry, so the
19px label drift could return silently.

Assert in the browser that an avatar row and a command row start their
leading slot and their label on the same x. Fails on pre-fix code, where
the avatar column sits 11px right of the command glyphs.
2026-08-11 01:50:55 -03:00
Peter Steinberger
4b46a14a54
feat(apple): show live subagent activity in chat (#121815)
* feat(apple): show live subagent activity in chat

* chore(apple): refresh native localization inventory
2026-08-10 21:50:13 -07:00
Vyctor H. Brzezowski
d7ea18e600
feat(ui): give pinned sessions their own sidebar group (#121712)
Pinned sessions shared the Pages navigation group's container and head,
so a pinned chat rendered as one more nav row directly under Automations
or Plugins with nothing separating elevated content from navigation.

Split the sidebar zone at render time: navigation entries stay in the
Pages list, session entries move into a second group with the sidebar's
inter-group spacing and a muted "Pinned" section label matching the
PAGES/SESSIONS treatment. Both groups keep the zone drop handlers, so
dragging a session into either one still pins it and records its slot,
and the group disappears entirely when nothing is pinned.
2026-08-11 01:49:03 -03:00
Peter Steinberger
c5b7b8e33c
fix(system-agent): setup chat fails silently on dev-roster gateways (#121784)
* fix(system-agent): repair setup-chat execution identity, harness fallback, and error surfacing

Always inject the reserved "openclaw" execution entry for off-roster system-agent runs.
Keep implicit harness runtime selection implicit so Codex refusal falls back to the OpenClaw harness, while explicit policy remains fail-closed.
Log openclaw.chat turn failures and surface their underlying causes in the UNAVAILABLE response.

* chore(plugin-sdk): refresh agent harness API baseline

* test(system-agent): align fixtures with optional harness routes

* fix(system-agent): keep execution roster out of TUI catalog lookup

* refactor(system-agent): keep verified state types internal

* fix(system-agent): separate setup policy and execution config
2026-08-10 21:44:00 -07:00
Peter Steinberger
c896b9b2cd
test(ui): remove duplicate update confirmation cases (#121836) 2026-08-10 21:40:03 -07:00
Peter Steinberger
98b222ded8
feat(scripts): report aliasing re-exports (#121849) 2026-08-10 21:36:22 -07:00
Peter Steinberger
df3e111c91
fix(ci): run policy tests for watched source changes (#121841)
* fix(ci): route source policy tests by watched paths

* chore(ci): keep policy watch table module-private
2026-08-10 21:30:21 -07:00
Peter Steinberger
ee7fae8a90
fix(ui): native command settings no longer require Raw mode (#121832)
* fix(ui): render native command settings controls

Render boolean-or-auto command settings as safe On/Off/Auto controls instead of forcing Raw mode, while preserving typed config values and fail-closed handling for unsafe unions.

* fix(ui): preserve oneOf union exclusivity
2026-08-10 21:28:06 -07:00
Peter Steinberger
19ed7feee1
test(ci): rebalance compact Node shards (#121834)
* test(ci): rebalance compact node shards

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(ci): guard compact planner coverage

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(ci): drop obsolete shard isolation check

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(ci): stop freezing compact shard placement

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-11 04:18:34 +00:00
Josh Avant
73a9eed95b
refactor(audit): add canonical admitted-run context (#120534)
* feat(audit): carry canonical admitted execution context

* fix(agents): preserve admitted context across retries

* fix(worker): fence legacy launch dialect

* test(gateway): track approval temp dirs

* fix(plugin-sdk): preserve harness attempt compatibility

* fix: close delegated run authority at owner boundaries

* fix: internalize delegated authority validators

* refactor: split delegated authority proof surfaces

* refactor: centralize command admission identity

* test: claim runtime tool authority

* fix(gateway): keep lifecycle cleanup within static budgets

* fix(agents): revalidate harness policy authority

* fix(agents): fence awaited approval capability results

* test(copilot): supply required harness capability fixtures

* fix(agent): preserve scoped embedded run admission

* fix(agent): preserve keyless and worker authority

* test(agent): bind incomplete-turn authority

* docs: preserve execution authority invariants

* chore(plugin-sdk): regenerate API baseline

* fix(gateway): notify pending claim closure

* fix(gateway): revalidate delegated tool authority

* fix(plugin-sdk): keep source guard internal

* fix: close delegated authority races

* fix: revalidate delegated side effects

* fix: close harness authority projection gaps

* fix: align authority integration types

* fix: isolate settled harness finalization

* fix: fence recovery identity finalization

* fix: preserve committed session worktrees

* fix: preserve worker placement agent identity

* fix: fence active harness tool work

* fix(plugins): restore embedded run admission owner

* chore(plugin-sdk): compose integrated surface budgets

* fix(copilot): keep finalization attempt type internal

* fix(plugins): complete admission owner type imports

* test(harness): use settled finalization attempt shape

* fix(security): retain exact side-run and approval authority

* fix(security): preserve protected authority through terminal sweep

* fix(agents): follow moved recovery store owner

* fix(ci): align integrated authority owners with gates

* fix(plugins): distinguish embedded agent adapter export

* chore(plugin-sdk): regenerate API baseline after rolling integration

* refactor(gateway): keep session authority within owner budgets

* fix(gateway): keep session helpers private

* docs(plugin-sdk): name the V2 parameter subpath

* chore(integration): reconcile worker and SDK surfaces

* docs(plugin-sdk): require the V2 host API floor

* chore(plugin-sdk): regenerate after proxy-auth integration
2026-08-10 23:15:20 -05:00
Vincent Koc
6dbea69515
fix(ci): separate frozen target context from candidate identity (#121835) 2026-08-11 12:14:19 +08:00
Peter Steinberger
8c3c8a2ab0
feat(android): show live subagent activity in chat (#121813)
* feat(android): show live subagent activity

* chore(android): mark task event handled

* fix(android): localize activity diff chips

* fix(android): reconcile dropped task activity
2026-08-10 21:08:44 -07:00
Peter Steinberger
45e4c838d0
feat(ui): show live subagent activity rows (#121840)
* feat(ui): show live subagent activity rows

* test(ui): split subagent activity coverage
2026-08-10 21:08:27 -07:00
Peter Steinberger
768f53d345
fix(secrets): resolve default aliases by source (#121630) 2026-08-10 21:05:26 -07:00
Peter Steinberger
39cce52ed2
fix: report message-tool-only runs that never reply (#121822)
* fix: record missing message-tool source replies

Settled message_tool_only runs now record a bounded message-tool-not-called terminal fact when no current-source delivery, silence, or continuation exists. Private final text remains private; long-final recovery and actual deliveries remain unchanged.

* fix: preserve intentional message-tool non-delivery

Reuse the canonical private-final exclusions for policy-denied, heartbeat, and room-event runs before recording message-tool-not-called. Keep stranded retry diagnostics unchanged and cover each lifecycle outcome.

* refactor: remove private-final test-only export

Keep the warning predicate local to its test now that production shares the canonical private-final classifier directly. This satisfies the exact-head dead-code export gate without changing runtime behavior.
2026-08-10 21:04:26 -07:00
Peter Steinberger
29bd7e462f
fix(ui): hide agent selectors for single-agent setups (#121826)
* fix(ui): hide redundant agent selectors

* test(ui): split agent selector regression

* test(ui): keep selector e2e fixtures multi-agent
2026-08-10 20:55:54 -07:00
Peter Steinberger
7dbc2916a3
fix: cloud workers work with trusted-proxy browser auth (#121671)
* fix(ui): make cloud recovery scope gateway-owned

Allow trusted-proxy and other authorized browser sessions to use cloud workers without deriving recovery ownership from client-visible credentials or Web Crypto. The Gateway now emits an opaque principal-bound recovery scope.

* test(gateway): type recovery scope fixture

* fix(ui): preserve cloud recovery across upgrades

Keep the shipped credential-derived scope for older v4 Gateways and migrate credential-proven pending recovery rows to the Gateway-owned namespace before publishing readiness.

* perf(ui): lazy-load recovery migration

* fix(ui): preserve canonical recovery promotion

* test(ui): use deterministic provider navigation

* fix(ui): authorize recovery scope migration

* build(plugin-sdk): refresh recovery compatibility baseline

* test(gateway): expect recovery migration authorization
2026-08-10 20:52:55 -07:00
Ayaan Zaidi
b65fa6b96a
fix(skills): keep specialized skills during cleanup (#121829)
Preserve distinct domain-specific skills with reliable triggers during automatic collection cleanup while retaining junk, duplicate, and stale-fragment removal.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-11 03:49:19 +00:00
Vyctor H. Brzezowski
619f3c9ab1
feat(ui): identify project files in Control UI chat (#121775)
Recognized workspace paths in chat now read as their basename behind a
file-type glyph, so a file reference is identifiable before it is read.
Markdown, package manifests, TypeScript and other code, TSX/JSX components,
config/data, shell scripts, and images each get their own mark; anything else
falls back to a plain document. Paths sharing a basename keep the smallest
trailing suffix that tells them apart, and Windows paths keep their own
separator.

Classification wins over authoring syntax: a path written in backticks is a
file link first, so it drops the inline-code chip and renders exactly like a
bare path beside it. Code spans that are not file links keep the chip.

The full path stays addressable: it drives the file panel, the new tooltip,
and the message Copy action (which returns the original Markdown).
Author-written labels in [label](path) links are never rewritten, and text
that is not a recognizable path stays plain prose.

The glyph is painted as a masked ::before like the sibling GitHub mark, so it
stays out of the accessibility tree and out of copied text and follows the
link color in every theme. Extension classification moves to one shared
resolver that the file preview modal now uses too, replacing its own
code/text extension set.
2026-08-11 00:48:43 -03:00
Peter Steinberger
27be68d2d1
fix(agents): report best-effort delivery failures (#121824)
* fix(agents): report best-effort delivery failures

Keep explicit delivery intent through Gateway and agent preflight so final delivery records a send or concrete failure outcome. Persist replay markers only after a route is prepared.

Refs #121804.

* test(gateway): preserve requested delivery contract

Update the multi-channel Gateway regression to assert that best-effort preflight preserves explicit delivery intent for final outcome reporting.

Refs #121804.
2026-08-10 20:40:34 -07:00
Paul Campbell
6bd83561ad
fix(mxc): agent tools fail in Windows workspace subdirectories (#115399)
* fix(mxc): validate agent workdirs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: fc20c06c-e8c0-4649-9efd-ad87fac71a4f

* fix(mxc): treat non-directory parents as unavailable workdirs

validateWorkdir declares that unusable workdirs return null, but only ENOENT was classified as missing. A workdir nested under a file raises ENOTDIR on Linux, which escaped as a raw filesystem error instead of the normal unavailable-workdir result.

The same ENOENT-only classification appeared at three sibling sites in this file, so the shared isMissingPathError guard fixes the whole class rather than the single reported path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d7253887-71c9-4083-a591-91979c981e39

* fix(mxc): sandbox provisioning crashes reading containerWorkdir

MxcFsBridge initialized three fields from this.sandbox, but sandbox is a constructor parameter property. Plugin sources load through jiti, which evaluates field initializers before assigning parameter properties, so this.sandbox was still undefined and every MXC sandbox provisioning failed with SandboxProvisioningError: Cannot read properties of undefined (reading 'containerWorkdir').

Assign the derived fields in the constructor body from the sandbox parameter instead. The repo tsconfig sets useDefineForClassFields=false, so tsgo and Vitest (esbuild) both apply legacy ordering and could not observe the failure; only the jiti-loaded runtime path did.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d7253887-71c9-4083-a591-91979c981e39

---------

Copilot-Session: fc20c06c-e8c0-4649-9efd-ad87fac71a4f
Copilot-Session: d7253887-71c9-4083-a591-91979c981e39
2026-08-11 11:38:34 +08:00
Peter Steinberger
b5d5ec340f
feat(cloud-workers): add desktop apps and browser autonomy (#121475)
* feat(cloud-workers): add desktop apps and browser autonomy

provider-attested Browser/Terminal launchers, shared visible loopback CDP Browser tool, no MCP/cookies/generic command.

* feat(ui): add cloud desktop app launcher

* docs(gateway): document cloud desktop apps and browser autonomy

* perf(ui): trim desktop launcher startup copy

* refactor(ui): simplify desktop launch feedback

* perf(ui): reuse desktop app labels

* fix(ui): keep desktop launch failures actionable

* fix(crabbox): allow browser bootstrap to finish

* fix(cloud-workers): honor provider provision budgets

* fix(cloud-workers): persist browser screenshot receipts

* fix(cloud-workers): bound browser screenshot lifecycle

* fix(cloud-workers): avoid replaying desktop launches

* test(cloud-workers): isolate browser runtime integration

* refactor(cloud-workers): tighten desktop runtime boundaries

* test(cloud-workers): keep browser runtime mock synchronous

* fix(cloud-workers): break gateway type import cycle

* fix(ci): settle admitted setup sessions in tests

* build(plugin-sdk): refresh desktop app contract

* ci: refresh merge-tree validation

* build(plugin-sdk): regenerate desktop app baseline

* style(gateway): format merged method order test
2026-08-10 20:31:07 -07:00
Peter Steinberger
cc9e75efb2
fix(media): accept canonical file URL forms (#121676)
* fix(media): accept canonical file URL forms

* fix(media): normalize file store keys

* fix(media): handle canonical file URLs end to end

* test(media): expose nested store key failure on Windows

Amp-Thread-ID: https://ampcode.com/threads/T-019fed8d-ad78-74f0-9592-a3466bb6a9bd
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* test(media): cover canonical file URL dedupe

Amp-Thread-ID: https://ampcode.com/threads/T-019fed8d-ad78-74f0-9592-a3466bb6a9bd
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Revert "test(media): cover canonical file URL dedupe"

This reverts commit 63efbbc879b6d180b4082048291349b439ec4b41.

* fix(media): keep nested store keys portable

* fix(media): complete canonical file URL handling

* fix(media): reject encoded separators in comparison keys

* fix(auto-reply): block canonical file URLs without sandbox

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-10 20:30:37 -07:00
Masato Hoshino
504badbfb1
fix(line): treat a non-positive mediaMaxMb as unset (#121184)
`channels.line.mediaMaxMb` has no range constraint, so a configured `0` or
negative value loads cleanly and then survives the `??` chain in `createLineBot`
into `mediaMaxBytes`. Every non-empty inbound media download is then measured
against a 0-byte budget it cannot satisfy: `saveMediaStream` throws
`Media exceeds 0MB limit`, the LINE handler degrades the attachment to
`[line attachment unavailable]`, and the only trace is a verbose log line that
never names the setting.

Treat a non-positive value as unset at every link of the chain, so it falls back
to the same 10 MB default an unset field already means, and a non-positive
caller override no longer discards a valid account config value. Matrix
(#120466) and Zalo (#120988) resolved the identical contract the same way; LINE
was the last bundled channel resolving this field by hand without the guard.

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-10 22:30:10 -05:00
Peter Steinberger
2715d1a878
refactor(agents): unify dot-family attempt helper names (#121550)
* refactor(agents): add dash-named attempt helpers

* refactor(agents): repoint dot-family attempt imports

* refactor(agents): remove dot-family attempt helpers
2026-08-10 20:27:56 -07:00
Peter Steinberger
69220fc0d4
test(macos): remove body-only view probes (#121820)
* test(macos): remove body-only view probes

* test(macos): drop retired i18n wrapper contract
2026-08-10 20:26:56 -07:00
Peter Steinberger
ebfd3ba154
improve: cut CI critical path without more workers (#121807)
* test(ci): remove redundant builds from critical path

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs(ci): format runner table

Amp-Thread-ID: https://ampcode.com/threads/T-019fee8d-665d-707b-a380-23f2a6a1ce03
Co-authored-by: Peter Steinberger <steipete@gmail.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-10 20:21:20 -07:00
ffyuuu
efe5d8d76e
fix(longcat): correct cache pricing and add provider icon (#121369)
* longcat: align baseUrl with docs, fix cacheWrite pricing, add brand icon

- Point baseUrl at the documented https://api.longcat.chat/openai/v1 path
  instead of the undocumented unversioned alias route.
- Fix cacheWrite pricing to 0: the LongCat pricing page has no separate
  cache-write charge (matches deepseek/moonshot/zai catalog conventions).
- Update the pricing page link to the current docs path.
- Add the official LongCat brand icon (provenance recorded in
  ATTRIBUTION.md) and display-name mapping so the Control UI shows proper
  branding instead of the letter-badge fallback.
- Migrate the persisted legacy default baseUrl via a plugin-owned doctor
  configRepair contract: onboarding persists
  models.providers.longcat.baseUrl and the runtime reads the stored value,
  so the contract rewrites exactly the former default to /openai/v1,
  preserves custom endpoints, and warns via a legacy-config rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(longcat): correct provider metadata

* fix(longcat): migrate persisted stock pricing

* style(longcat): format doctor repair

* fix(longcat): preserve doctor migration after compat cleanup

* fix(longcat): avoid map spread in doctor repair

---------

Co-authored-by: yuzehui02 <yuzehui02@meituan.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-10 20:15:18 -07:00
Peter Steinberger
4e22cee056
perf(ui): format sidebar timestamps on demand (#121819)
Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

Co-authored-by: Amp <amp@ampcode.com>
2026-08-10 20:09:52 -07:00
Vincent Koc
c87f77185d
fix(release): preserve frozen candidate identity (#121786)
* commit 'c3e3f276f2':
  fix(release): preserve frozen candidate identity
2026-08-11 11:08:13 +08:00
Marvinthebored
bd897a6143
fix(exec): make timeout unit explicit (#119874)
* fix: exec timeout field does not state its unit, so callers pass milliseconds

`exec.timeout` is in seconds, but its sibling `yieldMs` is in milliseconds and
the `process` tool's identically named `timeout` is also in milliseconds. Those
two tools are used together in one workflow: exec backgrounds a long command,
then process polls it.

The unit exists only in the field description, and code mode deliberately defers
descriptions - the model sees `timeout?: number` and nothing else. A caller that
guesses milliseconds passes 900000 intending 15 minutes and gets ~10 days, so
the command never times out.

Add `timeoutSeconds` as the canonical field, matching the convention already
used by the config default (`tools.exec.timeoutSeconds`) and the sibling
(`yieldMs`). Keep `timeout` as a deprecated alias; `timeoutSeconds` wins when
both are supplied. Project the new field onto `nodeExecSchema`, which
hand-picks its properties.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix(ci): satisfy test-type and dependency checks

Two CI failures from the test approach, not the change itself.

- check-test-types: TypeBox's TOptional does not surface `description` on its
  static type, so the schema assertions failed under tsgo. Read it through a
  narrow accessor instead.
- check-dependencies: knip flagged resolveExecTimeoutSeconds as an unused
  export because only the test imported it. Move it beside the schema it
  resolves, where the exec runtime imports it, so it is production-reachable.

Verified with the same commands CI runs: `run-tsgo.mjs -p
test/tsconfig/tsconfig.core.test.json` reports no errors in these files, and
knip --production reports no unused exports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix(ci): fit the exec description budget and satisfy oxlint

- `agent-tools.schema.test.ts` enforces a 550-char budget on the exec tool's
  model-facing descriptions; the two new ones pushed it to 640. Shortened both.
  The rename is what makes this possible: the unit now lives in the field name,
  so the description no longer has to carry it. Extended that budget test to
  pin `timeoutSeconds` alongside `timeout`.
- oxlint `no-base-to-string`: the test's description accessor typed the field as
  `unknown` and stringified it. Typed as `string | undefined` instead.

Verified: budget test passes (137 tests green across both suites), and all six
behavior tests still fail against origin/main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix: remove the unit-ambiguous exec timeout field instead of deprecating it

Owner decision on the review's "ambiguous model-visible alias" finding: drop
`timeout` entirely rather than keep it as a deprecated alias.

Keeping it visible defeated the purpose. Code mode renders property names and
types and defers descriptions, so a model would still be offered a bare
`timeout` sitting next to a millisecond-based `yieldMs` and could still pick it.
A deprecation marker in a description the model never sees changes nothing.

Backward compatibility is not required here: every tool call is constructed
fresh by the model from the current schema, and a repository search found no
internal caller passing `timeout` to the exec tool. `timeoutSeconds` is now the
only exec timeout field, on both the main and node-only surfaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix: migrate the internal exec callers to timeoutSeconds

Removing `timeout` from the exec schema broke three real internal callers that
the earlier survey missed, plus two test files and the runtime's own reads.
`check-test-types` caught it; my local search had filtered `timeout:` matches to
lines mentioning "exec" and discarded these.

- src/auto-reply/reply/bash-command.ts
- src/auto-reply/reply/commands-diagnostics.ts
- src/auto-reply/reply/commands-export-trajectory.ts
  all passed `timeout: timeoutSec` and now pass `timeoutSeconds: timeoutSec`.
- bash-tools.exec-run.ts reads the field through the same typeof narrowing the
  third call site already used; `params` is loosely typed, so a raw read is
  `unknown`.
- Test fixtures updated. A vitest `waitFor({ timeout })` option in
  background-abort.test.ts is NOT an exec param and was left alone.

`run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json` now reports zero errors
in changed files; the 5 remaining `rejectSymlinks` errors are pre-existing on
main. 148 tests pass across the affected suites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* docs: synchronize the exec config table with timeoutSeconds

Addresses the P2 finding. The tools.exec.timeoutSeconds row still described the
per-call override as `timeout` and `timeout: 0`, which no longer exist. The
only remaining bare `timeout` reference in this page is the deliberate
contrast with the process tool's millisecond field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix: migrate the QA lifecycle caller and the shared ExecToolArgs contract

Two findings from the re-review of 6e8c3560e7e, both real.

[P1] test/e2e/qa-lab/runtime/openclaw-exec-process-lifecycle.e2e.test.ts:174
still passed `timeout: 0.05`. After the removal that field is accepted and
ignored, so the call silently used the default timeout and could not produce
the asserted 50 ms `overall-timeout` result. Migrated.

[P2] ExecToolArgs in bash-tools.exec-request-preparation.ts:33 still declared
`timeout?: number` while the runtime reads only `timeoutSeconds`, offering
request-preparation users an accepted-but-inert argument. Renamed.

Why the typecheck missed both: ExecToolArgs is
`Record<string, unknown> & {...}`, and that index signature defeats excess
property checking, so a stale member produces no error at any call site. The
e2e file also sits outside the core test tsconfig I had been running locally.

Left alone deliberately: `processTool.execute({ action: "poll", timeout })` is
milliseconds and correct, and `createBashTool` in sessions/tools/bash.ts is a
separate tool surface with its own timeout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix(test): update the node exec tool property assertion to timeoutSeconds

src/gateway/tool-resolution.exclude.test.ts:413 asserts the exact property list
of the node-forced exec tool and still expected "timeout". Renamed to match
nodeExecSchema.

Found by checks-node-compact-large-5; my local core-test tsconfig does not cover
src/gateway, so it did not surface there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix: accept the legacy exec timeout as an undocumented alias

Removing the field from the schema does not reject it at runtime: the exec
schema accepts unknown properties, so a call built against an older schema was
accepted and then silently ignored, taking the default deadline instead of the
one it asked for.

Route every read through resolveExecTimeoutSeconds, which prefers the canonical
timeoutSeconds and falls back to a numeric timeout. The alias stays out of
execSchema, nodeExecSchema and ExecToolArgs, so no new caller can adopt it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* docs: update the background-process guide to timeoutSeconds

The exec parameter table and the inheritance note still taught `timeout` and
`timeout: 0`, which kept the retired spelling publicly discoverable and
contradicted the exec guide.

`poll`'s own millisecond `timeout` is left alone; it is a different tool and a
different unit, which is the collision this rename removes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh

* fix(exec): reject removed timeout field

* chore: regenerate plugin SDK API baseline

---------

Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-10 20:02:53 -07:00