Repository navigation
feat: add worktree execution context #360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 23 commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
937f9d2
feat(session): add execution_context column to session table
Astro-Han b18e96c
feat(session): synthesize executionContext for new and legacy sessions
Astro-Han ebda2c8
feat(session): persist execution_context through projectors
Astro-Han 5155b3b
feat(session): add Session.updateExecutionContext helper
Astro-Han 1bc52b3
feat(session): accept legacy string path on inbound messages
Astro-Han 32ccf95
feat(session): route message path through executionContext
Astro-Han 78ad32f
feat(instance): add Instance.activate for session-scoped binding
Astro-Han 557c9a9
feat(session): add state-machine guard helpers
Astro-Han 4bed20a
feat(tool): add EnterWorktree and ExitWorktree agent tools
Astro-Han 4d5b59c
feat(subagent): inherit parent activeDirectory at dispatch
Astro-Han fabf418
feat(sdk): expose Session.executionContext to clients
Astro-Han 62dc483
feat(app): titlebar worktree badge + Settings → Worktrees panel
Astro-Han fd58cf9
fix(worktree): use PawWork managed worktree convention
Astro-Han 6691c98
fix(worktree): preserve registry source semantics
Astro-Han 9e053b8
fix(session): root execution context at project owner
Astro-Han 71d115d
fix(worktree): block transitions during subagents
Astro-Han 12db7ff
fix(worktree): guard managed worktree ignore entry
Astro-Han ec189a7
fix(worktree): align settings with registry entries
Astro-Han c64db08
fix(app): mount worktree badge inside directory context
Astro-Han 51d510a
fix(app): use global context for worktree settings
Astro-Han 82c4c2c
fix(app): simplify worktree titlebar badge
Astro-Han a0250cc
fix(app): unify worktree title typography
Astro-Han 452a089
fix(app): preserve composer scroll dock height
Astro-Han 0cd3a8e
fix(app): address worktree settings review
Astro-Han ab288d9
fix(worktree): close execution context review gaps
Astro-Han 993fcfa
fix(sdk): require active worktree branch
Astro-Han 387bdd0
fix(opencode): harden worktree execution context
Astro-Han b041374
fix(app): refine worktree UI boundaries
Astro-Han be21701
chore(i18n): unify zh worktree term to 工作树
Astro-Han 84111b8
fix(app): hide worktrees from workspace chip
Astro-Han 213300c
refactor(app): redesign Settings → Worktrees with editorial layout
Astro-Han 65311e1
refactor(app): unify titlebar folder and worktree chips
Astro-Han 8c2a306
chore(i18n): polish worktree term and voice
Astro-Han c4de7bb
fix(ui): clarify enter/exit-worktree subtitles in chat
Astro-Han 5fed954
feat(opencode): expose previous worktree on exit metadata
Astro-Han 814de6b
feat(ui): show exited branch in worktree exit subtitle
Astro-Han 96c210d
fix(app): tighten titlebar new-session button
Astro-Han a3935ef
fix(app): close settings overlay when route changes
Astro-Han 1063607
fix(ui): restore titlebar icon hover, drop persistent bg
Astro-Han 5d4d7ea
fix(ui): clarify worktree tool labels
Astro-Han a754462
fix(app): polish worktree settings list
Astro-Han d05d8a4
fix(app): tighten session timeline spacing
Astro-Han 055c413
chore(app): remove unused worktree column labels
Astro-Han 753eb80
fix: address worktree review feedback
Astro-Han 35679f7
fix: address follow-up worktree review
Astro-Han f001381
fix: keep worktree context synchronized
Astro-Han 11cfcb3
test: cover worktree badge branch label
Astro-Han 21dea07
refactor: split settings worktree row
Astro-Han 530aff5
fix: address worktree review followups
Astro-Han 68074b7
fix: address worktree review issues
Astro-Han f6ba832
refactor: tighten tool metadata access
Astro-Han c69cc77
fix: tolerate partial worktree list failures
Astro-Han 61e2427
fix: harden worktree gitignore status check
Astro-Han 5f881fc
fix: tighten worktree settings helpers
Astro-Han 37520f5
fix: address worktree review regressions
Astro-Han 0d7ac5b
fix: guard deleted gitignore before worktree setup
Astro-Han e2c593d
fix: address latest worktree review comments
Astro-Han 06716ee
fix: harden worktree execution context
Astro-Han f054a86
fix: preserve partial worktree context
Astro-Han 22944d3
fix: require session row project fallback
Astro-Han 66a4d01
fix: harden session execution context recovery
Astro-Han 2126653
fix: canonicalize persisted execution context
Astro-Han 131872e
fix: address worktree review regressions
Astro-Han 8595bc9
fix: address worktree path review
Astro-Han 574b6a5
fix: preserve worktree path entry metadata
Astro-Han File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| import { type Component, createResource, createSignal, For, Show } from "solid-js" | ||
| import { Button } from "@opencode-ai/ui/button" | ||
| import { Icon } from "@opencode-ai/ui/icon" | ||
| import { showToast } from "@opencode-ai/ui/toast" | ||
| import { useGlobalSDK } from "@/context/global-sdk" | ||
| import { useGlobalSync } from "@/context/global-sync" | ||
| import { useLanguage } from "@/context/language" | ||
| import { SettingsList } from "./settings-list" | ||
|
|
||
| type WorktreeInfo = { | ||
| name: string | ||
| branch: string | ||
| directory: string | ||
| ownerDirectory: string | ||
| source?: "created" | "existing" | ||
| } | ||
|
|
||
| function basename(p: string): string { | ||
| const trimmed = p.replace(/[/\\]+$/, "") | ||
| const last = trimmed.split(/[/\\]/).pop() | ||
| return last || p | ||
| } | ||
|
|
||
| /** | ||
| * Settings → Worktrees panel. | ||
| * | ||
| * Lists every PawWork-tracked worktree directory for the current project (via | ||
| * `client.worktree.list()`); offers a per-row delete with two-step confirm. Delete is disabled | ||
| * when an open session in this app instance has the worktree as its activeDirectory; the user | ||
| * must call ExitWorktree from that session first. | ||
| */ | ||
| export const SettingsWorktrees: Component = () => { | ||
| const language = useLanguage() | ||
| const sdk = useGlobalSDK() | ||
| const sync = useGlobalSync() | ||
|
|
||
| const projectRoots = () => | ||
| sync.data.project.filter((project) => project.vcs === "git").map((project) => project.worktree).filter(Boolean) | ||
|
|
||
| const [data, { refetch }] = createResource( | ||
| () => projectRoots().join("\0"), | ||
| async () => { | ||
| const rows = await Promise.all( | ||
| projectRoots().map(async (ownerDirectory) => { | ||
| const res = await sdk.client.worktree.list({ directory: ownerDirectory }) | ||
| return (res.data ?? []).map((worktree) => ({ ...worktree, ownerDirectory }) as WorktreeInfo) | ||
| }), | ||
| ) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| const byDirectory = new Map<string, WorktreeInfo>() | ||
| for (const row of rows.flat()) byDirectory.set(row.directory, row) | ||
| return Array.from(byDirectory.values()) | ||
|
Astro-Han marked this conversation as resolved.
Resolved
|
||
| }, | ||
| ) | ||
|
|
||
| // Sessions whose activeDirectory points at a worktree path block its deletion. | ||
| const boundSessions = (): Map<string, string> => { | ||
| const map = new Map<string, string>() | ||
| const directories = new Set<string>() | ||
| for (const project of sync.data.project) { | ||
| directories.add(project.worktree) | ||
| for (const sandbox of project.sandboxes ?? []) directories.add(sandbox) | ||
|
Astro-Han marked this conversation as resolved.
Resolved
|
||
| } | ||
| for (const worktree of data() ?? []) { | ||
| directories.add(worktree.ownerDirectory) | ||
| directories.add(worktree.directory) | ||
| } | ||
|
|
||
| for (const directory of directories) { | ||
| const [store] = sync.child(directory, { bootstrap: false }) | ||
| for (const s of store.session ?? []) { | ||
| const exec = s.executionContext | ||
| if (!exec) continue | ||
| if (exec.activeDirectory && exec.activeDirectory !== exec.ownerDirectory) { | ||
| map.set(exec.activeDirectory, s.title) | ||
| } | ||
| } | ||
| } | ||
| return map | ||
| } | ||
|
|
||
| const [confirming, setConfirming] = createSignal<string | undefined>(undefined) | ||
| const [deleting, setDeleting] = createSignal<string | undefined>(undefined) | ||
|
|
||
| const handleDelete = async (directory: string) => { | ||
| setDeleting(directory) | ||
| try { | ||
| const ownerDirectory = data()?.find((worktree) => worktree.directory === directory)?.ownerDirectory ?? directory | ||
| const res = await sdk.client.worktree.remove({ directory: ownerDirectory, worktreeRemoveInput: { directory } }) | ||
| if (res.error) throw new Error(JSON.stringify(res.error)) | ||
| setConfirming(undefined) | ||
| void refetch() | ||
|
Astro-Han marked this conversation as resolved.
Resolved
|
||
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : String(err) | ||
| showToast({ | ||
| title: language.t("settings.worktrees.deleteFailed", { message }), | ||
| }) | ||
| } finally { | ||
| setDeleting(undefined) | ||
| } | ||
|
Astro-Han marked this conversation as resolved.
Resolved
|
||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| return ( | ||
| <SettingsList> | ||
| <div class="flex flex-col gap-3 py-4"> | ||
| <div class="flex flex-col gap-1"> | ||
| <h2 class="text-16-medium text-text-strong">{language.t("settings.worktrees.title")}</h2> | ||
| <p class="text-13-regular text-text-weak">{language.t("settings.worktrees.description")}</p> | ||
| </div> | ||
|
|
||
| <Show | ||
| when={!data.loading} | ||
| fallback={ | ||
| <div class="text-13-regular text-text-weak py-6 text-center">{language.t("common.loading")}</div> | ||
| } | ||
| > | ||
| <Show | ||
| when={(data() ?? []).length > 0} | ||
| fallback={ | ||
| <div class="text-13-regular text-text-weak py-6 text-center"> | ||
| {language.t("settings.worktrees.empty")} | ||
| </div> | ||
| } | ||
| > | ||
| <ul | ||
| class="flex flex-col divide-y divide-line-base rounded border border-line-base" | ||
| data-component="settings-worktrees-list" | ||
| > | ||
| <For each={data() ?? []}> | ||
| {(worktree) => { | ||
| const directory = () => worktree.directory | ||
| const name = () => worktree.name || basename(worktree.directory) | ||
| const branch = () => worktree.branch || "-" | ||
| const blocker = () => boundSessions().get(worktree.directory) | ||
| const blocked = () => !!blocker() | ||
| const isConfirming = () => confirming() === worktree.directory | ||
| const isDeleting = () => deleting() === worktree.directory | ||
|
|
||
| return ( | ||
| <li class="flex items-center gap-3 px-3 py-2.5"> | ||
| <Icon name="worktree" size="small" class="text-text-weak shrink-0" /> | ||
| <div class="flex flex-col min-w-0 flex-1"> | ||
| <span class="text-13-medium text-text-strong truncate" title={directory()}> | ||
| {name()} | ||
| </span> | ||
| <span class="text-13-regular text-text-weak truncate" title={directory()}> | ||
| {branch()} · {worktree.source ?? "created"} · {directory()} | ||
| </span> | ||
| </div> | ||
| <Show | ||
| when={isConfirming()} | ||
| fallback={ | ||
| <Button | ||
| variant="ghost" | ||
| size="small" | ||
| disabled={blocked() || isDeleting()} | ||
| title={ | ||
| blocked() | ||
| ? language.t("settings.worktrees.deleteDisabled.tooltip", { | ||
| session: blocker() ?? "", | ||
| }) | ||
| : undefined | ||
| } | ||
| onClick={() => setConfirming(directory())} | ||
| > | ||
| {language.t("settings.worktrees.delete")} | ||
| </Button> | ||
| } | ||
| > | ||
| <div class="flex items-center gap-1"> | ||
| <span class="text-13-regular text-text-weak"> | ||
| {language.t("settings.worktrees.confirmDelete.body", { name: name() })} | ||
| </span> | ||
| <Button | ||
| variant="ghost" | ||
| size="small" | ||
| disabled={isDeleting()} | ||
| onClick={() => setConfirming(undefined)} | ||
| > | ||
| {language.t("settings.worktrees.confirmDelete.cancelLabel")} | ||
| </Button> | ||
| <Button | ||
| variant="primary" | ||
| size="small" | ||
| disabled={isDeleting()} | ||
| onClick={() => handleDelete(directory())} | ||
| > | ||
| {language.t("settings.worktrees.confirmDelete.confirmLabel")} | ||
| </Button> | ||
| </div> | ||
| </Show> | ||
| </li> | ||
| ) | ||
| }} | ||
| </For> | ||
| </ul> | ||
| </Show> | ||
| </Show> | ||
| </div> | ||
| </SettingsList> | ||
| ) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.