Commit Graph

20 Commits

Author SHA1 Message Date
Peter Steinberger 38a5390e2e fix(agents): never restore a retired worktree via a generated name (#122393)
sessions.create reset-in-place persists the retired worktree's removedAt
before the RPC resolves, but a successor worktree create that rolled the
same random crustacean slug (1/420) hit nameIsUnavailable's owner-match
bypass and silently restored the retired registry record, clearing
removedAt and reviving the old branch (flaked
server.sessions.create.test.ts reset-in-place).

Generated names (title slugs and random slugs) now treat the caller's
removed records as collisions and take a numeric suffix; explicit
worktree names keep their documented reuse/restore semantics.
2026-08-11 20:02:26 -07:00
Peter Steinberger 32894a3ba5 feat: start sessions from registered projects (#121465)
* feat(gateway): add project registry sessions

* fix(gateway): authorize project branch discovery

* test(gateway): reuse worktree test imports

* fix(protocol): register projects schema owner

* fix(gateway): gate project paths by write scope

* feat(control-ui): register projects from folder browser

* refactor(gateway): extract session diff baseline preparation

* fix(gateway): honor admin scope in project listings
2026-08-10 17:56:12 -07:00
Peter Steinberger 59492c51a5 feat(agents): rename spawn_task tool to suggest_task (#121694)
* feat(agents): rename spawn_task tool to suggest_task

Models across vendors read the name spawn_task as 'starts background
work' and refuse to call it when the user has not asked to start
anything (Claude Sonnet 5, verbatim: 'spawn_task (which actively starts
background work)... Since you didn't ask me to start work, I'm flagging
this as a follow-up instead'). The tool only records a suggestion card,
so the name defeated the feature.

Rename the model-facing tool to suggest_task everywhere (catalog,
descriptors, presets, display config, OpenClawKit tool-display, docs)
and migrate persisted operator state via doctor: config tool policies
and cron SQLite toolsAllow entries that reference spawn_task are
rewritten to suggest_task. No runtime alias remains; runtime exposes
only the canonical name.

Live clean-room matrix on a dev gateway (virgin session per cell):
suggested-task-card phrasing now yields exactly one card on
anthropic/claude-sonnet-5, claude-haiku-4-5, openai/gpt-5.6-sol,
gpt-5.6-luna, and google/gemini-3.1-pro; pre-rename the same bait
produced prose-only follow-ups.

* chore(doctor): keep legacy tool-name predicate module-local

isLegacyTaskSuggestionToolName has no external consumers; the exported
form tripped the knip unused-export gate (deadcode:exports).

* fix(doctor): keep tool-name migration out of plugin-owned config

The rename migration traversed the whole raw config, so any object
carrying a toolsAllow key was rewritten - including opaque
plugins.entries.*.config, which core must not mutate. Scope the apply
pass to the typed core roots (tools, agents, channels, gateway) and pin
plugin config preservation in the test. Plugin-owned tool lists migrate
via the owning plugin's doctor contract instead.

* fix(ci): heal main deadcode and stale Swift protocol mirror

Unrelated main breakage blocking this PR's merge gate, healed here per
landing policy:

- #121653 left dead exports in src/skills/workshop: make
  removeSkillCollectionDirectory and SkillCollectionRestoreResult
  module-local; register collection-review.ts under the documented knip
  ignoreIssues test-only-export convention (production runs it via the
  scheduled maintenance loop).
- #121673 added ApprovalResolveParams.reviewer without regenerating the
  Swift mirror; regenerate GatewayModels.swift (additive only).

* fix(ci): satisfy generic approval-runtime request signature in resolver test

Third main-heal carried by this PR: #121673 (9935ca3b30) left the
scoped-request mock's concrete inferred type unassignable to the generic
GatewayNativeApprovalRuntime request signature, breaking check-test-types
on main. Cast the mock at the runtime literal; assertions keep the Mock.

* chore(ci): drop approval-test cast superseded by main's typed mock rewrite

* chore(ci): drop duplicate knip entry superseded by main's heal
2026-08-10 16:41:39 -07:00
Peter Steinberger 3621c4b511 feat: browse and start sessions inside agent workspaces (#121417)
* feat(gateway): allow workspace-contained browsing

* fix(ui): preserve canonical workspace selections
2026-08-09 23:57:46 -07:00
Peter Steinberger 73bdb4b924 feat(agents): record run-end worktree cleanup outcome; prove Workboard dirty retention (#120434)
* feat(agents): record run-end worktree cleanup outcome

Persist removed, retained, and failed run-end cleanup outcomes on managed worktree records. Operators and QA can inspect the durable fact through worktrees.list and openclaw worktrees list --json.

Release note: Managed worktree run-end cleanup now records why a checkout was removed or retained in worktree list JSON.

* test(qa): prove dirty worktree retention outcome

* chore(protocol): regenerate swift gateway models

* fix(agents): harden worktree cleanup recovery

Register run_end_cleanup_json as a lazy compatible column so same-version v6 index repair and read-only doctor migration can recover databases created before the column existed.

Type removal contention at the registry boundary; unexpected claim failures now best-effort record a bounded failed outcome and rethrow the original error.

* fix(ci): clear repo-wide lint debt blocking merge gates

The red-main landing rule requires this PR to repair repository-wide merge-gate debt instead of bypassing it. Apply the current lint contracts mechanically and split turn-transition coverage into a concept-named sibling with per-file-safe test state.

Exact line delta: +676/-574 (net +102) across 44 test/support files.

* fix(ci): preserve cached health refresh proof

Require the public refresh call to exist before accepting that sensitive fields were omitted, so the boundary proof cannot pass on a missing call.

* fix(ci): correct test typing left by the lint sweep

Literal-widened totalTokensVersion fixtures, a WebSocket RawData overload
mismatch, and the protocol schema document cast broke check-test-types
after the repo-wide lint repair. Aligns the fixtures with SessionEntry,
narrows Buffer handling per RawData, and keeps the JSON-shaped undefined
omission under structuredClone.

* test(agents): reuse upstream resource-loader test support

The session-loop split and #120463's helper extraction landed the same
createResourceLoader/createCompactionHandlers twice; the rebase kept both,
orphaning main's agent-session-loop-resource-loader.test-support.ts and
failing the dead-code gate. Import the upstream helpers and delete the
duplicates.

* fix(agents): reject finalized rows at the worktree removal claim

Address the accepted ClawSweeper late-claim finding by rereading and rejecting missing or finalized worktree rows inside the synchronous removal-claim transaction.

Preserve the authoritative cleanup invariant: finalized contenders record nothing, while retained-busy is written only while the row remains live.

* refactor(agents): reuse registry update for busy outcomes

Keep the live-row conditional write in the canonical registry update path so the finalized-claim repair stays below the registry max-lines ratchet without weakening the authoritative-outcome invariant.

* test(agents): drop session test duplicates after rebase

Keep current main as the canonical owner of next-turn lifecycle coverage and correctness test support after replaying the older lint-debt split.

* fix(agents): guard post-abort cleanup outcomes against finalization

After abortWorktreeRemoval releases a stale remover's claim, its retained or
failed write raced a finalizing remover and could overwrite the authoritative
removed-lossless fact. Route every retained/failed write through the live-row
condition; only the finalizing remover's own removed-lossless write stays
unconditional.

* fix(agents): persist the removal outcome atomically with finalization

A delayed removed-lossless write after remove() finalized could race a
restore plus newer cleanup and overwrite the newer operator-visible fact.
The run-end outcome now rides remove()'s finalization update; every other
cleanup write stays live-row conditional, so no post-finalize write path
remains.

* test(qa): restore strict cached-health contract assertions

The lint sweep's Boolean() coercions let truthy non-booleans satisfy the
wire-typed cached-meta contract. Assert the literal boolean for unknown-typed
fields and use nullish-coalesced strict equivalents for boolean chains.

* fix(agents): clear the stale cleanup outcome when restoring a worktree

A restored checkout begins a new lifecycle; leaving the removed-lossless
fact on the live row showed operators a stale result until the next
cleanup. Restore clears the recorded outcome and the regression asserts
the cleared state before the next cleanup records fresh truth.

* fix(agents): scope stale cleanup outcomes to their observed lifecycle

A stale remover's retained/failed write raced a concurrent remove-plus-
restore: the revived row is live again, so the live-row condition alone
could stamp a prior-lifecycle outcome. Condition those writes on the
activity stamp the remover observed; restore bumps lastActiveAt, making
any prior-lifecycle write a no-op.

* fix(agents): advance the restore activity stamp within one millisecond

Stale cleanup writes fence on the activity stamp they observed; a restore
completing in the same millisecond could revive the row with an identical
stamp and let the fence match. Restore now always advances past the
stored value, and the ABA regression pins the clock to prove the
same-millisecond case.
2026-08-08 20:32:11 -07:00
ClawSweeper f67bdbbfe0 feat: add readable managed worktree names (#114488)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-27 05:03:43 -07:00
Peter Steinberger 6c3caa38ab fix(ui): allow direct sessions in non-Git folders (#112433)
* fix(ui): allow direct sessions in non-Git folders

* test: align New Session fixtures with place picker

* fix(ui): preserve worktree intent on Git probe failures

* fix(types): keep worktree status alias internal
2026-07-21 19:36:53 -07:00
Peter Steinberger 783a5d21cf refactor(config): purge numeric tuning knobs behind built-in defaults (#111382) 2026-07-19 07:35:45 -07:00
Yuval Dinodia af05f64f01 fix(agents): preserve worktree-local ignored state during lossless cleanup (#108401)
* fix(worktrees): preserve provisioned cleanup state

Co-authored-by: yetval <yetvald@gmail.com>

* refactor(worktrees): break provisioned registry cycle

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 07:06:25 -07:00
Peter Steinberger 0ecbbe2382 fix(workboard): keep dispatch inside caller workspace authority (#107087)
* fix(workboard): inherit caller workspace authority

* docs(plugin-sdk): refresh API baseline

* fix(workboard): pin restricted worker authority

* fix(workboard): avoid host git for restricted dispatch

* refactor(worktrees): extract owner matching

* refactor(workboard): reuse sdk workspace helpers

* fix(workboard): narrow cleanup owner

* refactor(workboard): clarify claim value handling

* fix(workboard): inherit caller workspace authority

* test(worktrees): use managed temp directories

* chore: keep release notes in PR body

* fix(workboard): keep internal authority helpers private
2026-07-13 21:36:26 -07:00
Peter Steinberger df3fe6a4f2 feat(worktrees): configurable cleanup limits (max count, max total size) with Settings UI (#106224)
Config surface worktrees.cleanup.{maxCount,maxTotalSizeGb} (0/unset disables),
enforced by hourly gc, worktrees.gc RPC, and openclaw worktrees gc: least
recently active session/Workboard worktrees are snapshotted and removed until
count and total size fit. Manual worktrees, live locks, run leases, and active
session owners are never limit-evicted. Settings -> Worktrees gains a Cleanup
section with debounced, quota-aware stepper controls. Closes #106213.
2026-07-13 06:00:33 -07:00
Peter Steinberger ae2c6117d3 feat(ui): full-page New session screen with gateway folder browser (#104238)
* feat(gateway): add admin-only fs.listDir host directory listing

* feat(ui): replace new-session dialog with full-page /new screen and folder browser

* fix(ui): drop unnecessary template literal in new-session page

* refactor(ui): rename request token locals for review-bundle hygiene

* fix(ui): preserve typed draft on agent hydration and clear stale folder listings

* fix(ui): gate new-session submit on agent hydration and keep live folder edits

* test(ui): use exact textbox selector in new-session e2e

* test(ui): deep-link new-session e2e so agents.list override supplies workspace

* chore(i18n): translate new-session strings and refresh native inventory

* chore(i18n): reconcile locale metadata after rebase
2026-07-11 00:10:48 -07:00
Peter Steinberger 8f8934163b feat(ui): worktrees page owners and creation, structured snapshot failures, preserved-checkout cleanup (#103526)
* feat(ui): worktrees page owners and creation, structured snapshot failures, preserved-checkout cleanup

- Worktrees settings page gains an Owner column (manual/Workboard/session
  with a link into the owning chat) and a New worktree form with repo
  default, optional name, and a worktrees.branches-backed base picker
- non-forced worktrees.remove snapshot failures are a structured
  { removed: false, snapshotError } result (typed WorktreeSnapshotError in
  the service) instead of error-string sniffing in the UI
- deleting a session whose dirty/unpushed checkout was preserved now
  reports worktreePreserved through the session capability and offers an
  explicit force removal instead of silently orphaning the checkout

Part of #103431

* fix(ui): report preserved checkouts from batch session deletes

deleteMany now aggregates worktreePreserved results, and the Sessions
page surfaces one notice pointing at Settings -> Worktrees instead of
silently orphaning dirty checkouts during bulk deletion.

Part of #103431

* fix(ui): reconcile generated locale artifacts after rebase
2026-07-10 19:16:36 +01:00
Peter Steinberger fd2a2411b9 feat(ui): sessions sidebar redesign with agent sections, smart groups, and draft sessions (#103498)
* feat(ui): sessions sidebar redesign with agent sections, smart groups, and draft sessions

- agents become collapsible top-level sidebar sections; expanding an agent
  browses its sessions without navigating, replacing the hidden scope select
- built-in smart groups: one section per channel (rows keep their chat
  titles), a Work section for worktree/exec-node sessions with a
  repo/branch/node subtitle, custom groups, and Chats
- session names never show raw keys or peer ids; DM fallbacks shorten ids,
  dashboard sessions read 'New session', unnamed work sessions read as
  their checkout
- one + opens the new-session draft dialog: agent, exec host (paired
  system.run nodes), folder, worktree toggle with base-branch picker
  (worktrees.branches) and optional name; the first message creates the
  session and starts the run in one sessions.create call
- custom group catalog/order moves to the gateway (sessions.groups.*) with
  a one-time localStorage migration; rename/delete update members
  server-side instead of client-side paging

Part of #103431

* fix(ui): resolve dragged sessions across browsed agent sections

Dropping a row dragged out of a non-active agent section now finds the
session in the per-agent row cache instead of only the active scope, so
the category patch is applied instead of silently doing nothing.

Part of #103431

* fix(ui): propagate group catalog changes to open clients

sessions.groups.put/rename/delete now always broadcast a groups-change
event, and the session capability reloads the gateway-owned catalog when
one arrives, so another browser's group create/reorder/rename/delete no
longer leaves this client on a stale snapshot for the rest of the
connection.

Part of #103431

* docs: restore new session dialog section after rebase

* fix(ui): translate new sidebar/session strings and refresh docs map

Real locale translations for the new-session dialog and sidebar keys
(the fallback gate requires zero recorded English fallbacks), plus the
regenerated docs map for the new Control UI section.

Part of #103431

* fix(ui): repair locale metadata after rebase conflict resolution

* fix(ui): merge mainline locale keys with the sidebar redesign strings

* fix(ui): refresh raw-copy baseline for mainline tool-card strings

* fix(ui): reconcile generated locale artifacts after rebase
2026-07-10 18:54:44 +01:00
Peter Steinberger 48b0f4edf5 feat(gateway): session worktree targeting, branch listing, and server-side session groups (#103432)
* feat(gateway): session worktree targeting, branch listing, and group catalog

- sessions.create accepts worktreeBaseRef/worktreeName (write scope) and
  execNode (admin); worktree binding persists on the session entry as
  worktree { id, branch, repoRoot } and projects onto session rows with
  execNode so UIs can show checkout/branch/node state
- new worktrees.branches RPC lists local/remote branches (no fetch) for
  base-ref pickers; worktrees.remove now reports snapshotError
- sessions.delete reports preserved dirty checkouts as worktreePreserved
- gateway-owned session group catalog in the shared state DB with
  sessions.groups.list/put/rename/delete; rename/delete update member
  categories server-side without bumping recency; sessions.patch absorbs
  ad-hoc categories into the catalog
- group session display names prefer the human chat title (subject or
  space #channel) over stored compact tokens

Part of #103431

* fix(gateway): route group category updates through the session accessor and regen protocol models

- bulk member-category rename/clear uses applySessionEntryReplacements
  instead of legacy updateSessionStore call sites (session accessor
  boundary guard)
- regenerate Swift GatewayModels for the worktrees.branches schemas,
  snapshotError, and new sessions.create params

Part of #103431

* fix(gateway): resolvable remote branch refs and workspace-scoped branch listing

- worktrees.branches returns remote-only branches remote-qualified
  (origin/feature-a) so every advertised name works as a worktree base ref
- write-scoped worktrees.branches callers are limited to configured agent
  workspaces; other host paths require operator.admin, matching the
  sessions.create cwd bar

Part of #103431

* fix(gateway): guard worktree name reuse by owner and env-scope group transactions

- managedWorktrees.create rejects a caller-chosen name whose live or
  restorable record belongs to a different owner, so write-scoped
  sessions.create cannot bind a session into another session's or a
  manual checkout
- session group catalog writes run their SQLite transaction on the same
  env-scoped handle as their statements, keeping OPENCLAW_STATE_DIR
  overrides atomic and away from the default state DB

Part of #103431
2026-07-10 15:51:35 +01:00
Peter Steinberger 5f9e0e20d4 fix: scope task suggestions to supported surfaces (#102743)
* fix: make task suggestions surface-aware

* fix: honor task suggestion action scopes

* fix: align task surface compatibility types

* fix: isolate task suggestions across surfaces

* style: satisfy task selector lint

* chore: defer task suggestion release note

* test: dedupe gateway capability fixture

* test: cover combined gateway capabilities
2026-07-09 12:59:50 +01:00
Peter Steinberger 5533d979d4 feat: add follow-up task suggestions (#102422)
* feat(tools): add follow-up task suggestions

* chore: leave changelog to release flow

* fix(tools): add task suggestion display metadata

* fix(tools): update task suggestion display snapshot

* docs: format task suggestion tool table

* test(gateway): expect compaction worktree workspace

* test(gateway): preserve configured compaction workspace

* fix(ui): translate task suggestions
2026-07-09 06:30:01 +01:00
Peter Steinberger 161e66f7bd fix(ui): move Worktrees page from the sidebar into Settings navigation (#100995) 2026-07-06 17:16:39 +01:00
Peter Steinberger b4b1984fe7 feat(gateway): start a new chat session in a managed worktree (#100788)
Adds an additive worktree flag to sessions.create so any new chat can run in an
isolated managed worktree of the agent's git workspace, with the branch checked
out and .worktreeinclude provisioning applied. The session's spawnedCwd points at
the matching subdirectory inside the worktree so chat runs, CLI, and the file
browser execute there. agents.list gains workspaceGit (workspace or an ancestor
is a git checkout) to gate the affordance; web sidebar, iOS, and Android expose a
New-Chat-in-worktree action. Uses the method's operator.write scope, but the
.openclaw/worktree-setup.sh step runs only for operator.admin callers since it
executes repo code. Deleting the session, or leaving via a plain New Chat, clears
the cwd and lossless-removes the worktree; idle GC treats recent session activity
as worktree activity so an active session's checkout is never swept.

Live-verified end-to-end on a real gateway; follow-up to #100535 (issue #100534).
2026-07-06 10:23:41 +01:00
Peter Steinberger 0acd851a3b feat(agents): add managed git worktree lifecycle (create/provision/snapshot/restore/GC) (#100535)
Centralized managed worktrees under <state-dir>/worktrees/<repo-fingerprint>/<name>
with branch-per-task (openclaw/<name>), .worktreeinclude provisioning, an optional
.openclaw/worktree-setup.sh repo hook, and a SQLite registry in the shared state DB.
Removal always snapshots the tree (untracked included, gitignored excluded) to
refs/openclaw/snapshots/<id>; restore rebuilds the branch at the original commit with
the snapshot content as uncommitted state. Lossless run-end cleanup, 7-day idle GC for
run-owned worktrees (manual exempt), orphan reconciliation, 30-day snapshot retention.
Surfaces: worktrees.* gateway RPC (operator.admin mutations), openclaw worktrees CLI,
Control UI page, plugin-SDK facade + Workboard kind:"worktree" materialization.

E2E-verified on Testbox: full create->work->remove->restore->gc lifecycle.
2026-07-06 05:24:58 +01:00