harnery 0.43.0 → 0.45.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/README.md +3 -3
- package/dist/commander.js +1 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +41 -48
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +38 -11
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +10 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -7
- package/dist/commands/files.d.ts +2 -2
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +7 -4
- package/dist/commands/governor.js +22 -10
- package/dist/commands/inbox.d.ts +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +55 -2
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +28 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +26 -17
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +7 -2
- package/dist/commands/toc.d.ts +14 -0
- package/dist/commands/toc.d.ts.map +1 -1
- package/dist/commands/toc.js +7 -4
- package/dist/commands/tunnel.d.ts +15 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +453 -153
- package/dist/commands/turn-rollback.d.ts +5 -0
- package/dist/commands/turn-rollback.d.ts.map +1 -0
- package/dist/commands/turn-rollback.js +76 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +68 -30
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +18 -0
- package/dist/core/agents/live-authority-v3.d.ts +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +28 -7
- package/dist/core/agents/mailbox.d.ts +9 -8
- package/dist/core/agents/mailbox.d.ts.map +1 -1
- package/dist/core/agents/mailbox.js +24 -17
- package/dist/core/agents/render/prompt-context.d.ts +2 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +8 -12
- package/dist/core/agents/render/session-context.d.ts +2 -8
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +7 -30
- package/dist/core/agents/rules/stop-hook.js +3 -3
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +25 -14
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +5 -0
- package/dist/core/agents/state/proc-start.d.ts +19 -1
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +46 -1
- package/dist/core/agents/state/process-handle.d.ts +26 -0
- package/dist/core/agents/state/process-handle.d.ts.map +1 -0
- package/dist/core/agents/state/process-handle.js +121 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +180 -70
- package/dist/core/agents/state/unchanged-file.d.ts +44 -0
- package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
- package/dist/core/agents/state/unchanged-file.js +390 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
- package/dist/core/artifacts/delivery-card.js +2 -1
- package/dist/core/context/index.d.ts +22 -10
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +381 -119
- package/dist/core/context/observation.d.ts +26 -0
- package/dist/core/context/observation.d.ts.map +1 -0
- package/dist/core/context/observation.js +11 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +25 -14
- package/dist/core/diagnostics/path-identity.d.ts +3 -0
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
- package/dist/core/diagnostics/path-identity.js +12 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
- package/dist/core/diagnostics/sanitize.js +4 -3
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +29 -0
- package/dist/core/events/v3/generation-custody.d.ts +5 -0
- package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
- package/dist/core/events/v3/generation-custody.js +50 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +77 -64
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +14 -1
- package/dist/core/governor/service-read.d.ts +10 -2
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +105 -22
- package/dist/core/governor/service.d.ts +20 -2
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +72 -47
- package/dist/core/hooks/cli.js +33 -5
- package/dist/core/inbox/contract.d.ts +2 -1
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/index.d.ts +2 -0
- package/dist/core/inbox/index.d.ts.map +1 -1
- package/dist/core/inbox/index.js +2 -0
- package/dist/core/inbox/peer-messages.d.ts +37 -0
- package/dist/core/inbox/peer-messages.d.ts.map +1 -0
- package/dist/core/inbox/peer-messages.js +72 -0
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +67 -21
- package/dist/core/inbox/targets.d.ts +22 -0
- package/dist/core/inbox/targets.d.ts.map +1 -0
- package/dist/core/inbox/targets.js +108 -0
- package/dist/core/inbox/watcher.js +7 -3
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +17 -2
- package/dist/core/resources/contract.d.ts +3 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/service-status.d.ts +14 -1
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +23 -13
- package/dist/core/resources/service.d.ts +7 -13
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +34 -91
- package/dist/core/resources/status.d.ts +2 -5
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +36 -7
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +2 -9
- package/dist/core/servers/index.d.ts +34 -22
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +297 -144
- package/dist/core/servers/tunnels.d.ts +7 -7
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +47 -10
- package/dist/core/supervisor/contract.d.ts +12 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.js +2 -1
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +54 -9
- package/dist/core/supervisor/service.d.ts +7 -1
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +59 -29
- package/dist/core/supervisor/services.d.ts.map +1 -1
- package/dist/core/supervisor/services.js +22 -1
- package/dist/core/supervisor/status.d.ts +17 -2
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +70 -12
- package/dist/core/supervisor/timeline.d.ts.map +1 -1
- package/dist/core/supervisor/timeline.js +2 -1
- package/dist/core/turn-rollback/content.d.ts +53 -0
- package/dist/core/turn-rollback/content.d.ts.map +1 -0
- package/dist/core/turn-rollback/content.js +396 -0
- package/dist/core/turn-rollback/index.d.ts +72 -0
- package/dist/core/turn-rollback/index.d.ts.map +1 -0
- package/dist/core/turn-rollback/index.js +443 -0
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +105 -32
- package/dist/core/workflow/failure-evidence.d.ts +16 -0
- package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
- package/dist/core/workflow/failure-evidence.js +114 -0
- package/dist/core/workflow/index.d.ts +5 -3
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +3 -2
- package/dist/core/workflow/live-session-v3.d.ts +6 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +126 -5
- package/dist/core/workflow/proof.d.ts +5 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +115 -20
- package/dist/core/workflow/recovery-audit.d.ts +30 -0
- package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
- package/dist/core/workflow/recovery-audit.js +116 -0
- package/dist/core/workflow/run-state.d.ts +20 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +141 -32
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +19 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +13 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +19 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +14 -0
- package/dist/core/workflow/types.d.ts +64 -17
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +0 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +0 -1
- package/dist/core/workflow/workspaces/leases.d.ts +3 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +30 -16
- package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +328 -8
- package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/ownership.js +194 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +40 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +14 -0
- package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +42 -10
- package/dist/core/workflow/workspaces/state.d.ts +1 -0
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +21 -1
- package/dist/lib/browser/windows-chrome.d.ts +100 -12
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +262 -73
- package/dist/lib/docs-links.d.ts.map +1 -1
- package/dist/lib/docs-links.js +45 -2
- package/dist/lib/guarded-remove.d.ts +7 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +106 -12
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -0
- package/dist/lib/tunnel/state.d.ts +13 -6
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +58 -12
- package/dist/lib/zip/index.d.ts +41 -0
- package/dist/lib/zip/index.d.ts.map +1 -0
- package/dist/lib/zip/index.js +398 -0
- package/package.json +12 -1
- package/src/commander.ts +10 -0
- package/src/commands/agents.ts +54 -63
- package/src/commands/browse.ts +50 -14
- package/src/commands/diagnostics.ts +10 -0
- package/src/commands/doctor.ts +26 -7
- package/src/commands/files.ts +13 -5
- package/src/commands/governor.ts +23 -10
- package/src/commands/inbox.ts +52 -2
- package/src/commands/rm.ts +43 -4
- package/src/commands/servers.ts +27 -26
- package/src/commands/supervisor.ts +7 -2
- package/src/commands/toc.ts +14 -6
- package/src/commands/tunnel.ts +533 -166
- package/src/commands/turn-rollback.ts +111 -0
- package/src/commands/workflow.ts +89 -30
- package/src/core/agents/coord-client.ts +20 -0
- package/src/core/agents/live-authority-v3.ts +17 -0
- package/src/core/agents/live-lifecycle-v3.ts +52 -14
- package/src/core/agents/mailbox.ts +26 -17
- package/src/core/agents/render/prompt-context.ts +10 -12
- package/src/core/agents/render/session-context.ts +9 -32
- package/src/core/agents/rules/stop-hook.ts +3 -3
- package/src/core/agents/state/heartbeat-writer.ts +29 -12
- package/src/core/agents/state/live-coordination-writer.ts +29 -0
- package/src/core/agents/state/pidmap.ts +13 -0
- package/src/core/agents/state/proc-start.ts +62 -2
- package/src/core/agents/state/process-handle.ts +165 -0
- package/src/core/agents/state/stale-sweep.ts +247 -82
- package/src/core/agents/state/unchanged-file.ts +456 -0
- package/src/core/artifacts/delivery-card.ts +7 -1
- package/src/core/context/index.ts +446 -136
- package/src/core/context/observation.ts +29 -0
- package/src/core/diagnostics/bundle.ts +23 -18
- package/src/core/diagnostics/path-identity.ts +15 -0
- package/src/core/diagnostics/sanitize.ts +4 -3
- package/src/core/events/v3/authority-outbox.ts +56 -0
- package/src/core/events/v3/generation-custody.ts +59 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
- package/src/core/events/v3/producers/recorder.ts +94 -68
- package/src/core/governor/planning.ts +20 -8
- package/src/core/governor/service-read.ts +128 -20
- package/src/core/governor/service.ts +119 -42
- package/src/core/hooks/cli.ts +33 -5
- package/src/core/inbox/contract.ts +2 -1
- package/src/core/inbox/index.ts +2 -0
- package/src/core/inbox/peer-messages.ts +94 -0
- package/src/core/inbox/service.ts +88 -22
- package/src/core/inbox/targets.ts +148 -0
- package/src/core/inbox/watcher.ts +8 -9
- package/src/core/journal/index.ts +19 -2
- package/src/core/resources/contract.ts +4 -0
- package/src/core/resources/service-status.ts +35 -12
- package/src/core/resources/service.ts +57 -95
- package/src/core/resources/status.ts +57 -8
- package/src/core/semantic/service.ts +2 -8
- package/src/core/servers/index.ts +376 -141
- package/src/core/servers/tunnels.ts +57 -13
- package/src/core/supervisor/contract.ts +13 -0
- package/src/core/supervisor/explanations.ts +4 -1
- package/src/core/supervisor/findings.ts +68 -10
- package/src/core/supervisor/service.ts +87 -26
- package/src/core/supervisor/services.ts +26 -8
- package/src/core/supervisor/status.ts +89 -12
- package/src/core/supervisor/timeline.ts +2 -1
- package/src/core/turn-rollback/content.ts +499 -0
- package/src/core/turn-rollback/index.ts +580 -0
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +131 -49
- package/src/core/workflow/failure-evidence.ts +141 -0
- package/src/core/workflow/index.ts +12 -2
- package/src/core/workflow/live-session-v3.ts +146 -5
- package/src/core/workflow/proof.ts +134 -26
- package/src/core/workflow/recovery-audit.ts +181 -0
- package/src/core/workflow/run-state.ts +165 -34
- package/src/core/workflow/spawn-claude.ts +18 -0
- package/src/core/workflow/spawn-codex.ts +13 -0
- package/src/core/workflow/spawn-cursor.ts +18 -0
- package/src/core/workflow/spawn-opencode.ts +14 -0
- package/src/core/workflow/types.ts +51 -17
- package/src/core/workflow/workspaces/index.ts +0 -1
- package/src/core/workflow/workspaces/leases.ts +48 -17
- package/src/core/workflow/workspaces/local-git.ts +388 -7
- package/src/core/workflow/workspaces/ownership.ts +232 -0
- package/src/core/workflow/workspaces/reclaim.ts +62 -0
- package/src/core/workflow/workspaces/recovery.ts +45 -10
- package/src/core/workflow/workspaces/state.ts +20 -0
- package/src/lib/browser/windows-chrome.ts +346 -86
- package/src/lib/docs-links.ts +49 -2
- package/src/lib/guarded-remove.ts +132 -14
- package/src/lib/identities/assume.ts +4 -0
- package/src/lib/tunnel/state.ts +70 -15
- package/src/lib/zip/index.ts +483 -0
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
11
11
|
import { stateDirMode, stateFileMode } from "../../storage/modes.ts";
|
|
12
|
+
import { beginWorkflowRecovery, validateWorkflowRecoveryAuthorization } from "../recovery-audit.ts";
|
|
13
|
+
import { acquireWorkflowResumeLease } from "../run-state.ts";
|
|
12
14
|
import { WorkspaceAttestationError } from "./attestation-error.ts";
|
|
15
|
+
import { cleanupWorkspace } from "./cleanup.ts";
|
|
13
16
|
import {
|
|
14
17
|
git,
|
|
15
18
|
gitMaybe,
|
|
@@ -22,6 +25,11 @@ import {
|
|
|
22
25
|
worktreeInventory,
|
|
23
26
|
} from "./git.ts";
|
|
24
27
|
import { acquireNoClobberLease } from "./leases.ts";
|
|
28
|
+
import {
|
|
29
|
+
claimOverlapsWorkspace,
|
|
30
|
+
reserveWorkspaceReclaim,
|
|
31
|
+
type WorkspaceReclaimReservation,
|
|
32
|
+
} from "./ownership.ts";
|
|
25
33
|
import {
|
|
26
34
|
assertContainedExisting,
|
|
27
35
|
assertPathIdentity,
|
|
@@ -36,13 +44,24 @@ import {
|
|
|
36
44
|
type ValidatedRoot,
|
|
37
45
|
validateConfiguredRoot,
|
|
38
46
|
} from "./paths.ts";
|
|
39
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
prepareReclaim,
|
|
49
|
+
type ReclaimLeaseHolder,
|
|
50
|
+
type ReclaimMode,
|
|
51
|
+
type ReclaimPreparation,
|
|
52
|
+
WorkflowReclaimError,
|
|
53
|
+
type WorkspaceReclaimOptions,
|
|
54
|
+
type WorkspaceReclaimPreview,
|
|
55
|
+
type WorkspaceReclaimResult,
|
|
56
|
+
} from "./reclaim.ts";
|
|
57
|
+
import { hasActiveWorkflowEvidence, readWorkflowLiveness } from "./recovery.ts";
|
|
40
58
|
import {
|
|
41
59
|
appendWorkspaceEvent,
|
|
42
60
|
fileSha256,
|
|
43
61
|
readWorkflowSupplement,
|
|
44
62
|
readWorkspaceBinding,
|
|
45
63
|
readWorkspaceClaim,
|
|
64
|
+
readWorkspaceClaims,
|
|
46
65
|
readWorkspaceEvents,
|
|
47
66
|
stableDigest,
|
|
48
67
|
type WorkspaceClaim,
|
|
@@ -86,11 +105,18 @@ export interface LocalGitWorktreeProviderOptions {
|
|
|
86
105
|
descriptorPathsSupported?: () => boolean;
|
|
87
106
|
}
|
|
88
107
|
|
|
108
|
+
export interface LocalGitWorktreeProvider extends WorkspaceProvider {
|
|
109
|
+
reclaim(
|
|
110
|
+
binding: WorkspaceBinding,
|
|
111
|
+
options: WorkspaceReclaimOptions,
|
|
112
|
+
): Promise<WorkspaceReclaimResult>;
|
|
113
|
+
}
|
|
114
|
+
|
|
89
115
|
export function createLocalGitWorktreeProvider(
|
|
90
116
|
options: LocalGitWorktreeProviderOptions,
|
|
91
|
-
):
|
|
117
|
+
): LocalGitWorktreeProvider {
|
|
92
118
|
const coordRoot = resolve(options.coordRoot);
|
|
93
|
-
|
|
119
|
+
const provider: LocalGitWorktreeProvider = {
|
|
94
120
|
probe: (input) => probe(input, options.descriptorPathsSupported),
|
|
95
121
|
allocate: (request) => allocate(coordRoot, request),
|
|
96
122
|
readBinding: async (binding) => {
|
|
@@ -103,7 +129,9 @@ export function createLocalGitWorktreeProvider(
|
|
|
103
129
|
previewIntegration: (input) => previewIntegration(coordRoot, input),
|
|
104
130
|
applyAuthorizedIntegration: (input) => applyAuthorizedIntegration(coordRoot, input),
|
|
105
131
|
cleanup: (binding, intent) => cleanup(coordRoot, binding, intent),
|
|
132
|
+
reclaim: (binding, reclaimOptions) => reclaim(coordRoot, provider, binding, reclaimOptions),
|
|
106
133
|
};
|
|
134
|
+
return provider;
|
|
107
135
|
}
|
|
108
136
|
|
|
109
137
|
export async function probe(
|
|
@@ -783,6 +811,325 @@ function cancelUnderLease(
|
|
|
783
811
|
return { ...base, status: "cancelled" };
|
|
784
812
|
}
|
|
785
813
|
|
|
814
|
+
function reclaimPreview(
|
|
815
|
+
coordRoot: string,
|
|
816
|
+
binding: WorkspaceBinding,
|
|
817
|
+
mode: ReclaimMode,
|
|
818
|
+
): WorkspaceReclaimPreview {
|
|
819
|
+
const preview: WorkspaceReclaimPreview = {
|
|
820
|
+
action: "preview",
|
|
821
|
+
run_id: binding.run_id,
|
|
822
|
+
mode,
|
|
823
|
+
liveness: readWorkflowLiveness(coordRoot, binding.run_id),
|
|
824
|
+
evidence_sha256: reclaimEvidenceDigest(coordRoot, binding),
|
|
825
|
+
ownership: "unproven",
|
|
826
|
+
dirty_paths: null,
|
|
827
|
+
lease_holder: reclaimLeaseHolders(coordRoot, binding),
|
|
828
|
+
};
|
|
829
|
+
try {
|
|
830
|
+
const current = attestSync(coordRoot, binding);
|
|
831
|
+
if (current.status === "ok") {
|
|
832
|
+
preview.ownership = "ok";
|
|
833
|
+
preview.dirty_paths = [...(current.repository?.dirty_paths ?? [])];
|
|
834
|
+
} else {
|
|
835
|
+
preview.ownership_detail =
|
|
836
|
+
current.provider_drift.join("; ") || "ownership attestation failed";
|
|
837
|
+
}
|
|
838
|
+
} catch (error) {
|
|
839
|
+
preview.ownership_detail = (error as Error).message;
|
|
840
|
+
}
|
|
841
|
+
return preview;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function reclaimEvidenceDigest(coordRoot: string, binding: WorkspaceBinding): string {
|
|
845
|
+
const evidence = (name: string): string | null => {
|
|
846
|
+
try {
|
|
847
|
+
return readFileSync(join(coordRoot, ".harnery", "workflows", binding.run_id, name), "utf8");
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
return stableDigest({
|
|
854
|
+
binding,
|
|
855
|
+
proof: evidence("proof.json"),
|
|
856
|
+
transcript: evidence("transcript.jsonl"),
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function assertReclaimLiveness(
|
|
861
|
+
coordRoot: string,
|
|
862
|
+
binding: WorkspaceBinding,
|
|
863
|
+
recovery?: WorkspaceReclaimOptions["unknownLiveness"],
|
|
864
|
+
): void {
|
|
865
|
+
const liveness = readWorkflowLiveness(coordRoot, binding.run_id);
|
|
866
|
+
if (liveness === "active" || (recovery && hasActiveWorkflowEvidence(coordRoot, binding.run_id))) {
|
|
867
|
+
throw new WorkflowReclaimError(
|
|
868
|
+
"workflow_reclaim_run_live",
|
|
869
|
+
`run ${binding.run_id} has active evidence; its workspace was left untouched`,
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (recovery && reclaimEvidenceDigest(coordRoot, binding) !== recovery.evidenceSha256) {
|
|
873
|
+
throw new WorkflowReclaimError(
|
|
874
|
+
"workflow_reclaim_recovery_mismatch",
|
|
875
|
+
"workflow reclaim requires the exact current preview evidence digest",
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
if (liveness === "inactive") return;
|
|
879
|
+
if (liveness === "unknown" && recovery) return;
|
|
880
|
+
throw new WorkflowReclaimError(
|
|
881
|
+
"workflow_reclaim_liveness_unknown",
|
|
882
|
+
`run ${binding.run_id} has missing, corrupt, or ambiguous liveness evidence; its workspace was left untouched`,
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function assertReclaimOwnership(coordRoot: string, binding: WorkspaceBinding): WorkspaceClaim {
|
|
887
|
+
try {
|
|
888
|
+
const claim = requiredClaim(coordRoot, binding);
|
|
889
|
+
const current = attestSync(coordRoot, binding);
|
|
890
|
+
if (current.status !== "ok") {
|
|
891
|
+
throw new Error(current.provider_drift.join("; ") || "ownership attestation failed");
|
|
892
|
+
}
|
|
893
|
+
return claim;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", (error as Error).message);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function assertPeerWorkspaceAuthority(coordRoot: string, claim: WorkspaceClaim): void {
|
|
900
|
+
try {
|
|
901
|
+
for (const peer of readWorkspaceClaims(coordRoot, PROVIDER_ID)) {
|
|
902
|
+
if (peer.binding_id === claim.binding_id) continue;
|
|
903
|
+
const binding = readWorkspaceBinding(coordRoot, PROVIDER_ID, peer.binding_id);
|
|
904
|
+
const events = readWorkspaceEvents(coordRoot, PROVIDER_ID, peer.binding_id);
|
|
905
|
+
if (binding) requiredClaim(coordRoot, binding);
|
|
906
|
+
else if (events.some((event) => event.event === "bound")) {
|
|
907
|
+
throw new Error("retained peer binding authority is missing");
|
|
908
|
+
}
|
|
909
|
+
if (!claimOverlapsWorkspace(peer, claim.workspace_root)) continue;
|
|
910
|
+
// Process termination, archival and an expired reservation do not release
|
|
911
|
+
// a resource. Require the provider's release record AND current absence.
|
|
912
|
+
if (binding && events.some((event) => event.event === "released")) {
|
|
913
|
+
const current = attestSync(coordRoot, binding);
|
|
914
|
+
if (current.status === "ok" && current.resource_state === "released") continue;
|
|
915
|
+
}
|
|
916
|
+
throw new Error(`retained peer workspace ${peer.binding_id} overlaps the reclaim path`);
|
|
917
|
+
}
|
|
918
|
+
} catch (error) {
|
|
919
|
+
throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", (error as Error).message);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function reclaimAuthorityGuard(
|
|
924
|
+
coordRoot: string,
|
|
925
|
+
binding: WorkspaceBinding,
|
|
926
|
+
claim: WorkspaceClaim,
|
|
927
|
+
reservation: WorkspaceReclaimReservation,
|
|
928
|
+
recovery?: WorkspaceReclaimOptions["unknownLiveness"],
|
|
929
|
+
): void {
|
|
930
|
+
try {
|
|
931
|
+
reservation.assertCurrent();
|
|
932
|
+
assertReclaimLiveness(coordRoot, binding, recovery);
|
|
933
|
+
assertReclaimOwnership(coordRoot, binding);
|
|
934
|
+
assertPeerWorkspaceAuthority(coordRoot, claim);
|
|
935
|
+
} catch (error) {
|
|
936
|
+
if (error instanceof WorkflowReclaimError) throw error;
|
|
937
|
+
throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", (error as Error).message);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function reclaim(
|
|
942
|
+
coordRoot: string,
|
|
943
|
+
provider: WorkspaceProvider,
|
|
944
|
+
binding: WorkspaceBinding,
|
|
945
|
+
options: WorkspaceReclaimOptions,
|
|
946
|
+
): Promise<WorkspaceReclaimResult> {
|
|
947
|
+
if (options.mode !== "salvage" && options.mode !== "discard") {
|
|
948
|
+
throw new Error("unsupported workspace reclaim mode");
|
|
949
|
+
}
|
|
950
|
+
if (!options.yes) return reclaimPreview(coordRoot, binding, options.mode);
|
|
951
|
+
if (options.unknownLiveness) validateWorkflowRecoveryAuthorization(options.unknownLiveness);
|
|
952
|
+
|
|
953
|
+
// Liveness comes before terminal-proof cleanup helpers. A live run may not
|
|
954
|
+
// have a proof yet, and a missing proof must never be discovered after reset.
|
|
955
|
+
assertReclaimLiveness(coordRoot, binding, options.unknownLiveness);
|
|
956
|
+
const claim = assertReclaimOwnership(coordRoot, binding);
|
|
957
|
+
let reservation: WorkspaceReclaimReservation;
|
|
958
|
+
try {
|
|
959
|
+
reservation = reserveWorkspaceReclaim(coordRoot, claim);
|
|
960
|
+
} catch (error) {
|
|
961
|
+
throw new WorkflowReclaimError("workflow_reclaim_lease_held", (error as Error).message);
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
reclaimAuthorityGuard(coordRoot, binding, claim, reservation, options.unknownLiveness);
|
|
965
|
+
let releaseResume: () => void;
|
|
966
|
+
try {
|
|
967
|
+
releaseResume = acquireWorkflowResumeLease(coordRoot, binding.run_id);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
throw new WorkflowReclaimError("workflow_reclaim_lease_held", (error as Error).message);
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
let releaseBinding: () => void;
|
|
973
|
+
try {
|
|
974
|
+
releaseBinding = acquireLease(
|
|
975
|
+
coordRoot,
|
|
976
|
+
workspaceLockPath(coordRoot, PROVIDER_ID, binding.binding_id),
|
|
977
|
+
claim,
|
|
978
|
+
"binding",
|
|
979
|
+
);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
throw new WorkflowReclaimError("workflow_reclaim_lease_held", (error as Error).message);
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
let releaseRepository: () => void;
|
|
985
|
+
try {
|
|
986
|
+
releaseRepository = acquireRepositoryLease(coordRoot, claim);
|
|
987
|
+
} catch (error) {
|
|
988
|
+
throw new WorkflowReclaimError("workflow_reclaim_lease_held", (error as Error).message);
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
assertReclaimLiveness(coordRoot, binding, options.unknownLiveness);
|
|
992
|
+
assertReclaimOwnership(coordRoot, binding);
|
|
993
|
+
const preview = reclaimPreview(coordRoot, binding, options.mode);
|
|
994
|
+
const audit = options.unknownLiveness
|
|
995
|
+
? beginWorkflowRecovery(coordRoot, {
|
|
996
|
+
run_id: binding.run_id,
|
|
997
|
+
action: "reclaim_unknown_liveness",
|
|
998
|
+
actor: options.unknownLiveness.actor,
|
|
999
|
+
reason: options.unknownLiveness.reason,
|
|
1000
|
+
target_sha256: options.unknownLiveness.evidenceSha256,
|
|
1001
|
+
target: {
|
|
1002
|
+
binding_id: binding.binding_id,
|
|
1003
|
+
workspace_root: binding.workspace_root,
|
|
1004
|
+
mode: options.mode,
|
|
1005
|
+
liveness: preview.liveness,
|
|
1006
|
+
},
|
|
1007
|
+
})
|
|
1008
|
+
: undefined;
|
|
1009
|
+
let preparation: ReclaimPreparation | undefined;
|
|
1010
|
+
// Keep ordinary cleanup's terminal-evidence checks, intent and receipts.
|
|
1011
|
+
// Its provider callback uses the leases already held here instead of
|
|
1012
|
+
// trying to acquire them again. Preparation occurs only after all of
|
|
1013
|
+
// cleanup's path, ownership, ref and registration checks have succeeded.
|
|
1014
|
+
const cleanupResult = await cleanupWorkspace({
|
|
1015
|
+
coordRoot,
|
|
1016
|
+
runId: binding.run_id,
|
|
1017
|
+
provider: {
|
|
1018
|
+
...provider,
|
|
1019
|
+
cleanup: async (candidate, intent) => {
|
|
1020
|
+
assertReclaimLiveness(coordRoot, candidate, options.unknownLiveness);
|
|
1021
|
+
assertReclaimOwnership(coordRoot, candidate);
|
|
1022
|
+
return cleanupUnderLease(coordRoot, candidate, claim, intent, {
|
|
1023
|
+
mode: options.mode,
|
|
1024
|
+
recovery: options.unknownLiveness,
|
|
1025
|
+
beforeEffect: () => {
|
|
1026
|
+
audit?.assertCurrent();
|
|
1027
|
+
reclaimAuthorityGuard(
|
|
1028
|
+
coordRoot,
|
|
1029
|
+
candidate,
|
|
1030
|
+
claim,
|
|
1031
|
+
reservation,
|
|
1032
|
+
options.unknownLiveness,
|
|
1033
|
+
);
|
|
1034
|
+
},
|
|
1035
|
+
beforeRefEffect: () => {
|
|
1036
|
+
audit?.assertCurrent();
|
|
1037
|
+
reservation.assertCurrent();
|
|
1038
|
+
assertReclaimLiveness(coordRoot, candidate, options.unknownLiveness);
|
|
1039
|
+
requiredClaim(coordRoot, candidate);
|
|
1040
|
+
assertPeerWorkspaceAuthority(coordRoot, claim);
|
|
1041
|
+
},
|
|
1042
|
+
prepared: (value) => {
|
|
1043
|
+
preparation = value;
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
1048
|
+
});
|
|
1049
|
+
if (
|
|
1050
|
+
!preparation &&
|
|
1051
|
+
cleanupResult.status !== "released" &&
|
|
1052
|
+
cleanupResult.status !== "already_released"
|
|
1053
|
+
) {
|
|
1054
|
+
throw new WorkflowReclaimError(
|
|
1055
|
+
"workflow_reclaim_ownership_unproven",
|
|
1056
|
+
("reason" in cleanupResult ? cleanupResult.reason : undefined) ??
|
|
1057
|
+
"workspace cleanup preconditions could not be proved",
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
if (cleanupResult.status === "released" || cleanupResult.status === "already_released")
|
|
1061
|
+
audit?.complete();
|
|
1062
|
+
return {
|
|
1063
|
+
...preview,
|
|
1064
|
+
action: "reclaimed",
|
|
1065
|
+
lease_holder: [],
|
|
1066
|
+
preparation: preparation ?? {
|
|
1067
|
+
action: "already_gone",
|
|
1068
|
+
detail: "the workspace is already released",
|
|
1069
|
+
},
|
|
1070
|
+
cleanup: cleanupResult,
|
|
1071
|
+
};
|
|
1072
|
+
} finally {
|
|
1073
|
+
releaseRepository();
|
|
1074
|
+
}
|
|
1075
|
+
} finally {
|
|
1076
|
+
releaseBinding();
|
|
1077
|
+
}
|
|
1078
|
+
} finally {
|
|
1079
|
+
releaseResume();
|
|
1080
|
+
}
|
|
1081
|
+
} finally {
|
|
1082
|
+
reservation.release();
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function reclaimLeaseHolders(coordRoot: string, binding: WorkspaceBinding): ReclaimLeaseHolder[] {
|
|
1087
|
+
const paths: Array<[ReclaimLeaseHolder["scope"], string]> = [
|
|
1088
|
+
["binding", workspaceLockPath(coordRoot, PROVIDER_ID, binding.binding_id)],
|
|
1089
|
+
];
|
|
1090
|
+
if (binding.repository) {
|
|
1091
|
+
const key = stableDigest({ common: binding.repository.common_dir.identity });
|
|
1092
|
+
paths.push([
|
|
1093
|
+
"repository",
|
|
1094
|
+
join(coordRoot, ".harnery", "workspaces", PROVIDER_ID, ".leases", `repository-${key}.lock`),
|
|
1095
|
+
]);
|
|
1096
|
+
}
|
|
1097
|
+
const holders: ReclaimLeaseHolder[] = [];
|
|
1098
|
+
try {
|
|
1099
|
+
const owner = JSON.parse(
|
|
1100
|
+
readFileSync(join(coordRoot, ".harnery", "workflows", binding.run_id, "resume.lock"), "utf8"),
|
|
1101
|
+
) as Record<string, unknown>;
|
|
1102
|
+
holders.push({
|
|
1103
|
+
scope: "resume",
|
|
1104
|
+
pid: typeof owner.pid === "number" ? owner.pid : undefined,
|
|
1105
|
+
host: typeof owner.host === "string" ? owner.host : undefined,
|
|
1106
|
+
});
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
1109
|
+
holders.push({ scope: "resume", detail: "lease holder could not be read" });
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
for (const [scope, path] of paths) {
|
|
1113
|
+
try {
|
|
1114
|
+
const owner = JSON.parse(readFileSync(join(path, "current"), "utf8")) as Record<
|
|
1115
|
+
string,
|
|
1116
|
+
unknown
|
|
1117
|
+
>;
|
|
1118
|
+
holders.push({
|
|
1119
|
+
scope,
|
|
1120
|
+
owner_id: typeof owner.owner_id === "string" ? owner.owner_id : undefined,
|
|
1121
|
+
pid: typeof owner.pid === "number" ? owner.pid : undefined,
|
|
1122
|
+
host: typeof owner.host === "string" ? owner.host : undefined,
|
|
1123
|
+
});
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
1126
|
+
holders.push({ scope, detail: "lease holder could not be read" });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return holders;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
786
1133
|
async function cleanup(
|
|
787
1134
|
coordRoot: string,
|
|
788
1135
|
binding: WorkspaceBinding,
|
|
@@ -799,7 +1146,17 @@ async function cleanup(
|
|
|
799
1146
|
try {
|
|
800
1147
|
const releaseRepository = acquireRepositoryLease(coordRoot, claim);
|
|
801
1148
|
try {
|
|
802
|
-
|
|
1149
|
+
const reservation = reserveWorkspaceReclaim(coordRoot, claim);
|
|
1150
|
+
try {
|
|
1151
|
+
const authorityGuard = () => {
|
|
1152
|
+
reservation.assertCurrent();
|
|
1153
|
+
assertPeerWorkspaceAuthority(coordRoot, claim);
|
|
1154
|
+
};
|
|
1155
|
+
authorityGuard();
|
|
1156
|
+
return cleanupUnderLease(coordRoot, binding, claim, intent, undefined, authorityGuard);
|
|
1157
|
+
} finally {
|
|
1158
|
+
reservation.release();
|
|
1159
|
+
}
|
|
803
1160
|
} finally {
|
|
804
1161
|
releaseRepository();
|
|
805
1162
|
}
|
|
@@ -813,6 +1170,14 @@ function cleanupUnderLease(
|
|
|
813
1170
|
binding: WorkspaceBinding,
|
|
814
1171
|
claim: WorkspaceClaim,
|
|
815
1172
|
intent: WorkspaceCleanupIntent,
|
|
1173
|
+
reclaimRequest?: {
|
|
1174
|
+
mode: ReclaimMode;
|
|
1175
|
+
recovery?: WorkspaceReclaimOptions["unknownLiveness"];
|
|
1176
|
+
beforeEffect: () => void;
|
|
1177
|
+
beforeRefEffect: () => void;
|
|
1178
|
+
prepared: (value: ReclaimPreparation) => void;
|
|
1179
|
+
},
|
|
1180
|
+
beforeRemoval?: () => void,
|
|
816
1181
|
): WorkspaceCleanupResult {
|
|
817
1182
|
const repository = binding.repository;
|
|
818
1183
|
if (
|
|
@@ -857,7 +1222,7 @@ function cleanupUnderLease(
|
|
|
857
1222
|
};
|
|
858
1223
|
}
|
|
859
1224
|
const liveness = readWorkflowLiveness(coordRoot, binding.run_id);
|
|
860
|
-
if (liveness !== "inactive") {
|
|
1225
|
+
if (liveness !== "inactive" && !(liveness === "unknown" && reclaimRequest?.recovery)) {
|
|
861
1226
|
return {
|
|
862
1227
|
...base,
|
|
863
1228
|
status: "blocked",
|
|
@@ -871,11 +1236,11 @@ function cleanupUnderLease(
|
|
|
871
1236
|
return { ...base, status: "blocked", reason: "ownership attestation failed" };
|
|
872
1237
|
}
|
|
873
1238
|
if (
|
|
874
|
-
(before.repository?.dirty_paths.length ?? 0) > 0 ||
|
|
1239
|
+
(!reclaimRequest && (before.repository?.dirty_paths.length ?? 0) > 0) ||
|
|
875
1240
|
(before.repository?.conflicts.length ?? 0) > 0 ||
|
|
876
1241
|
(before.repository?.operation_in_progress.length ?? 0) > 0
|
|
877
1242
|
) {
|
|
878
|
-
appendWorkspaceEvent(coordRoot, claim, "preserved_dirty");
|
|
1243
|
+
if (!reclaimRequest) appendWorkspaceEvent(coordRoot, claim, "preserved_dirty");
|
|
879
1244
|
return {
|
|
880
1245
|
...base,
|
|
881
1246
|
status: "preserved_dirty",
|
|
@@ -949,6 +1314,19 @@ function cleanupUnderLease(
|
|
|
949
1314
|
};
|
|
950
1315
|
}
|
|
951
1316
|
|
|
1317
|
+
if (reclaimRequest) {
|
|
1318
|
+
assertReclaimLiveness(coordRoot, binding, reclaimRequest.recovery);
|
|
1319
|
+
assertReclaimOwnership(coordRoot, binding);
|
|
1320
|
+
revalidateClaimResources(claim, root);
|
|
1321
|
+
reclaimRequest.prepared(
|
|
1322
|
+
prepareReclaim({
|
|
1323
|
+
worktreePath: binding.workspace_root,
|
|
1324
|
+
mode: reclaimRequest.mode,
|
|
1325
|
+
runId: binding.run_id,
|
|
1326
|
+
beforeEffect: reclaimRequest.beforeEffect,
|
|
1327
|
+
}),
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
952
1330
|
appendWorkspaceEvent(coordRoot, claim, "cleanup_pending", {
|
|
953
1331
|
worktree_path: binding.workspace_root,
|
|
954
1332
|
gitdir: repository.gitdir,
|
|
@@ -958,6 +1336,8 @@ function cleanupUnderLease(
|
|
|
958
1336
|
target_ref_oid: targetRef.out,
|
|
959
1337
|
});
|
|
960
1338
|
if (before.workspace_exists) {
|
|
1339
|
+
reclaimRequest?.beforeEffect();
|
|
1340
|
+
beforeRemoval?.();
|
|
961
1341
|
revalidateClaimResources(claim, root);
|
|
962
1342
|
assertContainedExisting(root, binding.workspace_root, "workspace cleanup path");
|
|
963
1343
|
git(repository.source_root.realpath, ["worktree", "remove", binding.workspace_root]);
|
|
@@ -983,6 +1363,7 @@ function cleanupUnderLease(
|
|
|
983
1363
|
return { ...base, status: "blocked", reason: "workspace ref changed during cleanup" };
|
|
984
1364
|
}
|
|
985
1365
|
revalidateClaimResources(claim, root);
|
|
1366
|
+
reclaimRequest?.beforeRefEffect();
|
|
986
1367
|
const deleted = gitMaybe(repository.source_root.realpath, [
|
|
987
1368
|
"update-ref",
|
|
988
1369
|
"-d",
|