mixdog 1.3.2 → 1.3.4
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/package.json +8 -8
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +4 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/rules/lead/LEAD.md +5 -8
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +17 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +40 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +32 -11
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +13 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +68 -12
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +31 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +117 -19
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +147 -137
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +37 -74
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +13 -7
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +12 -20
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +24 -15
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +7 -5
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +18 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +8 -2
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +60 -3
- package/src/runtime/effort-judge/judge-client.mjs +61 -14
- package/src/runtime/effort-judge/model-install.mjs +34 -2
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/office/portable/portable-soffice.mjs +132 -30
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/bot-hops.mjs +16 -11
- package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/tool-execution-contract.mjs +4 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/turn-snapshot.mjs +233 -122
- package/src/runtime/shared/user-data-guard.mjs +51 -19
- package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +3 -1
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/session-runtime/agent-home-delivery.mjs +73 -91
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +2 -63
- package/src/session-runtime/agent-task-sessions.mjs +194 -64
- package/src/session-runtime/agent-tool-routing.mjs +49 -4
- package/src/session-runtime/boot/apis.mjs +3 -0
- package/src/session-runtime/boot/facade.mjs +5 -6
- package/src/session-runtime/boot/tools.mjs +23 -1
- package/src/session-runtime/held-requests.mjs +20 -25
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
- package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +44 -20
- package/src/session-runtime/services/agent-tool/render.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +12 -2
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
- package/src/session-runtime/setup-tool/executor.mjs +4 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
- package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
- package/src/session-runtime/tool-policy-refresh.mjs +1 -0
- package/src/session-runtime/tool-surface.mjs +9 -5
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run.mjs +5 -1
- package/src/session-runtime/usage-stats-api.mjs +1 -1
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +140 -23
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +11 -2
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +10 -0
- package/src/standalone/session-tool-output.mjs +1 -0
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/dist/index.mjs +403 -210
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +33 -17
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +11 -48
- package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
- package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
- package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
- package/src/session-runtime/room-member-gate.mjs +0 -61
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
* (runtime/shared/worktrees/manager.mjs), loaded on first use.
|
|
16
16
|
*/
|
|
17
17
|
import { randomUUID } from 'node:crypto';
|
|
18
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
18
19
|
import { isDeepStrictEqual } from 'node:util';
|
|
19
20
|
import {
|
|
20
21
|
WORKTREE_CARRY_CHOICES,
|
|
22
|
+
WORKTREE_SNAPSHOT_KIND,
|
|
21
23
|
branchMenuEntries,
|
|
22
24
|
isWorktreeBound,
|
|
23
25
|
isWorktreeError,
|
|
@@ -170,6 +172,27 @@ function assertRevision(revision, expectedRevision) {
|
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
|
|
175
|
+
/**
|
|
176
|
+
* The workspace methods every external caller (the runtime facade: desktop
|
|
177
|
+
* IPC, the daemon's `session.configure` lane, remote views) reaches: exactly
|
|
178
|
+
* the public input, so internal options (`ownTurn`, the calling-turn
|
|
179
|
+
* allowance of the Lead's own tool call) and the git tool's snapshot listing
|
|
180
|
+
* stay with in-process callers of the workspace API itself.
|
|
181
|
+
*/
|
|
182
|
+
export function publicWorkspaceMethods(api) {
|
|
183
|
+
return {
|
|
184
|
+
selectBranch: (input) => api.selectBranch(input),
|
|
185
|
+
requestWorktree: (input) => api.requestWorktree(input),
|
|
186
|
+
mergeWorkspace: (input) => api.mergeWorkspace(input),
|
|
187
|
+
removeWorktree: (input) => api.removeWorktree(input),
|
|
188
|
+
listWorktrees: () => api.listWorktrees(),
|
|
189
|
+
pruneWorktrees: () => api.pruneWorktrees(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// A settled turn's snapshot cleanup waits at most 2^5 times its first delay between attempts.
|
|
194
|
+
const SNAPSHOT_BACKOFF_STEPS = 5;
|
|
195
|
+
|
|
173
196
|
/** The in-flight marker state of each transition kind. */
|
|
174
197
|
const TRANSITION_STATE = { worktree: 'provisioning', branch: 'switching', 'remove-worktree': 'removing' };
|
|
175
198
|
|
|
@@ -220,12 +243,18 @@ export function createWorkspaceTransitions({
|
|
|
220
243
|
newTxId = randomUUID,
|
|
221
244
|
planWorktreeSetup = defaultPlanWorktreeSetup,
|
|
222
245
|
runWorktreeSetup = defaultRunWorktreeSetup,
|
|
246
|
+
snapshotRetryMs = 2_000,
|
|
247
|
+
snapshotRetryLimit = 20,
|
|
223
248
|
}) {
|
|
224
249
|
const worktreeSettings = () => normalizeWorktreeSettings(getConfig?.()?.worktree);
|
|
225
250
|
let pendingIntent = creationIntent(initialIntent, worktreeSettings());
|
|
226
|
-
// The
|
|
227
|
-
//
|
|
228
|
-
|
|
251
|
+
// The creation txIds of the read-only snapshots the current admitted turn
|
|
252
|
+
// made. Each turn owns its own set, so its cleanup never touches a later
|
|
253
|
+
// turn's snapshots (a txId, unlike a freed slug, is never reused).
|
|
254
|
+
let turnSnapshots = null;
|
|
255
|
+
// The running cleanups of settled turns' snapshots (controller → session id).
|
|
256
|
+
const snapshotCleanups = new Map();
|
|
257
|
+
let disposed = false;
|
|
229
258
|
const setupListeners = new Set();
|
|
230
259
|
let managerPromise = null;
|
|
231
260
|
const backend = () => {
|
|
@@ -480,49 +509,26 @@ export function createWorkspaceTransitions({
|
|
|
480
509
|
try {
|
|
481
510
|
await assertLeadIdle(session, { ownTurn: options.ownTurn === true });
|
|
482
511
|
const run = { signal: options.signal, txId, journal };
|
|
512
|
+
// A new checkout gets its dependencies prepared at the start of the
|
|
513
|
+
// first turn a session runs there (runPendingSetup, from its registry entry).
|
|
483
514
|
if (target.kind === 'worktree') {
|
|
484
|
-
|
|
515
|
+
return await steps.enterWorktree(session, previous, {
|
|
485
516
|
...run,
|
|
486
517
|
name: target.name,
|
|
487
518
|
nameHint: options.nameHint,
|
|
488
519
|
confirmLarge: options.confirmLarge,
|
|
489
520
|
changes: options.changes,
|
|
490
521
|
});
|
|
491
|
-
// A new checkout gets its dependencies prepared at the start of the
|
|
492
|
-
// session's next turn there (prepareTurnWorkspace).
|
|
493
|
-
if (created) {
|
|
494
|
-
pendingSetup = {
|
|
495
|
-
...created,
|
|
496
|
-
sessionId: session.id,
|
|
497
|
-
worktreeId: entered.workspace.worktreeId,
|
|
498
|
-
cwd: entered.workspace.cwd,
|
|
499
|
-
mainDir: previous.projectPath,
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
return entered;
|
|
503
522
|
}
|
|
504
523
|
if (target.kind === 'branch') {
|
|
505
|
-
|
|
524
|
+
return await steps.enterProjectBranch(session, previous, {
|
|
506
525
|
...run,
|
|
507
526
|
branch: target.branch,
|
|
508
527
|
create: target.create,
|
|
509
528
|
confirmLarge: options.confirmLarge,
|
|
510
529
|
});
|
|
511
|
-
if (created && entered.workspace?.worktreeId) {
|
|
512
|
-
pendingSetup = {
|
|
513
|
-
...created,
|
|
514
|
-
sessionId: session.id,
|
|
515
|
-
worktreeId: entered.workspace.worktreeId,
|
|
516
|
-
cwd: entered.workspace.cwd,
|
|
517
|
-
mainDir: previous.projectPath,
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
return entered;
|
|
521
530
|
}
|
|
522
|
-
|
|
523
|
-
// The session's retained worktree is gone: its pending setup goes with it.
|
|
524
|
-
if (pendingSetup?.sessionId === session.id) pendingSetup = null;
|
|
525
|
-
return removed;
|
|
531
|
+
return await steps.removeRetainedWorktree(session, previous, { ...run, force: target.force });
|
|
526
532
|
} catch (error) {
|
|
527
533
|
// Whatever already moved (cwd, record, prompts, worker roots, lease)
|
|
528
534
|
// goes back exactly; the previous workspace stays authoritative.
|
|
@@ -534,16 +540,26 @@ export function createWorkspaceTransitions({
|
|
|
534
540
|
}
|
|
535
541
|
}
|
|
536
542
|
|
|
537
|
-
/**
|
|
538
|
-
|
|
543
|
+
/**
|
|
544
|
+
* The one entry of every transition. `ownTurn` (internal: the Lead's own
|
|
545
|
+
* tool call) tolerates the calling turn; `txId` keys the creation.
|
|
546
|
+
*/
|
|
547
|
+
async function runTransition(sessionRef, target, options = {}, { ownTurn = false, txId = '' } = {}) {
|
|
539
548
|
const session = hostedSession(sessionRef);
|
|
540
549
|
return transition(session, normalizeTarget(target), {
|
|
541
550
|
expectedRevision: validRevision(options.expectedRevision),
|
|
542
551
|
confirmLarge: options.confirmLarge === true,
|
|
543
552
|
signal: options.signal,
|
|
553
|
+
ownTurn: ownTurn === true,
|
|
554
|
+
txId,
|
|
544
555
|
});
|
|
545
556
|
}
|
|
546
557
|
|
|
558
|
+
/** @type {import('../../runtime/shared/worktrees/contract.mjs').ApplyWorkspaceTransition} */
|
|
559
|
+
async function applyWorkspaceTransition(sessionRef, target, options = {}) {
|
|
560
|
+
return runTransition(sessionRef, target, options);
|
|
561
|
+
}
|
|
562
|
+
|
|
547
563
|
/** The pending worktree intent that applies to `session`'s next turn, if any. */
|
|
548
564
|
function intentFor(session) {
|
|
549
565
|
const intent = pendingIntent;
|
|
@@ -628,29 +644,35 @@ export function createWorkspaceTransitions({
|
|
|
628
644
|
* output tail) and what was skipped. A failure or timeout is in the note
|
|
629
645
|
* and the turn goes on; a cancel aborts the turn like any other stop. The
|
|
630
646
|
* worktree stays either way. The setup belongs to the worktree it was made
|
|
631
|
-
* for
|
|
632
|
-
*
|
|
633
|
-
*
|
|
647
|
+
* for, whichever path made it (a worktree task, a branch selection, `git
|
|
648
|
+
* worktree add`): its registry entry keeps it pending (manager
|
|
649
|
+
* claimWorktreeSetup) until the first turn a session runs there, never in
|
|
650
|
+
* another folder.
|
|
634
651
|
*/
|
|
635
652
|
async function runPendingSetup(session, signal) {
|
|
636
|
-
const pending = pendingSetup;
|
|
637
|
-
if (!pending || pending.sessionId !== session.id) return;
|
|
638
653
|
const here = readSessionWorkspace(session);
|
|
639
|
-
if (!isWorktreeBound(here)
|
|
640
|
-
|
|
654
|
+
if (!isWorktreeBound(here)) return;
|
|
655
|
+
const claimed = await (await backend()).claimWorktreeSetup(here.worktreeId);
|
|
656
|
+
if (!claimed) return;
|
|
641
657
|
const id = newTxId();
|
|
642
|
-
const report = {
|
|
658
|
+
const report = {
|
|
659
|
+
root: claimed.prepared?.root || here.projectPath,
|
|
660
|
+
prepared: claimed.prepared,
|
|
661
|
+
dependencyNotes: [],
|
|
662
|
+
installs: null,
|
|
663
|
+
notes: [],
|
|
664
|
+
};
|
|
643
665
|
const emit = (event) => {
|
|
644
666
|
for (const listener of setupListeners) {
|
|
645
667
|
try {
|
|
646
|
-
listener({ ...event, id, sessionId: session.id, cwd:
|
|
668
|
+
listener({ ...event, id, sessionId: session.id, cwd: here.cwd });
|
|
647
669
|
} catch {
|
|
648
670
|
/* a viewer's failure never stops the setup */
|
|
649
671
|
}
|
|
650
672
|
}
|
|
651
673
|
};
|
|
652
674
|
try {
|
|
653
|
-
const plan = await planWorktreeSetup({ worktreeDir:
|
|
675
|
+
const plan = await planWorktreeSetup({ worktreeDir: here.cwd, mainDir: here.projectPath, signal });
|
|
654
676
|
report.dependencyNotes = plan.notes;
|
|
655
677
|
if (plan.commands.length) {
|
|
656
678
|
report.installs = await runWorktreeSetup({ commands: plan.commands, signal, onProgress: emit });
|
|
@@ -715,30 +737,104 @@ export function createWorkspaceTransitions({
|
|
|
715
737
|
return { liveSessionIds, listSessionIds: listStoredSessionIds, manager: await backend() };
|
|
716
738
|
}
|
|
717
739
|
|
|
718
|
-
/**
|
|
719
|
-
|
|
720
|
-
|
|
740
|
+
/**
|
|
741
|
+
* Every managed worktree with who retains it (the manual list). Read-only
|
|
742
|
+
* snapshots (no branch) only with `includeSnapshots` (the git tool); the
|
|
743
|
+
* desktop list (DesktopManagedWorktreeRow, a branch per row) never gets them.
|
|
744
|
+
*/
|
|
745
|
+
async function listWorktrees({ includeSnapshots = false } = {}) {
|
|
746
|
+
const rows = await maintenance.listWorktreesWithRetention(await maintenanceScope());
|
|
747
|
+
return includeSnapshots ? rows : rows.filter((row) => row.kind !== WORKTREE_SNAPSHOT_KIND);
|
|
721
748
|
}
|
|
722
749
|
|
|
723
750
|
/** The manual prune: clean, merged worktrees no stored or live session retains. */
|
|
724
|
-
async function pruneWorktrees() {
|
|
725
|
-
return maintenance.pruneWorktrees(await maintenanceScope());
|
|
751
|
+
async function pruneWorktrees({ repoCommonDir = null } = {}) {
|
|
752
|
+
return maintenance.pruneWorktrees({ ...(await maintenanceScope()), repoCommonDir });
|
|
726
753
|
}
|
|
727
754
|
|
|
728
755
|
/** After `sessionId` was deleted: autoCleanup removes its clean, merged worktree. */
|
|
729
756
|
async function cleanupDeletedSession(sessionId) {
|
|
757
|
+
// Its snapshots go with it below; no turn cleanup keeps retrying them.
|
|
758
|
+
cancelSnapshotCleanups(sessionId);
|
|
730
759
|
return maintenance.cleanupDeletedSessionWorktree(sessionId, {
|
|
731
760
|
manager: await backend(),
|
|
732
761
|
autoCleanup: worktreeSettings().autoCleanup,
|
|
733
762
|
});
|
|
734
763
|
}
|
|
735
764
|
|
|
736
|
-
/**
|
|
737
|
-
|
|
738
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Turn admission (see admission-gate admitTurn); resolves the release. The
|
|
767
|
+
* release also starts the removal of the read-only snapshots this turn
|
|
768
|
+
* made — only those (removeTurnSnapshots).
|
|
769
|
+
*/
|
|
770
|
+
async function admitTurnWorkspace({ signal } = {}) {
|
|
771
|
+
const release = await admitTurn({ sessionId: getSession()?.id || '', cwd: () => getCurrentCwd() || '', signal });
|
|
772
|
+
const made = new Set();
|
|
773
|
+
turnSnapshots = made;
|
|
774
|
+
return () => {
|
|
775
|
+
release();
|
|
776
|
+
if (turnSnapshots === made) turnSnapshots = null;
|
|
777
|
+
const sessionId = getSession()?.id;
|
|
778
|
+
if (made.size && sessionId) void removeTurnSnapshots(sessionId, [...made]);
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Remove a settled turn's snapshots (by creation `txIds`) once nothing runs
|
|
784
|
+
* in them: a worker turn or background shell holding a workspace lease
|
|
785
|
+
* inside one keeps it (manager removeSessionSnapshots reports it `in-use`).
|
|
786
|
+
* Those are retried with a doubling delay (`snapshotRetryMs`, at most
|
|
787
|
+
* 2^SNAPSHOT_BACKOFF_STEPS times it) for at most `snapshotRetryLimit`
|
|
788
|
+
* attempts; a snapshot removal that fails otherwise is not retried. What is
|
|
789
|
+
* left then — or when the runtime is disposed, the session closes, is
|
|
790
|
+
* deleted or is no longer the one hosted here — is the prune's and the
|
|
791
|
+
* startup reconcile's. Admission is not held meanwhile: the removal only
|
|
792
|
+
* ever names this turn's snapshots.
|
|
793
|
+
*/
|
|
794
|
+
async function removeTurnSnapshots(sessionId, txIds) {
|
|
795
|
+
if (disposed) return;
|
|
796
|
+
const controller = new AbortController();
|
|
797
|
+
snapshotCleanups.set(controller, sessionId);
|
|
798
|
+
try {
|
|
799
|
+
let pending = txIds;
|
|
800
|
+
for (let attempt = 0; pending.length && attempt < snapshotRetryLimit; attempt++) {
|
|
801
|
+
if (attempt > 0) {
|
|
802
|
+
const wait = snapshotRetryMs * 2 ** Math.min(attempt - 1, SNAPSHOT_BACKOFF_STEPS);
|
|
803
|
+
await delay(wait, undefined, { ref: false, signal: controller.signal });
|
|
804
|
+
}
|
|
805
|
+
const session = getSession();
|
|
806
|
+
if (session?.id !== sessionId || session.closed === true) return;
|
|
807
|
+
const report = await (await backend()).removeSessionSnapshots(sessionId, { txIds: pending });
|
|
808
|
+
pending = report.kept.filter((row) => row.reason === 'in-use').map((row) => row.txId);
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
// Cancelled, or the backend failed: the rest is the maintenance's.
|
|
812
|
+
} finally {
|
|
813
|
+
snapshotCleanups.delete(controller);
|
|
814
|
+
}
|
|
739
815
|
}
|
|
740
816
|
|
|
741
|
-
|
|
817
|
+
/** Stop the snapshot cleanups of `sessionId` (every session's when null). */
|
|
818
|
+
function cancelSnapshotCleanups(sessionId = null) {
|
|
819
|
+
for (const [controller, owner] of snapshotCleanups) {
|
|
820
|
+
if (sessionId === null || owner === sessionId) controller.abort();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** Runtime teardown: no snapshot cleanup runs on (prune / startup reconcile finish them). */
|
|
825
|
+
function dispose() {
|
|
826
|
+
disposed = true;
|
|
827
|
+
cancelSnapshotCleanups();
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Run in `branch`'s checkout (see enterProjectBranch). `ownTurn`
|
|
832
|
+
* (internal: the Lead's own tool call) tolerates the calling turn.
|
|
833
|
+
*/
|
|
834
|
+
async function selectBranch(
|
|
835
|
+
{ branch, create = false, expectedRevision, confirmLarge = false, txId = '' } = {},
|
|
836
|
+
{ ownTurn = false } = {}
|
|
837
|
+
) {
|
|
742
838
|
const session = await ensureSession();
|
|
743
839
|
const current = currentWorkspace(session);
|
|
744
840
|
if (current.branch === branch && current.worktreeId) {
|
|
@@ -747,14 +843,127 @@ export function createWorkspaceTransitions({
|
|
|
747
843
|
return { revision: current.revision, workspace: current };
|
|
748
844
|
}
|
|
749
845
|
const target = { kind: 'branch', branch, create: create === true };
|
|
750
|
-
const result = await
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
846
|
+
const result = await runTransition(
|
|
847
|
+
{ sessionId: session.id },
|
|
848
|
+
target,
|
|
849
|
+
{ expectedRevision, confirmLarge },
|
|
850
|
+
{ ownTurn, txId: String(txId || '').trim() }
|
|
851
|
+
);
|
|
754
852
|
pendingIntent = null;
|
|
755
853
|
return result;
|
|
756
854
|
}
|
|
757
855
|
|
|
856
|
+
/** WORKTREE_PROJECT_MISMATCH unless `repoCommonDir` (when given) is the session Project's repository. */
|
|
857
|
+
async function sessionProject(session, repoCommonDir) {
|
|
858
|
+
const workspace = currentWorkspace(session);
|
|
859
|
+
const project = await inspectProject(workspace.projectPath);
|
|
860
|
+
if (repoCommonDir && !samePath(repoCommonDir, project.repoCommonDir)) {
|
|
861
|
+
throw worktreeError(
|
|
862
|
+
'WORKTREE_PROJECT_MISMATCH',
|
|
863
|
+
"Managed worktrees belong to this session's Project repository; run the command there.",
|
|
864
|
+
{ projectPath: workspace.projectPath, repoCommonDir }
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
return { workspace, project };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* A managed worktree for `branch` without moving the session (`git
|
|
872
|
+
* worktree add`): retained by this session like a branch it selected, so
|
|
873
|
+
* entering it later (selectBranch) reuses it. A branch already checked out
|
|
874
|
+
* elsewhere is WORKTREE_BRANCH_OCCUPIED, except in this session's own
|
|
875
|
+
* managed worktree, which is returned as it is. `startPoint` (with
|
|
876
|
+
* `create`): the commit the new branch starts at.
|
|
877
|
+
*/
|
|
878
|
+
async function addBranchWorktree({
|
|
879
|
+
branch,
|
|
880
|
+
create = false,
|
|
881
|
+
startPoint = null,
|
|
882
|
+
confirmLarge = false,
|
|
883
|
+
txId = '',
|
|
884
|
+
repoCommonDir = null,
|
|
885
|
+
} = {}) {
|
|
886
|
+
const session = hostedSession(null);
|
|
887
|
+
const { workspace } = await sessionProject(session, repoCommonDir);
|
|
888
|
+
const backendApi = await backend();
|
|
889
|
+
const { occupancy } = await backendApi.listBranchOccupancy({
|
|
890
|
+
projectPath: workspace.projectPath,
|
|
891
|
+
sessionId: session.id,
|
|
892
|
+
});
|
|
893
|
+
const held = occupancy.find((row) => row.branch === branch && row.kind !== 'free');
|
|
894
|
+
if (held && !(held.kind === 'session' && held.sessionId === session.id)) {
|
|
895
|
+
throw worktreeError('WORKTREE_BRANCH_OCCUPIED', `Branch ${branch} is checked out in ${held.path}.`, {
|
|
896
|
+
branch,
|
|
897
|
+
path: held.path,
|
|
898
|
+
kind: held.kind,
|
|
899
|
+
...(held.sessionId ? { sessionId: held.sessionId } : {}),
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
const resolved = await backendApi.resolveBranchWorkspace({
|
|
903
|
+
projectPath: workspace.projectPath,
|
|
904
|
+
branch,
|
|
905
|
+
create: create === true,
|
|
906
|
+
startPoint,
|
|
907
|
+
sessionId: session.id,
|
|
908
|
+
runtimeToken,
|
|
909
|
+
txId: String(txId || '').trim() || newTxId(),
|
|
910
|
+
confirmLarge: confirmLarge === true,
|
|
911
|
+
});
|
|
912
|
+
return {
|
|
913
|
+
path: resolved.path,
|
|
914
|
+
branch,
|
|
915
|
+
worktreeId: resolved.entry?.id ?? null,
|
|
916
|
+
created: resolved.created === true,
|
|
917
|
+
baseCommit: resolved.entry?.baseCommit ?? null,
|
|
918
|
+
mergeTarget: resolved.entry?.mergeTarget ?? null,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* A read-only snapshot of `commit` for this session's turn (`git worktree
|
|
924
|
+
* add --detach`): a detached managed checkout no session works in,
|
|
925
|
+
* removed when the turn settles (and nothing runs in it any more). The
|
|
926
|
+
* session does not move. Only inside an admitted turn: a snapshot always
|
|
927
|
+
* belongs to the turn that made it.
|
|
928
|
+
*/
|
|
929
|
+
async function createSnapshot({ commit = 'HEAD', confirmLarge = false, txId = '', repoCommonDir = null } = {}) {
|
|
930
|
+
const session = hostedSession(null);
|
|
931
|
+
const made = turnSnapshots;
|
|
932
|
+
if (!made) throw new Error('A read-only snapshot belongs to a turn; none is running in this session.');
|
|
933
|
+
const { workspace } = await sessionProject(session, repoCommonDir);
|
|
934
|
+
const tx = String(txId || '').trim() || newTxId();
|
|
935
|
+
const entry = await (await backend()).createSnapshotWorktree({
|
|
936
|
+
projectPath: workspace.projectPath,
|
|
937
|
+
sessionId: session.id,
|
|
938
|
+
runtimeToken,
|
|
939
|
+
commit,
|
|
940
|
+
txId: tx,
|
|
941
|
+
confirmLarge: confirmLarge === true,
|
|
942
|
+
});
|
|
943
|
+
made.add(tx);
|
|
944
|
+
return entry;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Remove this session's snapshot at `path` now; a dirty one only with
|
|
949
|
+
* `force`, and none while a worker or background shell runs in it.
|
|
950
|
+
*/
|
|
951
|
+
async function removeSnapshot({ path, force = false } = {}) {
|
|
952
|
+
const session = hostedSession(null);
|
|
953
|
+
const backendApi = await backend();
|
|
954
|
+
const entry = await backendApi.getWorktreeByPath(path);
|
|
955
|
+
if (entry?.kind !== WORKTREE_SNAPSHOT_KIND) throw worktreeError('WORKTREE_MISSING', undefined, { path });
|
|
956
|
+
if (entry.holderSessionId !== session.id) {
|
|
957
|
+
throw worktreeError(
|
|
958
|
+
'WORKTREE_BUSY_JOBS',
|
|
959
|
+
"This snapshot belongs to another session's turn; it is removed when that turn ends, or by a prune.",
|
|
960
|
+
{ reason: 'snapshot-held', sessionId: entry.holderSessionId, entryId: entry.id }
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
const removed = await backendApi.removeSnapshotWorktree({ entryId: entry.id, force: force === true });
|
|
964
|
+
return { ...removed, entryId: entry.id, path: entry.path };
|
|
965
|
+
}
|
|
966
|
+
|
|
758
967
|
/**
|
|
759
968
|
* "워크트리": re-enter the retained worktree now, or arm the lazy creation
|
|
760
969
|
* for the next message (confirmLarge acknowledges WORKTREE_LARGE_REPO_CONFIRM).
|
|
@@ -772,16 +981,13 @@ export function createWorkspaceTransitions({
|
|
|
772
981
|
* `leave` starts the worktree at HEAD and leaves them; `carry` moves them
|
|
773
982
|
* into it (the result's `changes` reports which). Re-entering a retained
|
|
774
983
|
* worktree never touches them.
|
|
984
|
+
*
|
|
985
|
+
* `ownTurn` (internal: the Lead's own tool call) tolerates the calling turn.
|
|
775
986
|
*/
|
|
776
|
-
async function requestWorktree(
|
|
777
|
-
name = null,
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
carryChanges,
|
|
781
|
-
prompt = '',
|
|
782
|
-
txId = '',
|
|
783
|
-
expectedRevision,
|
|
784
|
-
} = {}) {
|
|
987
|
+
async function requestWorktree(
|
|
988
|
+
{ name = null, confirmLarge = false, provision = false, carryChanges, prompt = '', txId = '', expectedRevision } = {},
|
|
989
|
+
{ ownTurn = false } = {}
|
|
990
|
+
) {
|
|
785
991
|
const nameHint = validName(name);
|
|
786
992
|
const expected = validRevision(expectedRevision);
|
|
787
993
|
if (carryChanges !== undefined && carryChanges !== null && !WORKTREE_CARRY_CHOICES.includes(carryChanges)) {
|
|
@@ -794,10 +1000,11 @@ export function createWorkspaceTransitions({
|
|
|
794
1000
|
if (current) {
|
|
795
1001
|
if (isWorktreeBound(current)) return { revision: current.revision, workspace: current, pending: false };
|
|
796
1002
|
if (await (await backend()).getSessionWorktree(session.id)) {
|
|
797
|
-
const entered = await
|
|
1003
|
+
const entered = await runTransition(
|
|
798
1004
|
{ sessionId: session.id },
|
|
799
1005
|
{ kind: 'worktree' },
|
|
800
|
-
{ expectedRevision: expected }
|
|
1006
|
+
{ expectedRevision: expected },
|
|
1007
|
+
{ ownTurn }
|
|
801
1008
|
);
|
|
802
1009
|
return { ...entered, pending: false };
|
|
803
1010
|
}
|
|
@@ -817,6 +1024,7 @@ export function createWorkspaceTransitions({
|
|
|
817
1024
|
...(await provisionIntent(session, intent, txId, prompt, {
|
|
818
1025
|
expectedRevision: expected,
|
|
819
1026
|
changes: carryChanges ?? 'ask',
|
|
1027
|
+
ownTurn: ownTurn === true,
|
|
820
1028
|
})),
|
|
821
1029
|
pending: false,
|
|
822
1030
|
};
|
|
@@ -997,10 +1205,14 @@ export function createWorkspaceTransitions({
|
|
|
997
1205
|
requestWorktree,
|
|
998
1206
|
mergeWorkspace,
|
|
999
1207
|
removeWorktree,
|
|
1208
|
+
addBranchWorktree,
|
|
1209
|
+
createSnapshot,
|
|
1210
|
+
removeSnapshot,
|
|
1000
1211
|
switchProject,
|
|
1001
1212
|
listWorktrees,
|
|
1002
1213
|
pruneWorktrees,
|
|
1003
1214
|
cleanupDeletedSession,
|
|
1004
1215
|
onWorktreeSetup,
|
|
1216
|
+
dispose,
|
|
1005
1217
|
};
|
|
1006
1218
|
}
|