dsh-worktree-switch 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/host/paths.ts","../src/host/guard.ts","../src/host/git.ts","../src/host/naming.ts","../src/host/handlers.ts","../src/host/routes.ts","../src/host/scope.ts","../src/index.ts"],"sourcesContent":["/**\n * Path predicates the host half shares between the verbs, the scope hint, and\n * the working-directory watcher.\n * @module dsh-worktree-switch/host/paths\n */\n\nimport { isAbsolute, relative } from 'node:path'\n\n/**\n * Whether one absolute directory is the path itself or an ancestor of it.\n * @param directory - candidate ancestor.\n * @param path - absolute path to test.\n * @returns true when `path` is `directory` or lives below it.\n */\nexport function contains(directory: string, path: string): boolean {\n const inside = relative(directory, path)\n return inside === '' || (!inside.startsWith('..') && !isAbsolute(inside))\n}\n","/**\n * The source-checkout guard: while a session runs in a worktree copy, shell\n * commands that would run Git against the project checkout are refused before\n * dispatch.\n *\n * The prompt context (`worktree-switch:scope`) explains where the workspace\n * is, but it cannot stop a model that still reaches for the checkout it can\n * see — and the sandbox deliberately grants writes there. This guard is the\n * enforcement half that needs no harness change: `tools/pre-execute` can deny\n * one call, and the model reads the reason as that call's error result.\n *\n * The pattern is deliberately narrow. Only a shell call that mentions `git`\n * is considered, and only when it names the project checkout either as an\n * explicit `workdir` or as a literal absolute path in the command text.\n * `cd ..`-style relative escapes and shell variables are not resolved; the\n * guard is a net, not the sandbox boundary.\n * @module dsh-worktree-switch/host/guard\n */\n\nimport { isAbsolute, resolve } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { PreToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools'\nimport type { SessionScope } from '../types.ts'\nimport { contains } from './paths.ts'\nimport type { SessionScopeRegistry } from './scope.ts'\n\n/** Tool names whose `command` argument is executed by a shell. */\nconst SHELL_TOOLS = new Set(['bash'])\n\n/** The arguments the guard reads from one shell call. */\ninterface ShellArguments {\n /** Command text as the model wrote it. */\n readonly command: string\n /** Explicit working directory, when the call carried one. */\n readonly workdir?: string\n}\n\n/**\n * Read a shell call's command and explicit directory, if this is one.\n * @param exec - the pending tool call.\n * @returns the fields the guard needs, or null for every other tool.\n */\nfunction shellArguments(exec: ToolExecution): ShellArguments | null {\n if (!SHELL_TOOLS.has(exec.name)) return null\n if (exec.arguments === null || typeof exec.arguments !== 'object') return null\n const args = exec.arguments as Record<string, unknown>\n const command = args['command']\n if (typeof command !== 'string' || command.trim() === '') return null\n const workdir = args['workdir']\n const explicit = typeof workdir === 'string' && workdir.trim() !== '' ? workdir : undefined\n return explicit === undefined ? { command } : { command, workdir: explicit }\n}\n\n/**\n * The model-facing denial for one shell command.\n * @param command - shell command text as the model wrote it.\n * @param workdir - explicit working directory, when the call carried one.\n * @param cwd - the session's committed working directory.\n * @param scope - the copy the session runs in.\n * @returns the denial reason, or null when the call may run.\n */\nexport function sourceCheckoutDenial(\n command: string,\n workdir: string | undefined,\n cwd: string,\n scope: SessionScope,\n): string | null {\n // Non-Git commands are out of scope: this guard is about the checkout's Git\n // state, not about every write the sandbox already allows.\n if (!/\\bgit\\b/.test(command)) return null\n const checkout = scope.worktree.path\n const project = scope.repositoryRoot\n if (workdir !== undefined) {\n const target = isAbsolute(workdir) ? workdir : resolve(cwd, workdir)\n if (contains(project, target) && !contains(checkout, target)) return denial(scope)\n }\n // The copy lives inside the project, so mention of the copy path must not\n // count as naming the checkout; strip it before looking for the project.\n if (command.split(checkout).join('').includes(project)) return denial(scope)\n return null\n}\n\n/** The one explanation every denial carries, including the way to get main. */\nfunction denial(scope: SessionScope): string {\n return `Denied: this session runs in the worktree copy ${JSON.stringify(scope.worktree.path)}, so Git commands targeting the project checkout ${JSON.stringify(scope.repositoryRoot)} are blocked. Run Git in the current working directory instead; to bring main's latest into this copy, use \\`git fetch origin main\\` and then \\`git merge origin/main\\` (or \\`git rebase origin/main\\`).`\n}\n\n/**\n * Refuse shell calls that run Git against the project checkout while the\n * session is inside a copy.\n *\n * The registry is the same one the prompt context reads, so the guard is\n * active exactly while the model has been told it is in a copy.\n * @param ctx - host context carrying `workingDirectory` and the tool runtime.\n * @param registry - sessions' confirmed copies.\n */\nexport function installSourceCheckoutGuard(ctx: Context, registry: SessionScopeRegistry): void {\n ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {\n const agent = exec.agent\n if (agent === undefined) return next()\n const scope = registry.get(String(agent.session.id))\n if (scope === null) return next()\n const args = shellArguments(exec)\n if (args === null) return next()\n const denial = sourceCheckoutDenial(args.command, args.workdir, ctx.workingDirectory.get(agent.session), scope)\n return denial === null ? next() : { kind: 'deny', reason: denial }\n })\n}\n","/**\n * Unconfined Git calls behind the plugin's user-initiated verbs.\n *\n * Creation goes through `ctx.worktrees` (the harness service), which runs its\n * own Git under the calling session's sandbox policy. The list, status, and\n * discard verbs here belong to a person clicking in the UI rather than to a\n * model turn: they inspect and delete the copies the user can see, from a path\n * the same UI just listed. They therefore spawn Git directly, the same way the\n * installed `dsh-worktree-space` plugin does, instead of borrowing a session's\n * policy for an operation no session asked for.\n * @module dsh-worktree-switch/host/git\n */\n\nimport { realpath } from 'node:fs/promises'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-subprocess'\n\n/** Whole-command deadline for one Git call. */\nexport const GIT_TIMEOUT_MS = 20_000\n\n/** Per-stream capture cap; worktree listings and status lines are small. */\nconst MAX_OUTPUT_BYTES = 1_048_576\n\n/**\n * Clear inherited repository selectors and config so the command reads the\n * checkout named by `cwd`, never one inherited from the Host's environment.\n * One entry per key: `undefined` removes the variable for the child.\n */\nconst GIT_ENV: NodeJS.ProcessEnv = Object.fromEntries([\n 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',\n 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_COUNT',\n 'GIT_CEILING_DIRECTORIES', 'GIT_PREFIX', 'GIT_SHALLOW_FILE', 'GIT_GRAFT_FILE',\n 'GIT_REPLACE_REF_BASE', 'GIT_ATTR_SOURCE',\n].map(key => [key, undefined]))\n\n/** One bounded Git invocation's captured result. */\nexport interface GitResult {\n /** Captured stdout. */\n readonly stdout: string\n /** Captured stderr. */\n readonly stderr: string\n /** Process exit status. */\n readonly exitCode: number\n}\n\n/** A Git invocation that exited with a status the caller did not allow. */\nexport class GitFailure extends Error {\n /**\n * @param args - arguments after the executable, for diagnostics.\n * @param result - the captured outcome.\n */\n constructor(readonly args: readonly string[], readonly result: GitResult) {\n const detail = result.stderr.trim() === '' ? result.stdout.trim() : result.stderr.trim()\n super(`git ${args.join(' ')} failed (exit ${result.exitCode})${detail === '' ? '' : `: ${detail}`}`)\n this.name = 'GitFailure'\n }\n}\n\n/** Options for {@link runGit}. */\nexport interface GitOptions {\n /** Caller cancellation, combined with the command deadline. */\n readonly signal?: AbortSignal | undefined\n /** Exit statuses that are not failures; defaults to `[0]`. */\n readonly allowedExitCodes?: readonly number[] | undefined\n /** Override for {@link GIT_TIMEOUT_MS}. */\n readonly timeoutMs?: number | undefined\n}\n\n/**\n * Run one Git command without a shell and await its termination.\n * @param ctx - Host context carrying the subprocess provider.\n * @param cwd - absolute directory the command runs in.\n * @param args - arguments after the executable.\n * @param options - cancellation, accepted exit statuses, and deadline.\n * @returns captured stdout, stderr, and exit status.\n * @throws {GitFailure} when the exit status is not allowed or the command was killed.\n */\nexport async function runGit(\n ctx: Context,\n cwd: string,\n args: readonly string[],\n options: GitOptions = {},\n): Promise<GitResult> {\n const deadline = AbortSignal.timeout(options.timeoutMs ?? GIT_TIMEOUT_MS)\n const signal = options.signal === undefined ? deadline : AbortSignal.any([options.signal, deadline])\n const allowed = options.allowedExitCodes ?? [0]\n const program = await ctx.subprocess.resolveExecutable('git', undefined, signal)\n const argv = [program, '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', ...args]\n const child = ctx.subprocess.spawn({\n argv,\n cwd,\n env: {\n ...GIT_ENV,\n GIT_TERMINAL_PROMPT: '0',\n GIT_LFS_SKIP_SMUDGE: '1',\n GIT_NO_LAZY_FETCH: '1',\n },\n signal,\n graceMs: 1_500,\n stdio: {\n stdin: 'ignore',\n stdout: { maxBytes: MAX_OUTPUT_BYTES },\n stderr: { maxBytes: MAX_OUTPUT_BYTES },\n },\n })\n try {\n const outcome = await child.done\n const stdout = child.collected.stdout?.readFrom(0).text ?? ''\n const stderr = child.collected.stderr?.readFrom(0).text ?? ''\n const result: GitResult = { stdout, stderr, exitCode: outcome.exitCode ?? -1 }\n if (outcome.exitCode === null || !allowed.includes(outcome.exitCode)) throw new GitFailure(args, result)\n return result\n } finally {\n child.terminate()\n await child.waitForExit()\n }\n}\n\n/**\n * Resolve the repository root that contains `directory`.\n * @param ctx - Host context carrying the subprocess provider.\n * @param directory - absolute directory inside (or at) the checkout.\n * @param signal - caller cancellation.\n * @returns the canonical top-level directory Git reports.\n * @throws {GitFailure} when the directory is not inside a Git working tree.\n */\nexport async function repositoryRoot(ctx: Context, directory: string, signal?: AbortSignal): Promise<string> {\n const result = await runGit(ctx, directory, ['rev-parse', '--show-toplevel'], {\n signal,\n allowedExitCodes: [0, 128],\n })\n if (result.exitCode !== 0) throw new GitFailure(['rev-parse', '--show-toplevel'], result)\n return result.stdout.replace(/\\r?\\n$/, '')\n}\n\n/**\n * Resolve symlinks in a path that exists, falling back to the path as given.\n * @param path - absolute path.\n * @returns the canonical spelling when the path exists.\n */\nexport async function canonicalPath(path: string): Promise<string> {\n try {\n return await realpath(path)\n } catch {\n return path\n }\n}\n\n/** One checkout reported by `git worktree list --porcelain`. */\nexport interface WorktreeRecord {\n /** Absolute checkout directory. */\n readonly path: string\n /** Branch the checkout has checked out, when it has one. */\n readonly branch?: string | undefined\n}\n\n/**\n * Parse `git worktree list --porcelain` output into its records.\n * @param stdout - the command's stdout.\n * @returns one record per reported checkout, in Git's order.\n */\nexport function parseWorktreeList(stdout: string): WorktreeRecord[] {\n const records: WorktreeRecord[] = []\n let path: string | undefined\n let branch: string | undefined\n const flush = (): void => {\n if (path === undefined) return\n records.push(branch === undefined ? { path } : { path, branch })\n path = undefined\n branch = undefined\n }\n for (const line of stdout.split('\\n')) {\n if (line === '') {\n flush()\n continue\n }\n if (line.startsWith('worktree ')) path = line.slice('worktree '.length)\n else if (line.startsWith('branch refs/heads/')) branch = line.slice('branch refs/heads/'.length)\n }\n flush()\n return records\n}\n\n/**\n * List the worktree checkouts of one repository.\n * @param ctx - Host context carrying the subprocess provider.\n * @param root - repository root the command runs in.\n * @param signal - caller cancellation.\n * @returns every checkout Git reports for the repository.\n */\nexport async function listWorktreeRecords(\n ctx: Context,\n root: string,\n signal?: AbortSignal,\n): Promise<WorktreeRecord[]> {\n const result = await runGit(ctx, root, ['worktree', 'list', '--porcelain'], { signal })\n return parseWorktreeList(result.stdout)\n}\n\n/**\n * Count the changed and untracked files of one checkout.\n * @param ctx - Host context carrying the subprocess provider.\n * @param directory - checkout directory to inspect.\n * @param signal - caller cancellation.\n * @returns the number of `git status --porcelain` entries.\n */\nexport async function changedFileCount(\n ctx: Context,\n directory: string,\n signal?: AbortSignal,\n): Promise<number> {\n const result = await runGit(ctx, directory, ['status', '--porcelain', '--untracked-files=normal'], {\n signal,\n })\n return result.stdout.split('\\n').filter(line => line.trim() !== '').length\n}\n","/**\n * Names for the copy a session gets: lowercase words and a short suffix, so a\n * branch, a directory, a `git branch` row and this plugin's UI all read the\n * same. No prefix: the pool path already says where it lives.\n * @module dsh-worktree-switch/host/naming\n */\n\n/** First word of a generated name. */\nconst ADJECTIVES = [\n 'amber', 'brisk', 'calm', 'clever', 'crisp', 'eager', 'faint', 'fresh',\n 'gentle', 'happy', 'hollow', 'kind', 'lively', 'mellow', 'nimble', 'polite',\n 'quiet', 'rapid', 'ready', 'silent', 'soft', 'solid', 'steady', 'sunny',\n 'swift', 'tidy', 'vivid', 'warm', 'wise', 'young',\n] as const\n\n/** Second word of a generated name. */\nconst NOUNS = [\n 'anchor', 'atlas', 'badger', 'birch', 'brook', 'cedar', 'comet', 'coral',\n 'delta', 'ember', 'fern', 'field', 'finch', 'glade', 'harbor', 'heron',\n 'island', 'lark', 'ledge', 'maple', 'meadow', 'otter', 'pebble', 'pine',\n 'quartz', 'river', 'shore', 'stone', 'trail', 'willow',\n] as const\n\n/**\n * Generate one candidate worktree name.\n * @param random - uniform source in `[0, 1)`; injectable for deterministic tests.\n * @returns a lowercase, hyphen-joined branch-safe name such as `quiet-harbor-3fa9`.\n */\nexport function worktreeName(random: () => number = Math.random): string {\n const pick = (values: readonly string[]): string => {\n const index = Math.min(values.length - 1, Math.floor(random() * values.length))\n return values[index] ?? values[0] ?? 'copy'\n }\n const suffix = Math.min(0xffff, Math.floor(random() * 0x10000)).toString(16).padStart(4, '0')\n return `${pick(ADJECTIVES)}-${pick(NOUNS)}-${suffix}`\n}\n\n/** A pool entry name is exactly one path segment, as `git worktree` names it. */\nconst NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/\n\n/**\n * Validate a worktree name received from the browser before it reaches Git.\n * @param value - candidate name.\n * @returns true for one plain path segment that Git also accepts as a branch.\n */\nexport function isWorktreeName(value: string): boolean {\n return NAME_PATTERN.test(value) && !value.endsWith('.lock')\n}\n","/**\n * The plugin's host Remote face: the five verbs the browser half can call,\n * expressed against a live session and the checkouts on disk.\n *\n * This module is the seam the host tests drive directly. Everything below it —\n * Git, the worktree service, the sandbox policy it applies, and session\n * working-directory publication — belongs to the harness and is exercised by\n * its own tests.\n * @module dsh-worktree-switch/host/handlers\n */\n\nimport { lstat, stat } from 'node:fs/promises'\nimport { basename, dirname, isAbsolute, relative, resolve } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { SessionId } from '@deepseek-ai/dsh-session'\nimport type {} from '@deepseek-ai/dsh-api-session-controller'\nimport type {} from '@deepseek-ai/dsh-experimental-worktree'\nimport type {} from '@deepseek-ai/dsh-working-directory'\nimport type {\n DiscardValue,\n ListValue,\n PrepareValue,\n ReleaseValue,\n SessionScope,\n StatusValue,\n WorktreeItem,\n WorktreeRef,\n} from '../types.ts'\nimport {\n canonicalPath,\n changedFileCount,\n GitFailure,\n listWorktreeRecords,\n repositoryRoot,\n runGit,\n} from './git.ts'\nimport { isWorktreeName, worktreeName } from './naming.ts'\nimport { contains } from './paths.ts'\n\n/** Attempts at a fresh name before a creation failure becomes an error. */\nconst CREATE_ATTEMPTS = 3\n\nexport type {\n DiscardValue,\n ListValue,\n PrepareValue,\n ReleaseValue,\n SessionScope,\n StatusValue,\n WorktreeItem,\n WorktreeRef,\n} from '../types.ts'\n\n/** A failed verb, carrying the stable code the browser half switches on. */\nexport class WorktreeToggleError extends Error {\n /**\n * @param code - stable failure code.\n * @param message - human-readable explanation.\n * @param details - extra machine-readable facts.\n */\n constructor(\n readonly code: string,\n message: string,\n readonly details: Readonly<Record<string, unknown>> = {},\n ) {\n super(message)\n this.name = 'WorktreeToggleError'\n }\n}\n\n/** Row config the verbs read. */\nexport interface WorktreeToggleOptions {\n /** Default for a project nothing has remembered yet. */\n readonly defaultEnabled: boolean\n /**\n * Report what each verb learned about the session's copy, so the model-facing\n * scope hint can follow it without a second Git walk.\n * @param sessionId - wire session identity.\n * @param scope - the confirmed copy, or null when the session is not in one.\n */\n readonly onScope?: (sessionId: string, scope: SessionScope | null) => void\n}\n\n/** The host face the route layer exposes over the Connection fetch bridge. */\nexport interface WorktreeToggle {\n /**\n * Report whether the session's project can be isolated, and in what.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the status payload; `supported: false` is a normal answer.\n */\n status(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<StatusValue>\n /**\n * Put the session into a fresh copy, creating it when necessary.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the created or already-present copy.\n * @throws {WorktreeToggleError} when the project cannot host a copy or Git refused.\n */\n prepare(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<PrepareValue>\n /**\n * Put the session back in its project directory, undoing a copy the switch\n * created for it when it never became the conversation's workspace.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the directory the session now runs in, the copy removed, and the\n * copy left on disk when deleting it was not safe.\n * @throws {WorktreeToggleError} when the project cannot be resolved or the\n * session could not be moved back.\n */\n release(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<ReleaseValue>\n /**\n * List the copies under one project's pool.\n * @param request - any directory inside the project.\n * @param signal - caller cancellation.\n * @returns the pool contents, or `supported: false` for a non-repository path.\n */\n list(request: { readonly path: string }, signal?: AbortSignal): Promise<ListValue>\n /**\n * Delete one copy and its branch.\n * @param request - a directory inside the project, and the copy's name.\n * @param signal - caller cancellation.\n * @returns the removed copy's identity.\n * @throws {WorktreeToggleError} when the copy is missing, dirty, or Git refused.\n */\n discard(\n request: { readonly path: string; readonly name: string },\n signal?: AbortSignal,\n ): Promise<DiscardValue>\n}\n\n/** Normalize a browser-supplied path before any filesystem work. */\nfunction absolutePath(value: string): string {\n const trimmed = value.trim()\n if (trimmed === '') throw new WorktreeToggleError('bad-request', 'a project path is required')\n return resolve(trimmed)\n}\n\n/**\n * The copy a session already runs in.\n *\n * A linked worktree is recognised from the checkout itself — its `.git` is a\n * file pointing back at the source repository, not a directory — rather than\n * from any configured pool path. The pool belongs to the harness worktree\n * service, so this plugin never needs a second copy of that setting: moving the\n * pool in the service's row config keeps every verb working.\n * @param current - the session's committed working directory.\n * @param repositoryRoot - canonical root of the project's repository.\n * @returns the copy's identity, or null when the directory is not one.\n */\nasync function worktreeAt(current: string, repositoryRoot: string): Promise<WorktreeRef | null> {\n const path = await canonicalPath(current)\n if (path === repositoryRoot) return null\n const inside = relative(repositoryRoot, path)\n if (inside === '' || inside.startsWith('..') || isAbsolute(inside)) return null\n const name = basename(path)\n if (!isWorktreeName(name)) return null\n try {\n if (!(await lstat(`${path}/.git`)).isFile()) return null\n } catch {\n return null\n }\n return { name, path, branch: name }\n}\n\n/**\n * Resolve the canonical root of the repository that contains a directory.\n * Git reports the spelling of the path it was given; the worktree service\n * publishes canonical paths, so both halves must agree on one spelling.\n * @param ctx - host context carrying `subprocess`.\n * @param directory - absolute directory inside the project.\n * @param signal - caller cancellation.\n * @returns the canonical repository root.\n */\nasync function repositoryOf(ctx: Context, directory: string, signal?: AbortSignal): Promise<string> {\n return canonicalPath(await repositoryRoot(ctx, directory, signal))\n}\n\n/**\n * Re-derive the copy one session runs in from its committed directory.\n *\n * A copy's root is the only linked checkout whose name matches the pool\n * convention, but a session may have changed into a directory *inside* the copy,\n * so ancestors are probed from the committed directory up to the project\n * anchor.\n * @param ctx - host context carrying `workingDirectory` and `subprocess`.\n * @param agent - live session owner.\n * @param signal - caller cancellation.\n * @returns the copy and project root, or null when the session is not in one.\n */\nexport async function detectSessionScope(\n ctx: Context,\n agent: Agent,\n signal?: AbortSignal,\n): Promise<SessionScope | null> {\n const current = ctx.workingDirectory.get(agent.session)\n const anchor = agent.session.header.cwd ?? current\n if (current === anchor || !contains(anchor, current)) return null\n let root: string\n try {\n root = await repositoryOf(ctx, anchor, signal)\n } catch {\n return null\n }\n signal?.throwIfAborted()\n let probe = current\n for (;;) {\n const copy = await worktreeAt(probe, root)\n if (copy !== null) return { worktree: copy, repositoryRoot: root }\n const parent = dirname(probe)\n if (probe === root || probe === anchor || parent === probe) return null\n probe = parent\n }\n}\n\n/**\n * Build the host face.\n * @param ctx - Host context with `agents`, `worktrees`, `workingDirectory`, and `subprocess`.\n * @param options - resolved row config.\n * @returns the five verbs.\n */\nexport function createWorktreeToggle(ctx: Context, options: WorktreeToggleOptions): WorktreeToggle {\n /** One in-flight preparation per session, so a double mount cannot create twice. */\n const pending = new Map<string, Promise<PrepareValue>>()\n /**\n * Copies this host instance created for a session, by session id. Turning the\n * switch off may only delete what the switch itself just made: a copy that\n * existed before this instance started is left for the panel's explicit\n * discard, because it may carry work the switch never saw.\n */\n const createdBySession = new Map<string, WorktreeRef>()\n\n const sessionOf = async (sessionId: string, signal?: AbortSignal): Promise<Agent> => {\n signal?.throwIfAborted()\n if (sessionId === '') throw new WorktreeToggleError('bad-request', 'a session id is required')\n const controller = ctx.get('sessionController')\n if (controller !== undefined) {\n const resolved = await controller.resolveAgent(sessionId as SessionId)\n if ('error' in resolved) {\n throw new WorktreeToggleError(resolved.error.code, resolved.error.message, {\n sessionId,\n })\n }\n return resolved.agent\n }\n const agent = ctx.agents.get(sessionId as SessionId)\n if (agent === undefined) {\n throw new WorktreeToggleError('session-not-found', `no live agent for session ${sessionId}`, {\n sessionId,\n })\n }\n return agent\n }\n\n const status = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<StatusValue> => {\n const agent = await sessionOf(request.sessionId, signal)\n // The immutable creation directory is the project anchor; the committed\n // working directory may already be a copy this session entered.\n const startDirectory = agent.session.header.cwd ?? ctx.workingDirectory.get(agent.session)\n let root: string\n try {\n root = await repositoryOf(ctx, startDirectory, signal)\n } catch (error) {\n if (error instanceof GitFailure) {\n return { supported: false, reason: 'not-a-git-repository', defaultEnabled: options.defaultEnabled }\n }\n throw error\n }\n const current = ctx.workingDirectory.get(agent.session)\n signal?.throwIfAborted()\n const sourceDirty = await changedFileCount(ctx, root, signal)\n const copy = await worktreeAt(current, root)\n options.onScope?.(request.sessionId, copy === null ? null : { worktree: copy, repositoryRoot: root })\n return {\n supported: true,\n repositoryRoot: root,\n sessionWorktree: copy,\n sourceDirty,\n defaultEnabled: options.defaultEnabled,\n }\n }\n\n const prepareOnce = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<PrepareValue> => {\n const agent = await sessionOf(request.sessionId, signal)\n const current = await status(request, signal)\n if (!current.supported) {\n throw new WorktreeToggleError(\n 'not-a-git-repository',\n 'the session project is not inside a Git working tree',\n )\n }\n if (current.sessionWorktree !== null) {\n options.onScope?.(request.sessionId, {\n worktree: current.sessionWorktree,\n repositoryRoot: current.repositoryRoot,\n })\n return {\n state: 'existing',\n worktree: current.sessionWorktree,\n repositoryRoot: current.repositoryRoot,\n sourceDirty: current.sourceDirty,\n }\n }\n // Status anchors on the immutable creation directory, while the worktree\n // service creates relative to the session's CURRENT directory. Refuse to\n // create when the two name different repositories: a copy in the wrong\n // project would look like success and be worse than no copy.\n const working = ctx.workingDirectory.get(agent.session)\n const workingProject = await repositoryOf(ctx, working, signal)\n if (workingProject !== current.repositoryRoot) {\n throw new WorktreeToggleError(\n 'session-moved',\n `this session now works in ${workingProject}, not in its project ${current.repositoryRoot}`,\n { repositoryRoot: current.repositoryRoot, workingProject },\n )\n }\n let lastFailure: unknown\n for (let attempt = 0; attempt < CREATE_ATTEMPTS; attempt += 1) {\n signal?.throwIfAborted()\n const name = worktreeName()\n try {\n const created = await ctx.worktrees.create(agent, { name, from: 'HEAD' }, signal)\n const worktree: WorktreeRef = { name: created.branch, path: created.path, branch: created.branch }\n createdBySession.set(request.sessionId, worktree)\n options.onScope?.(request.sessionId, { worktree, repositoryRoot: created.repositoryRoot })\n return {\n state: 'created',\n worktree,\n repositoryRoot: created.repositoryRoot,\n sourceDirty: current.sourceDirty,\n }\n } catch (error) {\n lastFailure = error\n const message = error instanceof Error ? error.message : String(error)\n // A name collision is the one failure a fresh name can fix. Everything\n // else (no commits yet, branch or path exists, Git missing) is reported\n // as-is: the caller must not be told \"created\" after a silent retry.\n if (!/already exists|worktree branch already exists|worktree path already exists/i.test(message)) {\n throw new WorktreeToggleError('worktree-create-failed', message, { name })\n }\n }\n }\n throw new WorktreeToggleError(\n 'worktree-create-failed',\n `could not allocate a free worktree name after ${CREATE_ATTEMPTS} attempts`,\n { cause: lastFailure instanceof Error ? lastFailure.message : String(lastFailure) },\n )\n }\n\n const prepare = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<PrepareValue> => {\n const running = pending.get(request.sessionId)\n if (running !== undefined) return running\n const operation = prepareOnce(request, signal)\n pending.set(request.sessionId, operation)\n try {\n return await operation\n } finally {\n if (pending.get(request.sessionId) === operation) pending.delete(request.sessionId)\n }\n }\n\n const list = async (\n request: { readonly path: string },\n signal?: AbortSignal,\n ): Promise<ListValue> => {\n const directory = absolutePath(request.path)\n let root: string\n try {\n root = await repositoryOf(ctx, directory, signal)\n } catch (error) {\n if (error instanceof GitFailure) return { supported: false, reason: 'not-a-git-repository' }\n throw error\n }\n const records = await listWorktreeRecords(ctx, root, signal)\n const items: WorktreeItem[] = []\n for (const record of records) {\n signal?.throwIfAborted()\n const path = await canonicalPath(record.path)\n // The repository's own checkout is the source, never a copy.\n if (path === root) continue\n const name = basename(path)\n if (!isWorktreeName(name)) continue\n const branch = record.branch ?? name\n let createdAt = 0\n let prunable = true\n try {\n const info = await stat(path)\n createdAt = Math.trunc(info.birthtimeMs > 0 ? info.birthtimeMs : info.mtimeMs)\n prunable = false\n } catch {\n // The checkout directory is gone; Git still lists it until pruned.\n }\n const changedFiles = prunable ? 0 : await changedFileCount(ctx, path, signal)\n items.push({ name, path, branch, createdAt, changedFiles, prunable })\n }\n items.sort((left, right) => right.createdAt - left.createdAt || left.name.localeCompare(right.name))\n return { supported: true, repositoryRoot: root, items }\n }\n\n /**\n * Delete one clean copy and its branch. Both the panel's discard and the\n * switch's own undo go through here, so the dirty guard has one home.\n * @param root - canonical repository root.\n * @param name - copy name; already validated by the caller.\n * @param signal - caller cancellation.\n * @returns the removed copy's identity.\n */\n const removeCopy = async (root: string, name: string, signal?: AbortSignal): Promise<DiscardValue> => {\n const listed = await list({ path: root }, signal)\n if (!listed.supported) {\n throw new WorktreeToggleError('not-a-git-repository', 'the project is not inside a Git working tree')\n }\n const item = listed.items.find(entry => entry.name === name)\n if (item === undefined) {\n throw new WorktreeToggleError(\n 'worktree-not-found',\n `no worktree named ${name} in ${root}`,\n { name, repositoryRoot: root },\n )\n }\n if (item.changedFiles > 0) {\n throw new WorktreeToggleError(\n 'worktree-dirty',\n `${item.name} has ${item.changedFiles} uncommitted change(s); commit or discard them first`,\n { name: item.name, changedFiles: item.changedFiles },\n )\n }\n if (item.prunable) {\n await runGit(ctx, root, ['worktree', 'prune'], { signal })\n } else {\n const removed = await runGit(ctx, root, ['worktree', 'remove', item.path], {\n signal,\n allowedExitCodes: [0, 128],\n })\n if (removed.exitCode !== 0) {\n throw new WorktreeToggleError(\n 'worktree-remove-failed',\n new GitFailure(['worktree', 'remove', item.path], removed).message,\n { name: item.name, path: item.path },\n )\n }\n }\n const deleted = await runGit(ctx, root, ['branch', '-D', '--', item.branch], {\n signal,\n allowedExitCodes: [0, 1],\n })\n // Status 1 with \"not found\" is the branch already being gone (removed by\n // hand between the read and the click); that is not a failed discard.\n const branchGone = deleted.exitCode === 1 && /not found/i.test(deleted.stderr)\n if (deleted.exitCode !== 0 && !branchGone) {\n throw new WorktreeToggleError(\n 'worktree-remove-failed',\n new GitFailure(['branch', '-D', item.branch], deleted).message,\n { name: item.name, branch: item.branch },\n )\n }\n for (const [sessionId, copy] of createdBySession) {\n if (copy.path === item.path) createdBySession.delete(sessionId)\n }\n return { removed: true, name: item.name, branch: item.branch, path: item.path }\n }\n\n const discard = async (\n request: { readonly path: string; readonly name: string },\n signal?: AbortSignal,\n ): Promise<DiscardValue> => {\n const directory = absolutePath(request.path)\n if (!isWorktreeName(request.name)) {\n throw new WorktreeToggleError('worktree-name-invalid', `not a worktree name: ${request.name}`)\n }\n const root = await repositoryOf(ctx, directory, signal)\n return await removeCopy(root, request.name, signal)\n }\n\n const release = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<ReleaseValue> => {\n // A create may still be in flight for this session: undo after it settles,\n // so a switch turned off mid-creation cannot race the copy into existence.\n const running = pending.get(request.sessionId)\n if (running !== undefined) {\n try {\n await running\n } catch {\n // Creation failed and already moved nothing; there is nothing to undo.\n }\n }\n signal?.throwIfAborted()\n const agent = await sessionOf(request.sessionId, signal)\n const current = ctx.workingDirectory.get(agent.session)\n // The immutable creation directory anchors the project, exactly as status\n // does: the committed directory may already be a copy this session entered.\n const anchor = agent.session.header.cwd ?? current\n let root: string\n try {\n root = await repositoryOf(ctx, anchor, signal)\n } catch (error) {\n if (error instanceof GitFailure) {\n throw new WorktreeToggleError(\n 'not-a-git-repository',\n 'the session project is not inside a Git working tree',\n )\n }\n throw error\n }\n const listed = await list({ path: anchor }, signal)\n if (!listed.supported) return { directory: current, removed: null, kept: null }\n const path = await canonicalPath(current)\n const item = listed.items.find(entry => contains(entry.path, path))\n if (item === undefined) {\n options.onScope?.(request.sessionId, null)\n return { directory: current, removed: null, kept: null }\n }\n const copy: WorktreeRef = { name: item.name, path: item.path, branch: item.branch }\n signal?.throwIfAborted()\n let directory: string\n try {\n directory = await ctx.workingDirectory.set(agent, anchor, signal)\n } catch (error) {\n throw new WorktreeToggleError(\n 'release-failed',\n `could not move the session back to ${anchor}: ${error instanceof Error ? error.message : String(error)}`,\n { name: copy.name, path: copy.path },\n )\n }\n options.onScope?.(request.sessionId, null)\n const mine = createdBySession.get(request.sessionId)\n if (mine === undefined || mine.path !== copy.path) {\n // Not the switch's own copy: leave it, the panel can discard it by hand.\n return { directory, removed: null, kept: copy }\n }\n try {\n const removed = await removeCopy(root, copy.name, signal)\n return {\n directory,\n removed: { name: removed.name, path: removed.path, branch: removed.branch },\n kept: null,\n }\n } catch (error) {\n if (signal?.aborted === true) throw error\n // Dirty or refused by Git: the session is still back in the project, so\n // the switch keeps its promise; the copy stays for the panel to handle.\n return { directory, removed: null, kept: copy }\n }\n }\n\n return { status, prepare, release, list, discard }\n}\n","/**\n * The Connection fetch bridge: five exact POST routes on the shared `/api`\n * channel, one per verb, speaking the `client-request` / `server-response`\n * envelope the browser's `connection.rpc.call` already implements.\n *\n * Exact routes are the only shape an out-of-tree plugin can claim: `/api` is\n * the one logical channel the API Gateway owns, so `rpc.handle` refuses it and\n * a second `rpc.intercept` throws. Every route below answers owner peers only,\n * which is what the local Web GUI is.\n * @module dsh-worktree-switch/host/routes\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ConnectionRpcResult } from '@deepseek-ai/dsh-client-connection'\nimport type {} from '@deepseek-ai/dsh-client-connection'\nimport type { DiscardValue, ListValue, PrepareValue, ReleaseValue, StatusValue, WorktreeToggle } from './handlers.ts'\nimport { WorktreeToggleError } from './handlers.ts'\n\n/** Absolute prefix every route below lives under. */\nexport const API_PREFIX = '/api/dsh-worktree-switch'\n\n/** Plugin id; the wire method prefix and the package name are the same string. */\nexport const PLUGIN_ID = 'dsh-worktree-switch'\n\n/** One route per verb. */\nexport const ENDPOINTS = ['status', 'prepare', 'release', 'list', 'discard'] as const\n\n/** A verb this route table serves. */\nexport type Endpoint = (typeof ENDPOINTS)[number]\n\n/** A decoded client request. */\ninterface ClientRequest {\n readonly rpcId: string\n readonly method: string\n readonly payload: Record<string, unknown>\n}\n\n/** A payload object with the named string field, or the failure to send back. */\nfunction requiredString(\n payload: Record<string, unknown>,\n field: string,\n): { readonly value: string } | { readonly failure: WorktreeToggleError } {\n const value = payload[field]\n if (typeof value !== 'string' || value.trim() === '') {\n return {\n failure: new WorktreeToggleError('bad-request', `payload.${field} must be a non-empty string`),\n }\n }\n return { value }\n}\n\n/**\n * Decode the browser's `client-request` envelope.\n * @param body - parsed JSON body.\n * @returns the request, or null when the envelope is malformed.\n */\nexport function readClientRequest(body: unknown): ClientRequest | null {\n if (body === null || typeof body !== 'object' || Array.isArray(body)) return null\n const record = body as Record<string, unknown>\n if (record.type !== 'client-request') return null\n const { rpcId, method } = record\n if (typeof rpcId !== 'string' || rpcId === '') return null\n if (typeof method !== 'string' || method === '') return null\n const payload = record.payload\n return {\n rpcId,\n method,\n payload: payload !== null && typeof payload === 'object' && !Array.isArray(payload)\n ? payload as Record<string, unknown>\n : {},\n }\n}\n\n/** A successful result. */\nfunction ok<T>(value: T): ConnectionRpcResult<T> {\n return { ok: true, value }\n}\n\n/** A failed result carrying the stable code. */\nfunction fail(code: string, message: string, details: Record<string, unknown> = {}): ConnectionRpcResult<never> {\n return { ok: false, error: { code, message, details } }\n}\n\n/**\n * Dispatch one decoded verb.\n * @param toggle - the host face.\n * @param endpoint - the verb.\n * @param payload - its decoded payload.\n * @param signal - request cancellation.\n * @returns the wire result.\n */\nexport async function dispatch(\n toggle: WorktreeToggle,\n endpoint: Endpoint,\n payload: Record<string, unknown>,\n signal: AbortSignal,\n): Promise<ConnectionRpcResult<StatusValue | PrepareValue | ReleaseValue | ListValue | DiscardValue>> {\n try {\n if (endpoint === 'status') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.status({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'prepare') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.prepare({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'release') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.release({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'list') {\n const path = requiredString(payload, 'path')\n if ('failure' in path) throw path.failure\n return ok(await toggle.list({ path: path.value }, signal))\n }\n const path = requiredString(payload, 'path')\n if ('failure' in path) throw path.failure\n const name = requiredString(payload, 'name')\n if ('failure' in name) throw name.failure\n return ok(await toggle.discard({ path: path.value, name: name.value }, signal))\n } catch (error) {\n if (error instanceof WorktreeToggleError) return fail(error.code, error.message, { ...error.details })\n return fail('internal', error instanceof Error ? error.message : String(error))\n }\n}\n\n/**\n * Register one exact route per verb on the shared `/api` channel.\n * @param ctx - Host context carrying `connection`.\n * @param toggle - the host face the routes call.\n */\nexport function registerWorktreeRoutes(ctx: Context, toggle: WorktreeToggle): void {\n for (const endpoint of ENDPOINTS) {\n ctx.effect(() => ctx.connection.fetch.register({\n path: `${API_PREFIX}/${endpoint}`,\n methods: ['POST'],\n requestBody: 'buffered',\n async fetch(request) {\n const contentType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n return new Response('content type must be application/json', { status: 415 })\n }\n let body: unknown\n try {\n body = await request.json()\n } catch {\n return new Response('body is not JSON', { status: 400 })\n }\n const message = readClientRequest(body)\n if (message === null) return new Response('invalid client-request message', { status: 400 })\n if (message.method !== `${PLUGIN_ID}/${endpoint}`) {\n return Response.json({\n type: 'server-response',\n rpcId: message.rpcId,\n result: fail('bad-request', 'RPC method does not match endpoint'),\n })\n }\n const result = await dispatch(toggle, endpoint, message.payload, request.signal)\n return Response.json({ type: 'server-response', rpcId: message.rpcId, result })\n },\n }), `${PLUGIN_ID} ${endpoint} route`)\n }\n}\n","/**\n * The copy a session is confirmed to run in, plus the one line the model reads\n * about it.\n *\n * The working-directory service already tells the model *where* it is. This\n * module answers the question that line does not: which checkout is the\n * session's workspace and which one is merely the project it was copied from.\n * The registry is fed by three sources — the switch verbs, `agent/created`\n * (resume), and `working-directory/change` (any `cd`) — so the line is never\n * stale, and it drops out entirely once the session leaves the copy.\n * @module dsh-worktree-switch/host/scope\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type {} from '@deepseek-ai/dsh-system-prompt'\nimport type { SessionScope } from '../types.ts'\nimport { contains } from './paths.ts'\n\n/** Sessions whose confirmed copy the prompt context reads. */\nexport class SessionScopeRegistry {\n private readonly scopes = new Map<string, SessionScope>()\n\n /**\n * Read one session's confirmed scope, without any filesystem work.\n * @param sessionId - wire session identity.\n * @returns the known copy, or null.\n */\n get(sessionId: string): SessionScope | null {\n return this.scopes.get(sessionId) ?? null\n }\n\n /**\n * Record that a session now runs in a copy.\n * @param sessionId - wire session identity.\n * @param scope - the confirmed copy and its project root.\n */\n remember(sessionId: string, scope: SessionScope): void {\n this.scopes.set(sessionId, scope)\n }\n\n /**\n * Drop a session's scope; a session with no entry gets no hint.\n * @param sessionId - wire session identity.\n */\n forget(sessionId: string): void {\n this.scopes.delete(sessionId)\n }\n\n /** Drop every entry; the plugin dropping its context drops its hints with it. */\n clear(): void {\n this.scopes.clear()\n }\n}\n\n/**\n * The model-facing line describing a worktree session's workspace.\n * @param scope - the session's confirmed copy.\n * @returns one sentence pair, JSON-quoted so paths survive the prompt.\n */\nexport function scopeHint(scope: SessionScope): string {\n return `This session is a linked Git worktree checkout at ${JSON.stringify(scope.worktree.path)} of the project ${JSON.stringify(scope.repositoryRoot)}. Run Git commands and file edits from the current working directory; the project checkout is the source, not this session's workspace.`\n}\n\n/** What the installer needs from the host half that owns Git. */\nexport interface WorktreeScopeWiring {\n /** Shared registry the prompt context reads. */\n readonly registry: SessionScopeRegistry\n /**\n * Re-derive one session's copy from its committed directory.\n * @param agent - live session owner.\n * @param signal - cancellation.\n * @returns the confirmed copy, or null when the session is not in one.\n */\n detect(agent: Agent, signal?: AbortSignal): Promise<SessionScope | null>\n}\n\n/**\n * Register the worktree scope as dynamic prompt context and keep it fresh.\n *\n * The context is `required`, so it survives compositions that suppress\n * optional runtime context, and it renders from the registry synchronously —\n * the expensive Git detection happens on session creation and directory\n * changes, never at request time.\n * @param ctx - host context carrying `systemPrompt`, `agents`, and `workingDirectory`.\n * @param wiring - registry plus the Git-backed detector.\n */\nexport function installWorktreeScope(ctx: Context, wiring: WorktreeScopeWiring): void {\n const { registry, detect } = wiring\n\n ctx.inject(['systemPrompt'], (scoped: Context) => {\n scoped.systemPrompt.context({\n name: 'worktree-switch:scope',\n order: scoped.systemPrompt.getContextOrder('WORKING_DIRECTORY') + 5,\n interpolate: false,\n required: true,\n text: (assembly) => {\n const agent = assembly.agent\n if (agent === undefined) return ''\n const sessionId = String(agent.session.id)\n const current = ctx.workingDirectory.get(agent.session)\n const scope = registry.get(sessionId)\n if (scope !== null && contains(scope.worktree.path, current)) return scopeHint(scope)\n // The committed directory is the authority: a session that left the\n // copy must not keep the line.\n if (scope !== null) registry.forget(sessionId)\n const anchor = agent.session.header.cwd ?? current\n // A session still at its creation directory is in no copy; only a\n // directory change inside the project is worth a Git look, and only\n // once per distinct directory value.\n if (current === anchor || !contains(anchor, current)) return ''\n if (lastChecked.get(sessionId) === current) return ''\n void refresh(agent, current)\n return ''\n },\n })\n })\n\n const inFlight = new Set<string>()\n /** The directory value each session was last inspected at, hit or miss. */\n const lastChecked = new Map<string, string>()\n\n /** Re-derive one live agent's scope, remembering or forgetting it. */\n const refresh = async (agent: Agent, current: string = ctx.workingDirectory.get(agent.session)): Promise<void> => {\n const sessionId = String(agent.session.id)\n const anchor = agent.session.header.cwd ?? current\n // Cheap reject before any Git work: a copy is always inside the project,\n // and a session still at its creation directory is in no copy.\n if (current === anchor || !contains(anchor, current)) {\n registry.forget(sessionId)\n return\n }\n if (inFlight.has(sessionId)) return\n inFlight.add(sessionId)\n lastChecked.set(sessionId, current)\n try {\n const found = await detect(agent)\n if (found === null) registry.forget(sessionId)\n else registry.remember(sessionId, found)\n } catch {\n registry.forget(sessionId)\n } finally {\n inFlight.delete(sessionId)\n }\n }\n\n // A resumed session enters no verb, so creation is where it is recognised; an\n // ordinary new session sits at its creation directory and stops at the cheap\n // check. The hook is serial, so the first request already sees the line.\n ctx.on('agent/created', async ({ agent }) => {\n await refresh(agent)\n return undefined\n })\n}\n\n","/**\n * Host half of the Worktree switch.\n *\n * It mounts no service itself: the bundle patch inserts the harness worktree\n * service (`@deepseek-ai/dsh-experimental-worktree`, which provides\n * `ctx.worktrees`) and this plugin's own row. This plugin exposes five verbs\n * over the Connection fetch bridge and drives that service for creation; it\n * also contributes one model-facing context line while a session runs in a\n * copy, and the browser half renders the switch and the panel.\n * @module dsh-worktree-switch\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport type {} from '@deepseek-ai/dsh-agent'\nimport type {} from '@deepseek-ai/dsh-experimental-worktree'\nimport type {} from '@deepseek-ai/dsh-subprocess'\nimport type {} from '@deepseek-ai/dsh-working-directory'\nimport { installSourceCheckoutGuard } from './host/guard.ts'\nimport { createWorktreeToggle, detectSessionScope } from './host/handlers.ts'\nimport { registerWorktreeRoutes } from './host/routes.ts'\nimport { installWorktreeScope, SessionScopeRegistry } from './host/scope.ts'\n\n/** Plugin id: package name, bundle row id, browser module id. */\nexport const name = 'dsh-worktree-switch'\n\n/**\n * Services this plugin cannot work without.\n *\n * `worktrees` is provided by the row this bundle's patch inserts next to its\n * own, so Cordis holds this plugin until that row has mounted.\n */\nexport const inject = ['agents', 'connection', 'subprocess', 'worktrees', 'workingDirectory']\n\n/** Row config accepted from `cordis.patch.yml`. */\nexport interface Config {\n /** Default for a project the user has never switched either way. */\n defaultEnabled: boolean\n /** Refuse shell commands that run Git against the project checkout while the session is in a copy. */\n guardSourceCheckout: boolean\n}\n\n/** Schemastery schema for {@link Config}. */\nexport const Config: Schema<Config> = Schema.object({\n defaultEnabled: Schema.boolean()\n .default(true)\n .description('Start a new session in a worktree copy until the user turns the switch off for that project.'),\n guardSourceCheckout: Schema.boolean()\n .default(true)\n .description('While a session is in a copy, deny shell commands that run Git against the project checkout.'),\n})\n\n/**\n * Mount the host half.\n * @param ctx - Host context.\n * @param config - validated row config.\n */\nexport function apply(ctx: Context, config: Config): void {\n const scopes = new SessionScopeRegistry()\n const toggle = createWorktreeToggle(ctx, {\n defaultEnabled: config.defaultEnabled,\n onScope: (sessionId, scope) => {\n if (scope === null) scopes.forget(sessionId)\n else scopes.remember(sessionId, scope)\n },\n })\n installWorktreeScope(ctx, {\n registry: scopes,\n detect: (agent, signal) => detectSessionScope(ctx, agent, signal),\n })\n if (config.guardSourceCheckout) installSourceCheckoutGuard(ctx, scopes)\n registerWorktreeRoutes(ctx, toggle)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["record"],"sources":["../src/host/paths.ts","../src/host/guard.ts","../src/host/git.ts","../src/host/base.ts","../src/host/naming.ts","../src/host/ownership.ts","../src/host/handlers.ts","../src/host/routes.ts","../src/host/scope.ts","../src/index.ts"],"sourcesContent":["/**\n * Path predicates the host half shares between the verbs, the scope hint, and\n * the working-directory watcher.\n * @module dsh-worktree-switch/host/paths\n */\n\nimport { isAbsolute, relative } from 'node:path'\n\n/**\n * Whether one absolute directory is the path itself or an ancestor of it.\n * @param directory - candidate ancestor.\n * @param path - absolute path to test.\n * @returns true when `path` is `directory` or lives below it.\n */\nexport function contains(directory: string, path: string): boolean {\n const inside = relative(directory, path)\n return inside === '' || (!inside.startsWith('..') && !isAbsolute(inside))\n}\n","/**\n * The source-checkout guard: while a session runs in a worktree copy, shell\n * commands that would run Git against the project checkout are refused before\n * dispatch.\n *\n * The prompt context (`worktree-switch:scope`) explains where the workspace\n * is, but it cannot stop a model that still reaches for the checkout it can\n * see — and the sandbox deliberately grants writes there. This guard is the\n * enforcement half that needs no harness change: `tools/pre-execute` can deny\n * one call, and the model reads the reason as that call's error result.\n *\n * The pattern is deliberately narrow. Only a shell call that mentions `git`\n * is considered, and only when it names the project checkout either as an\n * explicit `workdir` or as a literal absolute path in the command text.\n * `cd ..`-style relative escapes and shell variables are not resolved; the\n * guard is a net, not the sandbox boundary.\n * @module dsh-worktree-switch/host/guard\n */\n\nimport { isAbsolute, resolve } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { PreToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools'\nimport type { SessionScope } from '../types.ts'\nimport { contains } from './paths.ts'\nimport type { SessionScopeRegistry } from './scope.ts'\n\n/** Tool names whose `command` argument is executed by a shell. */\nconst SHELL_TOOLS = new Set(['bash'])\n\n/** The arguments the guard reads from one shell call. */\ninterface ShellArguments {\n /** Command text as the model wrote it. */\n readonly command: string\n /** Explicit working directory, when the call carried one. */\n readonly workdir?: string\n}\n\n/**\n * Read a shell call's command and explicit directory, if this is one.\n * @param exec - the pending tool call.\n * @returns the fields the guard needs, or null for every other tool.\n */\nfunction shellArguments(exec: ToolExecution): ShellArguments | null {\n if (!SHELL_TOOLS.has(exec.name)) return null\n if (exec.arguments === null || typeof exec.arguments !== 'object') return null\n const args = exec.arguments as Record<string, unknown>\n const command = args['command']\n if (typeof command !== 'string' || command.trim() === '') return null\n const workdir = args['workdir']\n const explicit = typeof workdir === 'string' && workdir.trim() !== '' ? workdir : undefined\n return explicit === undefined ? { command } : { command, workdir: explicit }\n}\n\n/**\n * The model-facing denial for one shell command.\n * @param command - shell command text as the model wrote it.\n * @param workdir - explicit working directory, when the call carried one.\n * @param cwd - the session's committed working directory.\n * @param scope - the copy the session runs in.\n * @returns the denial reason, or null when the call may run.\n */\nexport function sourceCheckoutDenial(\n command: string,\n workdir: string | undefined,\n cwd: string,\n scope: SessionScope,\n): string | null {\n // Non-Git commands are out of scope: this guard is about the checkout's Git\n // state, not about every write the sandbox already allows.\n if (!/\\bgit\\b/.test(command)) return null\n const checkout = scope.worktree.path\n const project = scope.repositoryRoot\n if (workdir !== undefined) {\n const target = isAbsolute(workdir) ? workdir : resolve(cwd, workdir)\n if (contains(project, target) && !contains(checkout, target)) return denial(scope)\n }\n // The copy lives inside the project, so mention of the copy path must not\n // count as naming the checkout; strip it before looking for the project.\n if (command.split(checkout).join('').includes(project)) return denial(scope)\n return null\n}\n\n/** The one explanation every denial carries, including the way to get main. */\nfunction denial(scope: SessionScope): string {\n return `Denied: this session runs in the worktree copy ${JSON.stringify(scope.worktree.path)}, so Git commands targeting the project checkout ${JSON.stringify(scope.repositoryRoot)} are blocked. Run Git in the current working directory instead; to bring main's latest into this copy, use \\`git fetch origin main\\` and then \\`git merge origin/main\\` (or \\`git rebase origin/main\\`).`\n}\n\n/**\n * Refuse shell calls that run Git against the project checkout while the\n * session is inside a copy.\n *\n * The registry is the same one the prompt context reads, so the guard is\n * active exactly while the model has been told it is in a copy.\n * @param ctx - host context carrying `workingDirectory` and the tool runtime.\n * @param registry - sessions' confirmed copies.\n */\nexport function installSourceCheckoutGuard(ctx: Context, registry: SessionScopeRegistry): void {\n ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {\n const agent = exec.agent\n if (agent === undefined) return next()\n const scope = registry.get(String(agent.session.id))\n if (scope === null) return next()\n const args = shellArguments(exec)\n if (args === null) return next()\n const denial = sourceCheckoutDenial(args.command, args.workdir, ctx.workingDirectory.get(agent.session), scope)\n return denial === null ? next() : { kind: 'deny', reason: denial }\n })\n}\n","/**\n * Unconfined Git calls behind the plugin's user-initiated verbs.\n *\n * Creation goes through `ctx.worktrees` (the harness service), which runs its\n * own Git under the calling session's sandbox policy. The list, status, and\n * discard verbs here belong to a person clicking in the UI rather than to a\n * model turn: they inspect and delete the copies the user can see, from a path\n * the same UI just listed. They therefore spawn Git directly, the same way the\n * installed `dsh-worktree-space` plugin does, instead of borrowing a session's\n * policy for an operation no session asked for.\n * @module dsh-worktree-switch/host/git\n */\n\nimport { realpath } from 'node:fs/promises'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-subprocess'\n\n/** Whole-command deadline for one Git call. */\nexport const GIT_TIMEOUT_MS = 20_000\n\n/** Per-stream capture cap; worktree listings and status lines are small. */\nconst MAX_OUTPUT_BYTES = 1_048_576\n\n/**\n * Clear inherited repository selectors and config so the command reads the\n * checkout named by `cwd`, never one inherited from the Host's environment.\n * One entry per key: `undefined` removes the variable for the child.\n */\nconst GIT_ENV: NodeJS.ProcessEnv = Object.fromEntries([\n 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',\n 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_COUNT',\n 'GIT_CEILING_DIRECTORIES', 'GIT_PREFIX', 'GIT_SHALLOW_FILE', 'GIT_GRAFT_FILE',\n 'GIT_REPLACE_REF_BASE', 'GIT_ATTR_SOURCE',\n].map(key => [key, undefined]))\n\n/** One bounded Git invocation's captured result. */\nexport interface GitResult {\n /** Captured stdout. */\n readonly stdout: string\n /** Captured stderr. */\n readonly stderr: string\n /** Process exit status. */\n readonly exitCode: number\n}\n\n/** A Git invocation that exited with a status the caller did not allow. */\nexport class GitFailure extends Error {\n /**\n * @param args - arguments after the executable, for diagnostics.\n * @param result - the captured outcome.\n */\n constructor(readonly args: readonly string[], readonly result: GitResult) {\n const detail = result.stderr.trim() === '' ? result.stdout.trim() : result.stderr.trim()\n super(`git ${args.join(' ')} failed (exit ${result.exitCode})${detail === '' ? '' : `: ${detail}`}`)\n this.name = 'GitFailure'\n }\n}\n\n/** Options for {@link runGit}. */\nexport interface GitOptions {\n /** Caller cancellation, combined with the command deadline. */\n readonly signal?: AbortSignal | undefined\n /** Exit statuses that are not failures; defaults to `[0]`. */\n readonly allowedExitCodes?: readonly number[] | undefined\n /** Override for {@link GIT_TIMEOUT_MS}. */\n readonly timeoutMs?: number | undefined\n}\n\n/**\n * Run one Git command without a shell and await its termination.\n * @param ctx - Host context carrying the subprocess provider.\n * @param cwd - absolute directory the command runs in.\n * @param args - arguments after the executable.\n * @param options - cancellation, accepted exit statuses, and deadline.\n * @returns captured stdout, stderr, and exit status.\n * @throws {GitFailure} when the exit status is not allowed or the command was killed.\n */\nexport async function runGit(\n ctx: Context,\n cwd: string,\n args: readonly string[],\n options: GitOptions = {},\n): Promise<GitResult> {\n const deadline = AbortSignal.timeout(options.timeoutMs ?? GIT_TIMEOUT_MS)\n const signal = options.signal === undefined ? deadline : AbortSignal.any([options.signal, deadline])\n const allowed = options.allowedExitCodes ?? [0]\n const program = await ctx.subprocess.resolveExecutable('git', undefined, signal)\n const argv = [program, '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', ...args]\n const child = ctx.subprocess.spawn({\n argv,\n cwd,\n env: {\n ...GIT_ENV,\n GIT_TERMINAL_PROMPT: '0',\n GIT_LFS_SKIP_SMUDGE: '1',\n GIT_NO_LAZY_FETCH: '1',\n },\n signal,\n graceMs: 1_500,\n stdio: {\n stdin: 'ignore',\n stdout: { maxBytes: MAX_OUTPUT_BYTES },\n stderr: { maxBytes: MAX_OUTPUT_BYTES },\n },\n })\n try {\n const outcome = await child.done\n const stdout = child.collected.stdout?.readFrom(0).text ?? ''\n const stderr = child.collected.stderr?.readFrom(0).text ?? ''\n const result: GitResult = { stdout, stderr, exitCode: outcome.exitCode ?? -1 }\n if (outcome.exitCode === null || !allowed.includes(outcome.exitCode)) throw new GitFailure(args, result)\n return result\n } finally {\n child.terminate()\n await child.waitForExit()\n }\n}\n\n/**\n * Resolve the repository root that contains `directory`.\n * @param ctx - Host context carrying the subprocess provider.\n * @param directory - absolute directory inside (or at) the checkout.\n * @param signal - caller cancellation.\n * @returns the canonical top-level directory Git reports.\n * @throws {GitFailure} when the directory is not inside a Git working tree.\n */\nexport async function repositoryRoot(ctx: Context, directory: string, signal?: AbortSignal): Promise<string> {\n const result = await runGit(ctx, directory, ['rev-parse', '--show-toplevel'], {\n signal,\n allowedExitCodes: [0, 128],\n })\n if (result.exitCode !== 0) throw new GitFailure(['rev-parse', '--show-toplevel'], result)\n return result.stdout.replace(/\\r?\\n$/, '')\n}\n\n/**\n * Resolve symlinks in a path that exists, falling back to the path as given.\n * @param path - absolute path.\n * @returns the canonical spelling when the path exists.\n */\nexport async function canonicalPath(path: string): Promise<string> {\n try {\n return await realpath(path)\n } catch {\n return path\n }\n}\n\n/** One checkout reported by `git worktree list --porcelain`. */\nexport interface WorktreeRecord {\n /** Absolute checkout directory. */\n readonly path: string\n /** Branch the checkout has checked out, when it has one. */\n readonly branch?: string | undefined\n}\n\n/**\n * Parse `git worktree list --porcelain` output into its records.\n * @param stdout - the command's stdout.\n * @returns one record per reported checkout, in Git's order.\n */\nexport function parseWorktreeList(stdout: string): WorktreeRecord[] {\n const records: WorktreeRecord[] = []\n let path: string | undefined\n let branch: string | undefined\n const flush = (): void => {\n if (path === undefined) return\n records.push(branch === undefined ? { path } : { path, branch })\n path = undefined\n branch = undefined\n }\n for (const line of stdout.split('\\n')) {\n if (line === '') {\n flush()\n continue\n }\n if (line.startsWith('worktree ')) path = line.slice('worktree '.length)\n else if (line.startsWith('branch refs/heads/')) branch = line.slice('branch refs/heads/'.length)\n }\n flush()\n return records\n}\n\n/**\n * List the worktree checkouts of one repository.\n * @param ctx - Host context carrying the subprocess provider.\n * @param root - repository root the command runs in.\n * @param signal - caller cancellation.\n * @returns every checkout Git reports for the repository.\n */\nexport async function listWorktreeRecords(\n ctx: Context,\n root: string,\n signal?: AbortSignal,\n): Promise<WorktreeRecord[]> {\n const result = await runGit(ctx, root, ['worktree', 'list', '--porcelain'], { signal })\n return parseWorktreeList(result.stdout)\n}\n\n/**\n * Count the changed and untracked files of one checkout.\n * @param ctx - Host context carrying the subprocess provider.\n * @param directory - checkout directory to inspect.\n * @param signal - caller cancellation.\n * @returns the number of `git status --porcelain` entries.\n */\nexport async function changedFileCount(\n ctx: Context,\n directory: string,\n signal?: AbortSignal,\n): Promise<number> {\n const result = await runGit(ctx, directory, ['status', '--porcelain', '--untracked-files=normal'], {\n signal,\n })\n return result.stdout.split('\\n').filter(line => line.trim() !== '').length\n}\n\n/**\n * Resolve the whereabouts of one checkout's Git administration directory.\n *\n * A linked worktree's `.git` is a file pointing at\n * `<common>/worktrees/<name>`; this is the directory Git owns for that one\n * checkout, which is where the switch records its claim on the copy.\n * @param ctx - Host context carrying the subprocess provider.\n * @param checkout - absolute directory of the checkout to ask.\n * @param signal - caller cancellation.\n * @returns the absolute admin directory Git reports.\n * @throws {GitFailure} when the directory is not inside a Git working tree.\n */\nexport async function absoluteGitDir(ctx: Context, checkout: string, signal?: AbortSignal): Promise<string> {\n const result = await runGit(ctx, checkout, ['rev-parse', '--absolute-git-dir'], { signal })\n const path = result.stdout.replace(/\\r?\\n$/, '')\n if (path === '') throw new GitFailure(['rev-parse', '--absolute-git-dir'], result)\n return path\n}\n\n/**\n * Read the branch one checkout has checked out.\n * @param ctx - Host context carrying the subprocess provider.\n * @param checkout - absolute directory of the checkout to ask.\n * @param signal - caller cancellation.\n * @returns the branch name, or null when the checkout is on a detached HEAD.\n */\nexport async function currentBranch(\n ctx: Context,\n checkout: string,\n signal?: AbortSignal,\n): Promise<string | null> {\n const result = await runGit(ctx, checkout, ['symbolic-ref', '--short', '--quiet', 'HEAD'], {\n signal,\n allowedExitCodes: [0, 1],\n })\n const name = result.stdout.replace(/\\r?\\n$/, '')\n return result.exitCode === 0 && name !== '' ? name : null\n}\n\n/**\n * Resolve one revision to the commit id it names.\n * @param ctx - Host context carrying the subprocess provider.\n * @param directory - checkout the command runs in.\n * @param revision - revision to resolve; `HEAD` or a full id are the usual values.\n * @param signal - caller cancellation.\n * @returns the commit id, or null when the revision does not resolve.\n */\nexport async function revParse(\n ctx: Context,\n directory: string,\n revision: string,\n signal?: AbortSignal,\n): Promise<string | null> {\n const result = await runGit(ctx, directory, ['rev-parse', '--verify', '--quiet', revision], {\n signal,\n allowedExitCodes: [0, 1, 128],\n })\n const id = result.stdout.replace(/\\r?\\n$/, '')\n return result.exitCode === 0 && id !== '' ? id : null\n}\n\n/**\n * Count the commits in one revision range.\n * @param ctx - Host context carrying the subprocess provider.\n * @param directory - checkout the command runs in.\n * @param range - the range to count, e.g. `HEAD..refs/heads/<branch>`.\n * @param signal - caller cancellation.\n * @returns the number of commits in the range.\n * @throws {GitFailure} when the range does not resolve.\n */\nexport async function revListCount(\n ctx: Context,\n directory: string,\n range: string,\n signal?: AbortSignal,\n): Promise<number> {\n const result = await runGit(ctx, directory, ['rev-list', '--count', range], { signal })\n const count = Number.parseInt(result.stdout.trim(), 10)\n return Number.isFinite(count) ? count : 0\n}\n\n/** One local branch, as `git for-each-ref` reports it. */\nexport interface BranchRef {\n /** Branch name without `refs/heads/`. */\n readonly name: string\n /** Commit the branch points at. */\n readonly commit: string\n /** Committer date of that commit, in milliseconds since the epoch. */\n readonly committedAt: number\n /** One-line subject of that commit. */\n readonly subject: string\n}\n\n/**\n * Parse `git for-each-ref` output produced with the NUL-separated format below.\n * @param stdout - the command's stdout.\n * @returns one entry per local branch, in Git's order.\n */\nexport function parseBranchRefs(stdout: string): BranchRef[] {\n const refs: BranchRef[] = []\n for (const line of stdout.split('\\n')) {\n if (line === '') continue\n const [name, commit, seconds, subject] = line.split('\\u0000')\n if (name === undefined || commit === undefined || seconds === undefined || subject === undefined) continue\n const committedAt = Number.parseInt(seconds, 10) * 1000\n refs.push({\n name,\n commit,\n committedAt: Number.isFinite(committedAt) ? committedAt : 0,\n subject,\n })\n }\n return refs\n}\n\n/**\n * List the local branches of one repository.\n * @param ctx - Host context carrying the subprocess provider.\n * @param root - repository root the command runs in.\n * @param signal - caller cancellation.\n * @returns every local branch with its tip commit.\n */\nexport async function listBranchRefs(\n ctx: Context,\n root: string,\n signal?: AbortSignal,\n): Promise<BranchRef[]> {\n const result = await runGit(\n ctx,\n root,\n [\n 'for-each-ref',\n '--format=%(refname:short)%00%(objectname)%00%(committerdate:unix)%00%(subject)',\n 'refs/heads',\n ],\n { signal },\n )\n return parseBranchRefs(result.stdout)\n}\n","/**\n * Where a new copy starts.\n *\n * Creation is the one place this plugin touches the network, and it does so as\n * a person-initiated action, not on behalf of a model turn: the browser asked\n * for a copy, so the host fetches the remote default branch itself instead of\n * borrowing a session's sandbox policy for it. This is the same class of action\n * as the panel's discard verb (see `./git.ts`).\n *\n * The fetch is deliberately best-effort. A stale remote, an unreachable host,\n * or a clone without a default branch must never turn the switch into a dead\n * end, so every failure falls back to the local HEAD — the only case the\n * browser half reports as degraded is \"there is a remote and a default branch,\n * yet fetching it failed\".\n * @module dsh-worktree-switch/host/base\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { BaseRef, PrepareBase } from '../types.ts'\nimport { runGit } from './git.ts'\n\n/** Whole-command deadline for the plugin's one network call. */\nconst FETCH_TIMEOUT_MS = 5_000\n\n/** A chosen base and how it was chosen. */\nexport interface BaseOutcome {\n /** Revision handed to the worktree service's `from` field. */\n readonly from: string\n /** Where `from` came from; only `fallback` is surfaced to the user. */\n readonly base: PrepareBase\n}\n\n/** Local HEAD, used by `head` mode and by every silent failure. */\nconst LOCAL_HEAD: BaseOutcome = { from: 'HEAD', base: { kind: 'head' } }\n\n/**\n * Resolve the default branch `origin` advertises, if it has one.\n *\n * `origin/HEAD` is the clone's own answer; clones that lost it still commonly\n * default to `main` or `master`, so those two are probed before giving up. A\n * repository with no `origin` is a normal local-only project, not a failure:\n * the caller skips fetching entirely and leaves no notice behind.\n * @param ctx - Host context carrying the subprocess provider.\n * @param root - canonical repository root to ask.\n * @param signal - caller cancellation.\n * @returns the branch name without its `origin/` prefix, or null.\n */\nasync function defaultBranch(ctx: Context, root: string, signal?: AbortSignal): Promise<string | null> {\n const remote = await runGit(ctx, root, ['remote', 'get-url', 'origin'], {\n signal,\n allowedExitCodes: [0, 1, 2],\n })\n if (remote.exitCode !== 0 || remote.stdout.trim() === '') return null\n const symbolic = await runGit(ctx, root, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], {\n signal,\n // 128 is what Git uses for \"not a symbolic ref\", which is the common case\n // for a clone whose remote-tracking HEAD was never created.\n allowedExitCodes: [0, 1, 128],\n })\n if (symbolic.exitCode === 0) {\n const name = symbolic.stdout.trim()\n if (name.startsWith('origin/') && name.length > 'origin/'.length) {\n return name.slice('origin/'.length)\n }\n }\n for (const candidate of ['main', 'master']) {\n const known = await runGit(ctx, root, ['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${candidate}`], {\n signal,\n allowedExitCodes: [0, 1],\n })\n if (known.exitCode === 0 && known.stdout.trim() !== '') return candidate\n }\n return null\n}\n\n/**\n * Choose the base commit for a copy that is about to be created.\n *\n * `head` mode never touches the network. `fresh` mode fetches only the default\n * branch, with an explicit refspec so a rewritten `remote.origin.fetch` cannot\n * silently skip the update, and returns the fetched commit itself rather than\n * the ref name: another process may advance the ref between the fetch and the\n * worktree service's checkout.\n * @param ctx - Host context carrying the subprocess provider.\n * @param root - canonical repository root the fetch runs in.\n * @param baseRef - configured base point.\n * @param signal - caller cancellation; distinct from the fetch deadline.\n * @returns the revision to create from and the outcome to report.\n * @throws when the caller cancels; every Git failure is contained.\n */\nexport async function resolveBase(\n ctx: Context,\n root: string,\n baseRef: BaseRef,\n signal?: AbortSignal,\n): Promise<BaseOutcome> {\n if (baseRef === 'head') return LOCAL_HEAD\n let branch: string | null\n try {\n branch = await defaultBranch(ctx, root, signal)\n } catch {\n // Not knowing whether a remote exists is not the same as knowing one\n // failed: stay silent rather than claim a degraded fetch.\n signal?.throwIfAborted()\n return LOCAL_HEAD\n }\n if (branch === null) return LOCAL_HEAD\n try {\n await runGit(\n ctx,\n root,\n ['fetch', '--no-tags', '--quiet', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`],\n { signal, timeoutMs: FETCH_TIMEOUT_MS },\n )\n const resolved = await runGit(ctx, root, ['rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`], {\n signal,\n })\n const from = resolved.stdout.trim()\n if (from === '') throw new Error(`origin/${branch} has no commit after fetching it`)\n return { from, base: { kind: 'remote', branch } }\n } catch {\n // A cancelled request is not a degraded fetch: let the caller's abort win\n // instead of quietly creating a copy from HEAD after the user left.\n signal?.throwIfAborted()\n return { from: 'HEAD', base: { kind: 'fallback' } }\n }\n}\n","/**\n * Names for the copy a session gets: lowercase words and a short suffix, so a\n * branch, a directory, a `git branch` row and this plugin's UI all read the\n * same. No prefix: the pool path already says where it lives.\n * @module dsh-worktree-switch/host/naming\n */\n\n/** First word of a generated name. */\nconst ADJECTIVES = [\n 'amber', 'brisk', 'calm', 'clever', 'crisp', 'eager', 'faint', 'fresh',\n 'gentle', 'happy', 'hollow', 'kind', 'lively', 'mellow', 'nimble', 'polite',\n 'quiet', 'rapid', 'ready', 'silent', 'soft', 'solid', 'steady', 'sunny',\n 'swift', 'tidy', 'vivid', 'warm', 'wise', 'young',\n] as const\n\n/** Second word of a generated name. */\nconst NOUNS = [\n 'anchor', 'atlas', 'badger', 'birch', 'brook', 'cedar', 'comet', 'coral',\n 'delta', 'ember', 'fern', 'field', 'finch', 'glade', 'harbor', 'heron',\n 'island', 'lark', 'ledge', 'maple', 'meadow', 'otter', 'pebble', 'pine',\n 'quartz', 'river', 'shore', 'stone', 'trail', 'willow',\n] as const\n\n/**\n * Generate one candidate worktree name.\n * @param random - uniform source in `[0, 1)`; injectable for deterministic tests.\n * @returns a lowercase, hyphen-joined branch-safe name such as `quiet-harbor-3fa9`.\n */\nexport function worktreeName(random: () => number = Math.random): string {\n const pick = (values: readonly string[]): string => {\n const index = Math.min(values.length - 1, Math.floor(random() * values.length))\n return values[index] ?? values[0] ?? 'copy'\n }\n const suffix = Math.min(0xffff, Math.floor(random() * 0x10000)).toString(16).padStart(4, '0')\n return `${pick(ADJECTIVES)}-${pick(NOUNS)}-${suffix}`\n}\n\n/** A pool entry name is exactly one path segment, as `git worktree` names it. */\nconst NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/\n\n/** The shape this plugin generates: two lowercase words and four hex digits. */\nconst GENERATED_PATTERN = /^[a-z]+-[a-z]+-[0-9a-f]{4}$/\n\n/**\n * Whether a branch name looks like one this plugin generated.\n *\n * Leftover-branch cleanup only offers names of this shape: an ordinary feature\n * branch is never a candidate, even when no checkout holds it.\n * @param value - candidate branch name.\n * @returns true for the generated-name format.\n */\nexport function isGeneratedName(value: string): boolean {\n return GENERATED_PATTERN.test(value)\n}\n\n/**\n * Validate a worktree name received from the browser before it reaches Git.\n * @param value - candidate name.\n * @returns true for one plain path segment that Git also accepts as a branch.\n */\nexport function isWorktreeName(value: string): boolean {\n return NAME_PATTERN.test(value) && !value.endsWith('.lock')\n}\n","/**\n * The switch's claim on a copy.\n *\n * \"This copy was created by the switch for this session\" used to live only in\n * host memory, so a host restart turned every copy into a stranger and the\n * leave path could never prove it was safe to undo. The claim is now stored in\n * the checkout's own Git administration directory: it travels with the copy,\n * vanishes when the copy is deleted, never appears in the working tree, and\n * survives a restart. A missing or unparseable marker means \"not owned\" — the\n * plugin does not guess, because guessing wrong deletes work.\n * @module dsh-worktree-switch/host/ownership\n */\n\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { absoluteGitDir } from './git.ts'\n\n/** File name inside the checkout's admin directory. */\nconst MARKER_FILE = 'dsh-worktree-switch.json'\n\n/** What the switch records about one copy it created. */\nexport interface Ownership {\n /** Session the copy was created for. */\n readonly sessionId: string\n /** When the copy was created, in milliseconds since the epoch. */\n readonly createdAt: number\n /** Commit the copy started from; any other tip means the copy has commits. */\n readonly baseCommit: string\n}\n\n/**\n * Read one checkout's ownership marker.\n *\n * Every failure — no admin directory, no marker, invalid JSON, a shape this\n * version does not understand — reads as \"no claim\". The leave path treats\n * that as a reason to keep the copy, never as a reason to take it.\n * @param ctx - Host context carrying the subprocess provider.\n * @param checkout - absolute directory of the copy.\n * @param signal - caller cancellation.\n * @returns the recorded claim, or null when there is none.\n */\nexport async function readOwnership(\n ctx: Context,\n checkout: string,\n signal?: AbortSignal,\n): Promise<Ownership | null> {\n try {\n const directory = await absoluteGitDir(ctx, checkout, signal)\n const raw = await readFile(join(directory, MARKER_FILE), 'utf8')\n const parsed: unknown = JSON.parse(raw)\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return null\n const record = parsed as Record<string, unknown>\n const { sessionId, createdAt, baseCommit } = record\n if (typeof sessionId !== 'string' || sessionId === '') return null\n if (typeof createdAt !== 'number' || !Number.isFinite(createdAt)) return null\n if (typeof baseCommit !== 'string' || baseCommit === '') return null\n return { sessionId, createdAt, baseCommit }\n } catch {\n return null\n }\n}\n\n/**\n * Record the switch's claim on a freshly created copy.\n * @param ctx - Host context carrying the subprocess provider.\n * @param checkout - absolute directory of the copy.\n * @param ownership - the claim to write.\n * @param signal - caller cancellation.\n * @throws when the admin directory cannot be resolved or the marker cannot be written.\n */\nexport async function writeOwnership(\n ctx: Context,\n checkout: string,\n ownership: Ownership,\n signal?: AbortSignal,\n): Promise<void> {\n const directory = await absoluteGitDir(ctx, checkout, signal)\n await writeFile(join(directory, MARKER_FILE), `${JSON.stringify(ownership)}\\n`, 'utf8')\n}\n","/**\n * The plugin's host Remote face: the verbs the browser half can call,\n * expressed against a live session and the checkouts on disk.\n *\n * This module is the seam the host tests drive directly. Everything below it —\n * Git, the worktree service, the sandbox policy it applies, and session\n * working-directory publication — belongs to the harness and is exercised by\n * its own tests.\n * @module dsh-worktree-switch/host/handlers\n */\n\nimport { lstat, stat } from 'node:fs/promises'\nimport { basename, dirname, isAbsolute, relative, resolve } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { SessionId } from '@deepseek-ai/dsh-session'\nimport type {} from '@deepseek-ai/dsh-api-session-controller'\nimport type {} from '@deepseek-ai/dsh-experimental-worktree'\nimport type {} from '@deepseek-ai/dsh-working-directory'\nimport type {\n BaseRef,\n CopyRef,\n DiscardBranchValue,\n DiscardValue,\n KeptReason,\n ListValue,\n OrphanBranch,\n PrepareValue,\n ReleaseValue,\n ScopeValue,\n SessionScope,\n StatusValue,\n WorktreeItem,\n WorktreeRef,\n} from '../types.ts'\nimport { resolveBase } from './base.ts'\nimport {\n canonicalPath,\n changedFileCount,\n currentBranch,\n GitFailure,\n listBranchRefs,\n listWorktreeRecords,\n repositoryRoot,\n revListCount,\n revParse,\n runGit,\n} from './git.ts'\nimport type { GitResult, WorktreeRecord } from './git.ts'\nimport { isGeneratedName, isWorktreeName, worktreeName } from './naming.ts'\nimport { readOwnership, writeOwnership } from './ownership.ts'\nimport { contains } from './paths.ts'\n\n/** Attempts at a fresh name before a creation failure becomes an error. */\nconst CREATE_ATTEMPTS = 3\n\nexport type {\n CopyRef,\n DiscardBranchValue,\n DiscardValue,\n KeptReason,\n ListValue,\n OrphanBranch,\n PrepareValue,\n ReleaseValue,\n ScopeValue,\n SessionScope,\n StatusValue,\n WorktreeItem,\n WorktreeRef,\n} from '../types.ts'\n\n/** A failed verb, carrying the stable code the browser half switches on. */\nexport class WorktreeToggleError extends Error {\n /**\n * @param code - stable failure code.\n * @param message - human-readable explanation.\n * @param details - extra machine-readable facts.\n */\n constructor(\n readonly code: string,\n message: string,\n readonly details: Readonly<Record<string, unknown>> = {},\n ) {\n super(message)\n this.name = 'WorktreeToggleError'\n }\n}\n\n/** Row config the verbs read. */\nexport interface WorktreeToggleOptions {\n /** Default for a project nothing has remembered yet. */\n readonly defaultEnabled: boolean\n /** Commit a new copy starts from: the fetched remote default branch, or local HEAD. */\n readonly baseRef: BaseRef\n /**\n * Report what each verb learned about the session's copy, so the model-facing\n * scope hint can follow it without a second Git walk.\n * @param sessionId - wire session identity.\n * @param scope - the confirmed copy, or null when the session is not in one.\n */\n readonly onScope?: (sessionId: string, scope: SessionScope | null) => void\n /**\n * Read the copy the host already believes a session runs in, so the badge's\n * `scope` verb can answer without a Git walk.\n * @param sessionId - wire session identity.\n * @returns the remembered scope, or null when nothing is known.\n */\n readonly readScope?: (sessionId: string) => SessionScope | null\n}\n\n/** The host face the route layer exposes over the Connection fetch bridge. */\nexport interface WorktreeToggle {\n /**\n * Report whether the session's project can be isolated, and in what.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the status payload; `supported: false` is a normal answer.\n */\n status(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<StatusValue>\n /**\n * Put the session into a fresh copy, creating it when necessary.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the created or already-present copy.\n * @throws {WorktreeToggleError} when the project cannot host a copy or Git refused.\n */\n prepare(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<PrepareValue>\n /**\n * Put the session back in its project directory and undo the copy the switch\n * created for it, when that copy is provably untouched: owned, still at its\n * base commit, and clean. A copy with content, or one whose ownership cannot\n * be proven, is kept and reported with a reason.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the directory the session now runs in, the copy removed, and the\n * copy left on disk with the reason it stayed.\n * @throws {WorktreeToggleError} when the project cannot be resolved, the\n * session could not be moved back, or a delete this call attempted failed.\n */\n release(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<ReleaseValue>\n /**\n * Report whether the session runs in a copy, without touching the source\n * checkout's status and without moving any directory.\n * @param request - the calling session.\n * @param signal - caller cancellation.\n * @returns the copy, its project root, and the copy's own uncommitted count.\n */\n scope(request: { readonly sessionId: string }, signal?: AbortSignal): Promise<ScopeValue>\n /**\n * List the copies under one project's pool, the leftover generated-name\n * branches with no checkout, and how many copies this host kept on leave.\n * @param request - any directory inside the project.\n * @param signal - caller cancellation.\n * @returns the project's cleanup picture, or `supported: false` for a non-repository path.\n */\n list(request: { readonly path: string }, signal?: AbortSignal): Promise<ListValue>\n /**\n * Delete one copy and, unless it is detached, its branch.\n * @param request - a directory inside the project, and the copy's name.\n * @param signal - caller cancellation.\n * @returns the removed copy's identity.\n * @throws {WorktreeToggleError} when the copy is missing, dirty, or Git refused.\n */\n discard(\n request: { readonly path: string; readonly name: string },\n signal?: AbortSignal,\n ): Promise<DiscardValue>\n /**\n * Delete one leftover branch that no checkout has.\n * @param request - a directory inside the project, and the branch name.\n * @param signal - caller cancellation.\n * @returns the deleted branch.\n * @throws {WorktreeToggleError} when the branch is not a generated name, is\n * checked out, or Git refused; an already-deleted branch is a success.\n */\n discardBranch(\n request: { readonly path: string; readonly branch: string },\n signal?: AbortSignal,\n ): Promise<DiscardBranchValue>\n}\n\n/**\n * The failure one refused branch delete reports.\n * @param branch - branch the verb tried to delete.\n * @param args - the Git command that refused, for the message.\n * @param result - its captured outcome.\n * @returns the error the browser half switches on.\n */\nfunction branchDeleteFailure(branch: string, args: readonly string[], result: GitResult): WorktreeToggleError {\n return new WorktreeToggleError('branch-delete-failed', new GitFailure(args, result).message, { branch })\n}\n\n/** Normalize a browser-supplied path before any filesystem work. */\nfunction absolutePath(value: string): string {\n const trimmed = value.trim()\n if (trimmed === '') throw new WorktreeToggleError('bad-request', 'a project path is required')\n return resolve(trimmed)\n}\n\n/**\n * The copy a session already runs in.\n *\n * A linked worktree is recognised from the checkout itself — its `.git` is a\n * file pointing back at the source repository, not a directory — rather than\n * from any configured pool path. The pool belongs to the harness worktree\n * service, so this plugin never needs a second copy of that setting: moving the\n * pool in the service's row config keeps every verb working.\n * @param ctx - host context carrying `subprocess`.\n * @param current - the session's committed working directory.\n * @param repositoryRoot - canonical root of the project's repository.\n * @param signal - caller cancellation.\n * @returns the copy's identity, or null when the directory is not one.\n */\nasync function worktreeAt(\n ctx: Context,\n current: string,\n repositoryRoot: string,\n signal?: AbortSignal,\n): Promise<CopyRef | null> {\n const path = await canonicalPath(current)\n if (path === repositoryRoot) return null\n const inside = relative(repositoryRoot, path)\n if (inside === '' || inside.startsWith('..') || isAbsolute(inside)) return null\n const name = basename(path)\n if (!isWorktreeName(name)) return null\n try {\n if (!(await lstat(`${path}/.git`)).isFile()) return null\n } catch {\n return null\n }\n // The checkout's real branch, not its directory name: a detached copy has\n // none, and reporting the name as one would be a lie every surface repeats.\n return { name, path, branch: await currentBranch(ctx, path, signal) }\n}\n\n/**\n * Resolve the canonical root of the repository that contains a directory.\n * Git reports the spelling of the path it was given; the worktree service\n * publishes canonical paths, so both halves must agree on one spelling.\n * @param ctx - host context carrying `subprocess`.\n * @param directory - absolute directory inside the project.\n * @param signal - caller cancellation.\n * @returns the canonical repository root.\n */\nasync function repositoryOf(ctx: Context, directory: string, signal?: AbortSignal): Promise<string> {\n return canonicalPath(await repositoryRoot(ctx, directory, signal))\n}\n\n/**\n * Re-derive the copy one session runs in from its committed directory.\n *\n * A copy's root is the only linked checkout whose name matches the pool\n * convention, but a session may have changed into a directory *inside* the copy,\n * so ancestors are probed from the committed directory up to the project\n * anchor.\n * @param ctx - host context carrying `workingDirectory` and `subprocess`.\n * @param agent - live session owner.\n * @param signal - caller cancellation.\n * @returns the copy and project root, or null when the session is not in one.\n */\nexport async function detectSessionScope(\n ctx: Context,\n agent: Agent,\n signal?: AbortSignal,\n): Promise<SessionScope | null> {\n const current = ctx.workingDirectory.get(agent.session)\n const anchor = agent.session.header.cwd ?? current\n if (current === anchor || !contains(anchor, current)) return null\n let root: string\n try {\n root = await repositoryOf(ctx, anchor, signal)\n } catch {\n return null\n }\n signal?.throwIfAborted()\n let probe = current\n for (;;) {\n const copy = await worktreeAt(ctx, probe, root, signal)\n if (copy !== null) return { worktree: copy, repositoryRoot: root }\n const parent = dirname(probe)\n if (probe === root || probe === anchor || parent === probe) return null\n probe = parent\n }\n}\n\n/**\n * The copy rows of one repository, newest first.\n *\n * A row whose checkout directory is gone is still a copy: Git keeps the\n * registration until it is pruned, and the panel offers the same discard for\n * it, marked as missing. A detached copy keeps `branch: null` so no verb can\n * ever mistake the directory name for a branch to delete.\n * @param ctx - host context carrying `subprocess`.\n * @param root - canonical repository root.\n * @param records - `git worktree list` records for the repository.\n * @param signal - caller cancellation.\n * @returns one row per copy under the project.\n */\nasync function copyItems(\n ctx: Context,\n root: string,\n records: readonly WorktreeRecord[],\n signal?: AbortSignal,\n): Promise<WorktreeItem[]> {\n const items: WorktreeItem[] = []\n for (const record of records) {\n signal?.throwIfAborted()\n const path = await canonicalPath(record.path)\n // The repository's own checkout is the source, never a copy.\n if (path === root) continue\n const name = basename(path)\n if (!isWorktreeName(name)) continue\n let createdAt = 0\n let prunable = true\n try {\n const info = await stat(path)\n createdAt = Math.trunc(info.birthtimeMs > 0 ? info.birthtimeMs : info.mtimeMs)\n prunable = false\n } catch {\n // The checkout directory is gone; Git still lists it until pruned.\n }\n const changedFiles = prunable ? 0 : await changedFileCount(ctx, path, signal)\n items.push({ name, path, branch: record.branch ?? null, createdAt, changedFiles, prunable })\n }\n items.sort((left, right) => right.createdAt - left.createdAt || left.name.localeCompare(right.name))\n return items\n}\n\n/**\n * Generated-name branches that no checkout holds.\n *\n * Deliberately narrow: only the names this plugin generates qualify, so an\n * ordinary feature branch never appears in a list whose only action is delete.\n * The commit count is what the source checkout's HEAD does not contain — a\n * branch whose work is fully merged reads as 0, which is the one safe signal.\n * @param ctx - host context carrying `subprocess`.\n * @param root - canonical repository root.\n * @param records - `git worktree list` records for the repository.\n * @param signal - caller cancellation.\n * @returns the leftover branches, newest commit first.\n */\nasync function orphanBranches(\n ctx: Context,\n root: string,\n records: readonly WorktreeRecord[],\n signal?: AbortSignal,\n): Promise<OrphanBranch[]> {\n const checkedOut = new Set<string>()\n for (const record of records) {\n if (record.branch !== undefined) checkedOut.add(record.branch)\n }\n const orphans: OrphanBranch[] = []\n for (const ref of await listBranchRefs(ctx, root, signal)) {\n signal?.throwIfAborted()\n if (!isGeneratedName(ref.name) || checkedOut.has(ref.name)) continue\n let commits = 0\n try {\n commits = await revListCount(ctx, root, `HEAD..${ref.commit}`, signal)\n } catch {\n // An unborn HEAD (a repository with no commits) cannot answer; the\n // branch still lists, with nothing claimed about its size.\n }\n orphans.push({\n branch: ref.name,\n commits,\n subject: ref.subject,\n committedAt: ref.committedAt,\n })\n }\n orphans.sort(\n (left, right) => right.committedAt - left.committedAt || left.branch.localeCompare(right.branch),\n )\n return orphans\n}\n\n/** The wire identity of one listed copy; a detached copy keeps its null branch. */\nfunction copyRef(item: WorktreeItem): CopyRef {\n return { name: item.name, path: item.path, branch: item.branch }\n}\n\n/**\n * Build the host face.\n * @param ctx - Host context with `agents`, `worktrees`, `workingDirectory`, and `subprocess`.\n * @param options - resolved row config.\n * @returns the verbs above.\n */\nexport function createWorktreeToggle(ctx: Context, options: WorktreeToggleOptions): WorktreeToggle {\n /** One in-flight preparation per session, so a double mount cannot create twice. */\n const pending = new Map<string, Promise<PrepareValue>>()\n /**\n * Copies this host instance kept when the switch was turned off, by\n * repository root. A host restart forgets them; the panel's count then\n * honestly drops back to leftovers only.\n */\n const keptCopies = new Map<string, Set<string>>()\n\n const keep = (root: string, name: string): void => {\n const names = keptCopies.get(root) ?? new Set<string>()\n names.add(name)\n keptCopies.set(root, names)\n }\n\n const forget = (root: string, name: string): void => {\n keptCopies.get(root)?.delete(name)\n }\n\n const sessionOf = async (sessionId: string, signal?: AbortSignal): Promise<Agent> => {\n signal?.throwIfAborted()\n if (sessionId === '') throw new WorktreeToggleError('bad-request', 'a session id is required')\n const controller = ctx.get('sessionController')\n if (controller !== undefined) {\n const resolved = await controller.resolveAgent(sessionId as SessionId)\n if ('error' in resolved) {\n throw new WorktreeToggleError(resolved.error.code, resolved.error.message, {\n sessionId,\n })\n }\n return resolved.agent\n }\n const agent = ctx.agents.get(sessionId as SessionId)\n if (agent === undefined) {\n throw new WorktreeToggleError('session-not-found', `no live agent for session ${sessionId}`, {\n sessionId,\n })\n }\n return agent\n }\n\n const status = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<StatusValue> => {\n const agent = await sessionOf(request.sessionId, signal)\n // The immutable creation directory is the project anchor; the committed\n // working directory may already be a copy this session entered.\n const startDirectory = agent.session.header.cwd ?? ctx.workingDirectory.get(agent.session)\n let root: string\n try {\n root = await repositoryOf(ctx, startDirectory, signal)\n } catch (error) {\n if (error instanceof GitFailure) {\n return { supported: false, reason: 'not-a-git-repository', defaultEnabled: options.defaultEnabled }\n }\n throw error\n }\n const current = ctx.workingDirectory.get(agent.session)\n signal?.throwIfAborted()\n const sourceDirty = await changedFileCount(ctx, root, signal)\n const copy = await worktreeAt(ctx, current, root, signal)\n options.onScope?.(request.sessionId, copy === null ? null : { worktree: copy, repositoryRoot: root })\n return {\n supported: true,\n repositoryRoot: root,\n sessionWorktree: copy,\n sourceDirty,\n defaultEnabled: options.defaultEnabled,\n }\n }\n\n const prepareOnce = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<PrepareValue> => {\n const agent = await sessionOf(request.sessionId, signal)\n const current = await status(request, signal)\n if (!current.supported) {\n throw new WorktreeToggleError(\n 'not-a-git-repository',\n 'the session project is not inside a Git working tree',\n )\n }\n if (current.sessionWorktree !== null) {\n options.onScope?.(request.sessionId, {\n worktree: current.sessionWorktree,\n repositoryRoot: current.repositoryRoot,\n })\n return {\n state: 'existing',\n worktree: current.sessionWorktree,\n repositoryRoot: current.repositoryRoot,\n sourceDirty: current.sourceDirty,\n }\n }\n // Status anchors on the immutable creation directory, while the worktree\n // service creates relative to the session's CURRENT directory. Refuse to\n // create when the two name different repositories: a copy in the wrong\n // project would look like success and be worse than no copy.\n const working = ctx.workingDirectory.get(agent.session)\n const workingProject = await repositoryOf(ctx, working, signal)\n if (workingProject !== current.repositoryRoot) {\n throw new WorktreeToggleError(\n 'session-moved',\n `this session now works in ${workingProject}, not in its project ${current.repositoryRoot}`,\n { repositoryRoot: current.repositoryRoot, workingProject },\n )\n }\n // The base is resolved once, before any name attempt: a fetch is the one\n // costly step here, and a name collision must not repeat it.\n const base = await resolveBase(ctx, current.repositoryRoot, options.baseRef, signal)\n let lastFailure: unknown\n for (let attempt = 0; attempt < CREATE_ATTEMPTS; attempt += 1) {\n signal?.throwIfAborted()\n const name = worktreeName()\n try {\n const created = await ctx.worktrees.create(agent, { name, from: base.from }, signal)\n const worktree: WorktreeRef = { name: created.branch, path: created.path, branch: created.branch }\n // Record the claim where it survives a restart. No caller signal here:\n // the copy already exists and the session is already inside it, so\n // finishing this bookkeeping beats aborting half-done. A failed write\n // must not fail the creation either — the copy then reads as not-owned\n // later, is kept, and the notice says so.\n const baseCommit = await revParse(ctx, created.path, 'HEAD')\n if (baseCommit !== null) {\n try {\n await writeOwnership(ctx, created.path, {\n sessionId: request.sessionId,\n createdAt: Date.now(),\n baseCommit,\n })\n } catch {\n // Deliberately swallowed: see above.\n }\n }\n options.onScope?.(request.sessionId, { worktree, repositoryRoot: created.repositoryRoot })\n return {\n state: 'created',\n worktree,\n repositoryRoot: created.repositoryRoot,\n sourceDirty: current.sourceDirty,\n base: base.base,\n }\n } catch (error) {\n lastFailure = error\n const message = error instanceof Error ? error.message : String(error)\n // A name collision is the one failure a fresh name can fix. Everything\n // else (no commits yet, branch or path exists, Git missing) is reported\n // as-is: the caller must not be told \"created\" after a silent retry.\n if (!/already exists|worktree branch already exists|worktree path already exists/i.test(message)) {\n throw new WorktreeToggleError('worktree-create-failed', message, { name })\n }\n }\n }\n throw new WorktreeToggleError(\n 'worktree-create-failed',\n `could not allocate a free worktree name after ${CREATE_ATTEMPTS} attempts`,\n { cause: lastFailure instanceof Error ? lastFailure.message : String(lastFailure) },\n )\n }\n\n const prepare = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<PrepareValue> => {\n const running = pending.get(request.sessionId)\n if (running !== undefined) return running\n const operation = prepareOnce(request, signal)\n pending.set(request.sessionId, operation)\n try {\n return await operation\n } finally {\n if (pending.get(request.sessionId) === operation) pending.delete(request.sessionId)\n }\n }\n\n const scope = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<ScopeValue> => {\n const agent = await sessionOf(request.sessionId, signal)\n const current = ctx.workingDirectory.get(agent.session)\n const known = options.readScope?.(request.sessionId) ?? null\n // The host's own memory answers first; the directory check only re-derives\n // when the session has moved somewhere memory does not explain.\n const found = known !== null && contains(known.worktree.path, current)\n ? known\n : await detectSessionScope(ctx, agent, signal)\n if (found === null) return { inWorktree: false }\n signal?.throwIfAborted()\n const changedFiles = await changedFileCount(ctx, found.worktree.path, signal)\n options.onScope?.(request.sessionId, found)\n return {\n inWorktree: true,\n worktree: found.worktree,\n repositoryRoot: found.repositoryRoot,\n changedFiles,\n }\n }\n\n const list = async (\n request: { readonly path: string },\n signal?: AbortSignal,\n ): Promise<ListValue> => {\n const directory = absolutePath(request.path)\n let root: string\n try {\n root = await repositoryOf(ctx, directory, signal)\n } catch (error) {\n if (error instanceof GitFailure) return { supported: false, reason: 'not-a-git-repository' }\n throw error\n }\n const records = await listWorktreeRecords(ctx, root, signal)\n const items = await copyItems(ctx, root, records, signal)\n const orphans = await orphanBranches(ctx, root, records, signal)\n const kept = keptCopies.get(root)\n // Only copies that still exist count: a kept copy someone deleted by hand\n // must not inflate the number the sidebar shows.\n const keptCopiesStillHere = kept === undefined\n ? 0\n : items.filter(item => kept.has(item.name)).length\n return { supported: true, repositoryRoot: root, items, orphans, keptCopies: keptCopiesStillHere }\n }\n\n /**\n * Delete one branch by name.\n * @param root - canonical repository root.\n * @param branch - branch to delete; already validated by the caller.\n * @param signal - caller cancellation.\n * @throws {WorktreeToggleError} when Git refused.\n */\n const deleteBranch = async (root: string, branch: string, signal?: AbortSignal): Promise<void> => {\n const deleted = await runGit(ctx, root, ['branch', '-D', '--', branch], {\n signal,\n allowedExitCodes: [0, 1],\n })\n // Status 1 with \"not found\" is the branch already being gone (removed by\n // hand between the read and the click); that is not a failed delete.\n const gone = deleted.exitCode === 1 && /not found/i.test(deleted.stderr)\n if (deleted.exitCode !== 0 && !gone) throw branchDeleteFailure(branch, ['branch', '-D', branch], deleted)\n }\n\n /**\n * Delete a branch a prunable copy still holds.\n *\n * `git branch -D` refuses while any worktree registration names the branch,\n * even a registration whose directory is gone, so the branch is removed\n * through the ref itself. The registration still pins Git's bookkeeping, but\n * nothing can have the branch checked out any more: the directory is missing.\n * @param root - canonical repository root.\n * @param branch - branch to delete; already validated by the caller.\n * @param signal - caller cancellation.\n * @throws {WorktreeToggleError} when Git refused (for example a locked ref).\n */\n const deleteBranchByRef = async (root: string, branch: string, signal?: AbortSignal): Promise<void> => {\n const deleted = await runGit(ctx, root, ['update-ref', '-d', `refs/heads/${branch}`], {\n signal,\n allowedExitCodes: [0, 1],\n })\n if (deleted.exitCode !== 0) {\n throw branchDeleteFailure(branch, ['update-ref', '-d', branch], deleted)\n }\n }\n\n /**\n * Delete one clean copy and, unless it is detached, its branch.\n *\n * Both the panel's discard and the switch's own undo go through here, so the\n * dirty guard and the delete order have one home. A prunable copy — its\n * directory was deleted by hand — deletes the branch first and prunes the\n * registration only once that succeeded, so a refused delete cannot leave the\n * registration already gone.\n * @param root - canonical repository root.\n * @param name - copy name; already validated by the caller.\n * @param signal - caller cancellation.\n * @returns the removed copy's identity.\n */\n const removeCopy = async (\n root: string,\n name: string,\n signal?: AbortSignal,\n ): Promise<DiscardValue> => {\n const records = await listWorktreeRecords(ctx, root, signal)\n const items = await copyItems(ctx, root, records, signal)\n const item = items.find(entry => entry.name === name)\n if (item === undefined) {\n throw new WorktreeToggleError(\n 'worktree-not-found',\n `no worktree named ${name} in ${root}`,\n { name, repositoryRoot: root },\n )\n }\n if (item.changedFiles > 0) {\n throw new WorktreeToggleError(\n 'worktree-dirty',\n `${item.name} has ${item.changedFiles} uncommitted change(s); commit or discard them first`,\n { name: item.name, changedFiles: item.changedFiles },\n )\n }\n if (item.prunable) {\n if (item.branch !== null) await deleteBranchByRef(root, item.branch, signal)\n await runGit(ctx, root, ['worktree', 'prune'], { signal })\n } else {\n const removed = await runGit(ctx, root, ['worktree', 'remove', item.path], {\n signal,\n allowedExitCodes: [0, 128],\n })\n if (removed.exitCode !== 0) {\n throw new WorktreeToggleError(\n 'worktree-remove-failed',\n new GitFailure(['worktree', 'remove', item.path], removed).message,\n { name: item.name, path: item.path },\n )\n }\n if (item.branch !== null) await deleteBranch(root, item.branch, signal)\n }\n forget(root, item.name)\n return { removed: true, name: item.name, branch: item.branch, path: item.path }\n }\n\n const discard = async (\n request: { readonly path: string; readonly name: string },\n signal?: AbortSignal,\n ): Promise<DiscardValue> => {\n const directory = absolutePath(request.path)\n if (!isWorktreeName(request.name)) {\n throw new WorktreeToggleError('worktree-name-invalid', `not a worktree name: ${request.name}`)\n }\n const root = await repositoryOf(ctx, directory, signal)\n return await removeCopy(root, request.name, signal)\n }\n\n const discardBranch = async (\n request: { readonly path: string; readonly branch: string },\n signal?: AbortSignal,\n ): Promise<DiscardBranchValue> => {\n const directory = absolutePath(request.path)\n if (!isGeneratedName(request.branch)) {\n throw new WorktreeToggleError(\n 'branch-name-invalid',\n `not a branch this plugin generated: ${request.branch}`,\n )\n }\n const root = await repositoryOf(ctx, directory, signal)\n const records = await listWorktreeRecords(ctx, root, signal)\n if (records.some(record => record.branch === request.branch)) {\n throw new WorktreeToggleError(\n 'branch-in-use',\n `${request.branch} is checked out by a worktree; discard that copy instead`,\n { branch: request.branch },\n )\n }\n // Deleting a branch that is already gone is the second click of a double\n // click, not a failure: the caller's goal is reached either way.\n const known = new Set((await listBranchRefs(ctx, root, signal)).map(ref => ref.name))\n if (known.has(request.branch)) await deleteBranch(root, request.branch, signal)\n return { removed: true, branch: request.branch }\n }\n\n const release = async (\n request: { readonly sessionId: string },\n signal?: AbortSignal,\n ): Promise<ReleaseValue> => {\n // A create may still be in flight for this session: undo after it settles,\n // so a switch turned off mid-creation cannot race the copy into existence.\n const running = pending.get(request.sessionId)\n if (running !== undefined) {\n try {\n await running\n } catch {\n // Creation failed and already moved nothing; there is nothing to undo.\n }\n }\n signal?.throwIfAborted()\n const agent = await sessionOf(request.sessionId, signal)\n const current = ctx.workingDirectory.get(agent.session)\n // The immutable creation directory anchors the project, exactly as status\n // does: the committed directory may already be a copy this session entered.\n const anchor = agent.session.header.cwd ?? current\n let root: string\n try {\n root = await repositoryOf(ctx, anchor, signal)\n } catch (error) {\n if (error instanceof GitFailure) {\n throw new WorktreeToggleError(\n 'not-a-git-repository',\n 'the session project is not inside a Git working tree',\n )\n }\n throw error\n }\n const records = await listWorktreeRecords(ctx, root, signal)\n const items = await copyItems(ctx, root, records, signal)\n const path = await canonicalPath(current)\n const item = items.find(entry => contains(entry.path, path))\n if (item === undefined) {\n options.onScope?.(request.sessionId, null)\n return { directory: current, removed: null, kept: null, keptReason: null }\n }\n const copy = copyRef(item)\n signal?.throwIfAborted()\n let directory: string\n try {\n directory = await ctx.workingDirectory.set(agent, anchor, signal)\n } catch (error) {\n throw new WorktreeToggleError(\n 'release-failed',\n `could not move the session back to ${anchor}: ${error instanceof Error ? error.message : String(error)}`,\n { name: copy.name, path: copy.path },\n )\n }\n options.onScope?.(request.sessionId, null)\n const keepCopy = (reason: KeptReason, error?: string): ReleaseValue => {\n keep(root, item.name)\n return error === undefined\n ? { directory, removed: null, kept: copy, keptReason: reason }\n : { directory, removed: null, kept: copy, keptReason: reason, error }\n }\n // Ownership, not memory, decides: the claim was written into the copy's own\n // admin directory when it was created, so a restarted host still knows.\n const ownership = await readOwnership(ctx, item.path, signal)\n if (ownership === null || ownership.sessionId !== request.sessionId) {\n return keepCopy('not-owned')\n }\n if (item.changedFiles > 0) {\n return keepCopy('dirty', `${item.changedFiles} uncommitted change(s)`)\n }\n // Nothing may have happened inside the copy for it to be undone: the tip\n // must still be the commit it was created from.\n const tip = await revParse(ctx, item.path, 'HEAD', signal)\n if (tip === null || tip !== ownership.baseCommit) {\n let commits = 0\n try {\n commits = await revListCount(ctx, item.path, `${ownership.baseCommit}..HEAD`, signal)\n } catch {\n // The base commit is gone (for example after a rebase); the copy still\n // has content this call cannot undo, so it stays.\n }\n return keepCopy('dirty', `${commits} new commit(s)`)\n }\n // From here on a failure is reported, never swallowed: the caller is not\n // told the switch is off while a delete it attempted silently failed. The\n // copy (or the branch it left behind) is a cleanup item, so record it\n // before throwing — the sidebar count is how a person finds it again.\n let removed: DiscardValue\n try {\n removed = await removeCopy(root, item.name, signal)\n } catch (error) {\n keep(root, item.name)\n throw error\n }\n return {\n directory,\n removed: { name: removed.name, path: removed.path, branch: removed.branch },\n kept: null,\n keptReason: null,\n }\n }\n\n return { status, prepare, release, scope, list, discard, discardBranch }\n}\n","/**\n * The Connection fetch bridge: one exact POST route per verb on the shared\n * `/api` channel, speaking the `client-request` / `server-response`\n * envelope the browser's `connection.rpc.call` already implements.\n *\n * Exact routes are the only shape an out-of-tree plugin can claim: `/api` is\n * the one logical channel the API Gateway owns, so `rpc.handle` refuses it and\n * a second `rpc.intercept` throws. Every route below answers owner peers only,\n * which is what the local Web GUI is.\n * @module dsh-worktree-switch/host/routes\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ConnectionRpcResult } from '@deepseek-ai/dsh-client-connection'\nimport type {} from '@deepseek-ai/dsh-client-connection'\nimport type {\n DiscardBranchValue, DiscardValue, ListValue, PrepareValue, ReleaseValue, ScopeValue, StatusValue, WorktreeToggle,\n} from './handlers.ts'\nimport { WorktreeToggleError } from './handlers.ts'\n\n/** Absolute prefix every route below lives under. */\nexport const API_PREFIX = '/api/dsh-worktree-switch'\n\n/** Plugin id; the wire method prefix and the package name are the same string. */\nexport const PLUGIN_ID = 'dsh-worktree-switch'\n\n/** One route per verb. */\nexport const ENDPOINTS = ['status', 'prepare', 'release', 'scope', 'list', 'discard', 'discardBranch'] as const\n\n/** A verb this route table serves. */\nexport type Endpoint = (typeof ENDPOINTS)[number]\n\n/** A decoded client request. */\ninterface ClientRequest {\n readonly rpcId: string\n readonly method: string\n readonly payload: Record<string, unknown>\n}\n\n/** A payload object with the named string field, or the failure to send back. */\nfunction requiredString(\n payload: Record<string, unknown>,\n field: string,\n): { readonly value: string } | { readonly failure: WorktreeToggleError } {\n const value = payload[field]\n if (typeof value !== 'string' || value.trim() === '') {\n return {\n failure: new WorktreeToggleError('bad-request', `payload.${field} must be a non-empty string`),\n }\n }\n return { value }\n}\n\n/**\n * Decode the browser's `client-request` envelope.\n * @param body - parsed JSON body.\n * @returns the request, or null when the envelope is malformed.\n */\nexport function readClientRequest(body: unknown): ClientRequest | null {\n if (body === null || typeof body !== 'object' || Array.isArray(body)) return null\n const record = body as Record<string, unknown>\n if (record.type !== 'client-request') return null\n const { rpcId, method } = record\n if (typeof rpcId !== 'string' || rpcId === '') return null\n if (typeof method !== 'string' || method === '') return null\n const payload = record.payload\n return {\n rpcId,\n method,\n payload: payload !== null && typeof payload === 'object' && !Array.isArray(payload)\n ? payload as Record<string, unknown>\n : {},\n }\n}\n\n/** A successful result. */\nfunction ok<T>(value: T): ConnectionRpcResult<T> {\n return { ok: true, value }\n}\n\n/** A failed result carrying the stable code. */\nfunction fail(code: string, message: string, details: Record<string, unknown> = {}): ConnectionRpcResult<never> {\n return { ok: false, error: { code, message, details } }\n}\n\n/**\n * Dispatch one decoded verb.\n * @param toggle - the host face.\n * @param endpoint - the verb.\n * @param payload - its decoded payload.\n * @param signal - request cancellation.\n * @returns the wire result.\n */\nexport async function dispatch(\n toggle: WorktreeToggle,\n endpoint: Endpoint,\n payload: Record<string, unknown>,\n signal: AbortSignal,\n): Promise<ConnectionRpcResult<\n StatusValue | PrepareValue | ReleaseValue | ScopeValue | ListValue | DiscardValue | DiscardBranchValue\n>> {\n try {\n if (endpoint === 'status') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.status({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'prepare') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.prepare({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'release') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.release({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'scope') {\n const sessionId = requiredString(payload, 'sessionId')\n if ('failure' in sessionId) throw sessionId.failure\n return ok(await toggle.scope({ sessionId: sessionId.value }, signal))\n }\n if (endpoint === 'list') {\n const path = requiredString(payload, 'path')\n if ('failure' in path) throw path.failure\n return ok(await toggle.list({ path: path.value }, signal))\n }\n const path = requiredString(payload, 'path')\n if ('failure' in path) throw path.failure\n if (endpoint === 'discardBranch') {\n const branch = requiredString(payload, 'branch')\n if ('failure' in branch) throw branch.failure\n return ok(await toggle.discardBranch({ path: path.value, branch: branch.value }, signal))\n }\n const name = requiredString(payload, 'name')\n if ('failure' in name) throw name.failure\n return ok(await toggle.discard({ path: path.value, name: name.value }, signal))\n } catch (error) {\n if (error instanceof WorktreeToggleError) return fail(error.code, error.message, { ...error.details })\n return fail('internal', error instanceof Error ? error.message : String(error))\n }\n}\n\n/**\n * Register one exact route per verb on the shared `/api` channel.\n * @param ctx - Host context carrying `connection`.\n * @param toggle - the host face the routes call.\n */\nexport function registerWorktreeRoutes(ctx: Context, toggle: WorktreeToggle): void {\n for (const endpoint of ENDPOINTS) {\n ctx.effect(() => ctx.connection.fetch.register({\n path: `${API_PREFIX}/${endpoint}`,\n methods: ['POST'],\n requestBody: 'buffered',\n async fetch(request) {\n const contentType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n return new Response('content type must be application/json', { status: 415 })\n }\n let body: unknown\n try {\n body = await request.json()\n } catch {\n return new Response('body is not JSON', { status: 400 })\n }\n const message = readClientRequest(body)\n if (message === null) return new Response('invalid client-request message', { status: 400 })\n if (message.method !== `${PLUGIN_ID}/${endpoint}`) {\n return Response.json({\n type: 'server-response',\n rpcId: message.rpcId,\n result: fail('bad-request', 'RPC method does not match endpoint'),\n })\n }\n const result = await dispatch(toggle, endpoint, message.payload, request.signal)\n return Response.json({ type: 'server-response', rpcId: message.rpcId, result })\n },\n }), `${PLUGIN_ID} ${endpoint} route`)\n }\n}\n","/**\n * The copy a session is confirmed to run in, plus the one line the model reads\n * about it.\n *\n * The working-directory service already tells the model *where* it is. This\n * module answers the question that line does not: which checkout is the\n * session's workspace and which one is merely the project it was copied from.\n * The registry is fed by three sources — the switch verbs, `agent/created`\n * (resume), and `working-directory/change` (any `cd`) — so the line is never\n * stale, and it drops out entirely once the session leaves the copy.\n * @module dsh-worktree-switch/host/scope\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type {} from '@deepseek-ai/dsh-system-prompt'\nimport type { SessionScope } from '../types.ts'\nimport { contains } from './paths.ts'\n\n/** Sessions whose confirmed copy the prompt context reads. */\nexport class SessionScopeRegistry {\n private readonly scopes = new Map<string, SessionScope>()\n\n /**\n * Read one session's confirmed scope, without any filesystem work.\n * @param sessionId - wire session identity.\n * @returns the known copy, or null.\n */\n get(sessionId: string): SessionScope | null {\n return this.scopes.get(sessionId) ?? null\n }\n\n /**\n * Record that a session now runs in a copy.\n * @param sessionId - wire session identity.\n * @param scope - the confirmed copy and its project root.\n */\n remember(sessionId: string, scope: SessionScope): void {\n this.scopes.set(sessionId, scope)\n }\n\n /**\n * Drop a session's scope; a session with no entry gets no hint.\n * @param sessionId - wire session identity.\n */\n forget(sessionId: string): void {\n this.scopes.delete(sessionId)\n }\n\n /** Drop every entry; the plugin dropping its context drops its hints with it. */\n clear(): void {\n this.scopes.clear()\n }\n}\n\n/**\n * The model-facing line describing a worktree session's workspace.\n * @param scope - the session's confirmed copy.\n * @returns one sentence pair, JSON-quoted so paths survive the prompt.\n */\nexport function scopeHint(scope: SessionScope): string {\n return `This session is a linked Git worktree checkout at ${JSON.stringify(scope.worktree.path)} of the project ${JSON.stringify(scope.repositoryRoot)}. Run Git commands and file edits from the current working directory; the project checkout is the source, not this session's workspace.`\n}\n\n/** What the installer needs from the host half that owns Git. */\nexport interface WorktreeScopeWiring {\n /** Shared registry the prompt context reads. */\n readonly registry: SessionScopeRegistry\n /**\n * Re-derive one session's copy from its committed directory.\n * @param agent - live session owner.\n * @param signal - cancellation.\n * @returns the confirmed copy, or null when the session is not in one.\n */\n detect(agent: Agent, signal?: AbortSignal): Promise<SessionScope | null>\n}\n\n/**\n * Register the worktree scope as dynamic prompt context and keep it fresh.\n *\n * The context is `required`, so it survives compositions that suppress\n * optional runtime context, and it renders from the registry synchronously —\n * the expensive Git detection happens on session creation and directory\n * changes, never at request time.\n * @param ctx - host context carrying `systemPrompt`, `agents`, and `workingDirectory`.\n * @param wiring - registry plus the Git-backed detector.\n */\nexport function installWorktreeScope(ctx: Context, wiring: WorktreeScopeWiring): void {\n const { registry, detect } = wiring\n\n ctx.inject(['systemPrompt'], (scoped: Context) => {\n scoped.systemPrompt.context({\n name: 'worktree-switch:scope',\n order: scoped.systemPrompt.getContextOrder('WORKING_DIRECTORY') + 5,\n interpolate: false,\n required: true,\n text: (assembly) => {\n const agent = assembly.agent\n if (agent === undefined) return ''\n const sessionId = String(agent.session.id)\n const current = ctx.workingDirectory.get(agent.session)\n const scope = registry.get(sessionId)\n if (scope !== null && contains(scope.worktree.path, current)) return scopeHint(scope)\n // The committed directory is the authority: a session that left the\n // copy must not keep the line.\n if (scope !== null) registry.forget(sessionId)\n const anchor = agent.session.header.cwd ?? current\n // A session still at its creation directory is in no copy; only a\n // directory change inside the project is worth a Git look, and only\n // once per distinct directory value.\n if (current === anchor || !contains(anchor, current)) return ''\n if (lastChecked.get(sessionId) === current) return ''\n void refresh(agent, current)\n return ''\n },\n })\n })\n\n const inFlight = new Set<string>()\n /** The directory value each session was last inspected at, hit or miss. */\n const lastChecked = new Map<string, string>()\n\n /** Re-derive one live agent's scope, remembering or forgetting it. */\n const refresh = async (agent: Agent, current: string = ctx.workingDirectory.get(agent.session)): Promise<void> => {\n const sessionId = String(agent.session.id)\n const anchor = agent.session.header.cwd ?? current\n // Cheap reject before any Git work: a copy is always inside the project,\n // and a session still at its creation directory is in no copy.\n if (current === anchor || !contains(anchor, current)) {\n registry.forget(sessionId)\n return\n }\n if (inFlight.has(sessionId)) return\n inFlight.add(sessionId)\n lastChecked.set(sessionId, current)\n try {\n const found = await detect(agent)\n if (found === null) registry.forget(sessionId)\n else registry.remember(sessionId, found)\n } catch {\n registry.forget(sessionId)\n } finally {\n inFlight.delete(sessionId)\n }\n }\n\n // A resumed session enters no verb, so creation is where it is recognised; an\n // ordinary new session sits at its creation directory and stops at the cheap\n // check. The hook is serial, so the first request already sees the line.\n ctx.on('agent/created', async ({ agent }) => {\n await refresh(agent)\n return undefined\n })\n}\n\n","/**\n * Host half of the Worktree switch.\n *\n * It mounts no service itself: the bundle patch inserts the harness worktree\n * service (`@deepseek-ai/dsh-experimental-worktree`, which provides\n * `ctx.worktrees`) and this plugin's own row. This plugin exposes five verbs\n * over the Connection fetch bridge and drives that service for creation; it\n * also contributes one model-facing context line while a session runs in a\n * copy, and the browser half renders the switch and the panel.\n * @module dsh-worktree-switch\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport type {} from '@deepseek-ai/dsh-agent'\nimport type {} from '@deepseek-ai/dsh-experimental-worktree'\nimport type {} from '@deepseek-ai/dsh-subprocess'\nimport type {} from '@deepseek-ai/dsh-working-directory'\nimport { installSourceCheckoutGuard } from './host/guard.ts'\nimport { createWorktreeToggle, detectSessionScope } from './host/handlers.ts'\nimport { registerWorktreeRoutes } from './host/routes.ts'\nimport { installWorktreeScope, SessionScopeRegistry } from './host/scope.ts'\nimport type { BaseRef } from './types.ts'\n\n/** Plugin id: package name, bundle row id, browser module id. */\nexport const name = 'dsh-worktree-switch'\n\n/**\n * Services this plugin cannot work without.\n *\n * `worktrees` is provided by the row this bundle's patch inserts next to its\n * own, so Cordis holds this plugin until that row has mounted.\n */\nexport const inject = ['agents', 'connection', 'subprocess', 'worktrees', 'workingDirectory']\n\nexport type { BaseRef } from './types.ts'\n\n/** Row config accepted from `cordis.patch.yml`. */\nexport interface Config {\n /** Default for a project the user has never switched either way. */\n defaultEnabled: boolean\n /** Refuse shell commands that run Git against the project checkout while the session is in a copy. */\n guardSourceCheckout: boolean\n /** Commit a new copy starts from: the fetched remote default branch, or local HEAD. */\n baseRef: BaseRef\n}\n\n/** Schemastery schema for {@link Config}. */\nexport const Config: Schema<Config> = Schema.object({\n defaultEnabled: Schema.boolean()\n .default(true)\n .description('Start a new session in a worktree copy until the user turns the switch off for that project.'),\n guardSourceCheckout: Schema.boolean()\n .default(true)\n .description('While a session is in a copy, deny shell commands that run Git against the project checkout.'),\n baseRef: Schema.union([Schema.const('fresh'), Schema.const('head')])\n .default('fresh')\n .description(\n \"Where a new copy starts: 'fresh' fetches origin's default branch first; 'head' starts at local HEAD without network access.\",\n ),\n})\n\n/**\n * Mount the host half.\n * @param ctx - Host context.\n * @param config - validated row config.\n */\nexport function apply(ctx: Context, config: Config): void {\n const scopes = new SessionScopeRegistry()\n const toggle = createWorktreeToggle(ctx, {\n defaultEnabled: config.defaultEnabled,\n baseRef: config.baseRef,\n onScope: (sessionId, scope) => {\n if (scope === null) scopes.forget(sessionId)\n else scopes.remember(sessionId, scope)\n },\n readScope: sessionId => scopes.get(sessionId),\n })\n installWorktreeScope(ctx, {\n registry: scopes,\n detect: (agent, signal) => detectSessionScope(ctx, agent, signal),\n })\n if (config.guardSourceCheckout) installSourceCheckoutGuard(ctx, scopes)\n registerWorktreeRoutes(ctx, toggle)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-worktree-switch",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "A New Session switch that puts the conversation into a throwaway Git worktree under the project's worktree pool (`<project>/.dsh/worktrees` by default), plus a panel to inspect and discard the copies.",
5
5
  "keywords": [
6
6
  "dsh",