harness-dispatch 0.4.0 → 0.6.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/CHANGELOG.md +152 -0
- package/README.md +232 -87
- package/config.default.yaml +416 -397
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/billing.d.ts +35 -4
- package/dist/billing.d.ts.map +1 -1
- package/dist/billing.js +48 -17
- package/dist/billing.js.map +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +147 -91
- package/dist/bin.js.map +1 -1
- package/dist/breaker-store.d.ts +60 -9
- package/dist/breaker-store.d.ts.map +1 -1
- package/dist/breaker-store.js +246 -50
- package/dist/breaker-store.js.map +1 -1
- package/dist/circuit-breaker.d.ts +35 -0
- package/dist/circuit-breaker.d.ts.map +1 -1
- package/dist/circuit-breaker.js +68 -11
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/config/coercions.d.ts +31 -0
- package/dist/config/coercions.d.ts.map +1 -0
- package/dist/config/coercions.js +169 -0
- package/dist/config/coercions.js.map +1 -0
- package/dist/config/env-interpolation.d.ts +36 -0
- package/dist/config/env-interpolation.d.ts.map +1 -0
- package/dist/config/env-interpolation.js +73 -0
- package/dist/config/env-interpolation.js.map +1 -0
- package/dist/config/protocol.d.ts +51 -0
- package/dist/config/protocol.d.ts.map +1 -0
- package/dist/config/protocol.js +300 -0
- package/dist/config/protocol.js.map +1 -0
- package/dist/config/route-fields.d.ts +72 -0
- package/dist/config/route-fields.d.ts.map +1 -0
- package/dist/config/route-fields.js +128 -0
- package/dist/config/route-fields.js.map +1 -0
- package/dist/config/validation.d.ts +72 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +189 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/config.d.ts +19 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +214 -635
- package/dist/config.js.map +1 -1
- package/dist/configure-yaml.d.ts +23 -0
- package/dist/configure-yaml.d.ts.map +1 -0
- package/dist/configure-yaml.js +193 -0
- package/dist/configure-yaml.js.map +1 -0
- package/dist/dispatch-log.d.ts.map +1 -1
- package/dist/dispatch-log.js +2 -3
- package/dist/dispatch-log.js.map +1 -1
- package/dist/dispatchers/base.d.ts +3 -0
- package/dist/dispatchers/base.d.ts.map +1 -1
- package/dist/dispatchers/base.js.map +1 -1
- package/dist/dispatchers/generic-cli.d.ts +15 -1
- package/dist/dispatchers/generic-cli.d.ts.map +1 -1
- package/dist/dispatchers/generic-cli.js +69 -5
- package/dist/dispatchers/generic-cli.js.map +1 -1
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
- package/dist/dispatchers/openai-compatible.js +70 -2
- package/dist/dispatchers/openai-compatible.js.map +1 -1
- package/dist/dispatchers/shared/kill-tree.d.ts +17 -4
- package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -1
- package/dist/dispatchers/shared/kill-tree.js +28 -4
- package/dist/dispatchers/shared/kill-tree.js.map +1 -1
- package/dist/dispatchers/shared/rate-limit-headers.d.ts +17 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -1
- package/dist/dispatchers/shared/rate-limit-headers.js +27 -0
- package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.d.ts +11 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.js +62 -35
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -1
- package/dist/dispatchers/shared/subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/subprocess.js +3 -0
- package/dist/dispatchers/shared/subprocess.js.map +1 -1
- package/dist/dispatchers/shared/which-available.d.ts.map +1 -1
- package/dist/dispatchers/shared/which-available.js +16 -2
- package/dist/dispatchers/shared/which-available.js.map +1 -1
- package/dist/file-lock.d.ts +17 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +143 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/http/parse.d.ts +64 -0
- package/dist/http/parse.d.ts.map +1 -0
- package/dist/http/parse.js +230 -0
- package/dist/http/parse.js.map +1 -0
- package/dist/http/server.d.ts +2 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +249 -286
- package/dist/http/server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/job-runner.js +23 -5
- package/dist/job-runner.js.map +1 -1
- package/dist/jobs/context.d.ts +17 -0
- package/dist/jobs/context.d.ts.map +1 -0
- package/dist/jobs/context.js +84 -0
- package/dist/jobs/context.js.map +1 -0
- package/dist/jobs/store.d.ts +80 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +279 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/jobs/types.d.ts +135 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +11 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/jobs.d.ts +165 -85
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +824 -208
- package/dist/jobs.js.map +1 -1
- package/dist/leaderboard.d.ts +24 -1
- package/dist/leaderboard.d.ts.map +1 -1
- package/dist/leaderboard.js +94 -2
- package/dist/leaderboard.js.map +1 -1
- package/dist/mcp/config-hot-reload.d.ts.map +1 -1
- package/dist/mcp/config-hot-reload.js +1 -1
- package/dist/mcp/config-hot-reload.js.map +1 -1
- package/dist/mcp/dispatcher-factory.d.ts +16 -1
- package/dist/mcp/dispatcher-factory.d.ts.map +1 -1
- package/dist/mcp/dispatcher-factory.js +41 -3
- package/dist/mcp/dispatcher-factory.js.map +1 -1
- package/dist/mcp/tool-schemas.d.ts +194 -0
- package/dist/mcp/tool-schemas.d.ts.map +1 -0
- package/dist/mcp/tool-schemas.js +311 -0
- package/dist/mcp/tool-schemas.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -51
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +122 -151
- package/dist/mcp/tools.js.map +1 -1
- package/dist/observability/index.d.ts +16 -3
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +23 -7
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/spans.d.ts.map +1 -1
- package/dist/observability/spans.js +4 -1
- package/dist/observability/spans.js.map +1 -1
- package/dist/quota.d.ts +106 -22
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +198 -58
- package/dist/quota.js.map +1 -1
- package/dist/route-policy.d.ts.map +1 -1
- package/dist/route-policy.js +13 -3
- package/dist/route-policy.js.map +1 -1
- package/dist/router.d.ts +29 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +102 -40
- package/dist/router.js.map +1 -1
- package/dist/safety.d.ts.map +1 -1
- package/dist/safety.js +11 -2
- package/dist/safety.js.map +1 -1
- package/dist/state-dir.d.ts +17 -0
- package/dist/state-dir.d.ts.map +1 -0
- package/dist/state-dir.js +21 -0
- package/dist/state-dir.js.map +1 -0
- package/dist/status.d.ts +49 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +67 -4
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +74 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/working-dir.d.ts +13 -0
- package/dist/working-dir.d.ts.map +1 -1
- package/dist/working-dir.js +27 -0
- package/dist/working-dir.js.map +1 -1
- package/dist/workspace-lock.d.ts +40 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +258 -0
- package/dist/workspace-lock.js.map +1 -0
- package/dist/workspace-resolve.d.ts +98 -0
- package/dist/workspace-resolve.d.ts.map +1 -0
- package/dist/workspace-resolve.js +382 -0
- package/dist/workspace-resolve.js.map +1 -0
- package/dist/workspaces.d.ts +16 -0
- package/dist/workspaces.d.ts.map +1 -1
- package/dist/workspaces.js +166 -22
- package/dist/workspaces.js.map +1 -1
- package/package.json +99 -96
- package/dist/dashboard/live.d.ts +0 -49
- package/dist/dashboard/live.d.ts.map +0 -1
- package/dist/dashboard/live.js +0 -149
- package/dist/dashboard/live.js.map +0 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutual exclusion over a working directory, across processes.
|
|
3
|
+
*
|
|
4
|
+
* `workspace_policy: shared_locked` promises that two dispatches never edit
|
|
5
|
+
* one workspace at the same time. Until now that promise was implemented as a
|
|
6
|
+
* `Map` in module scope — real within a process, and every job used to get its
|
|
7
|
+
* OWN process, so concurrent jobs sharing a directory were free to clobber
|
|
8
|
+
* each other the entire time. The guarantee read correctly and did nothing.
|
|
9
|
+
* Pooling supervisors made it bind for jobs sharing a supervisor, which
|
|
10
|
+
* narrowed the hole to four ways instead of N but did not close it.
|
|
11
|
+
*
|
|
12
|
+
* Two layers, because they solve different halves:
|
|
13
|
+
*
|
|
14
|
+
* 1. An in-process promise chain. Fast, fair (FIFO by arrival), and it keeps
|
|
15
|
+
* same-process waiters off the filesystem entirely.
|
|
16
|
+
* 2. A lock FILE, for everything the first layer cannot see. Created with
|
|
17
|
+
* `wx`, which fails atomically if it already exists on both Windows and
|
|
18
|
+
* POSIX.
|
|
19
|
+
*
|
|
20
|
+
* Lock files live in the state directory, keyed by a hash of the resolved
|
|
21
|
+
* path, rather than inside the workspace: a dispatcher should not drop
|
|
22
|
+
* bookkeeping files into a user's repository, where they would show up in
|
|
23
|
+
* `git status` and in the workspace diff the caller is handed back.
|
|
24
|
+
*
|
|
25
|
+
* A holder that dies must not wedge the directory forever, so the lock carries
|
|
26
|
+
* a heartbeat refreshed while held, and a lock whose heartbeat has gone stale
|
|
27
|
+
* is stolen. That is the same rule, and the same threshold, the job orphan
|
|
28
|
+
* check already uses — one staleness concept in the system, not two.
|
|
29
|
+
*/
|
|
30
|
+
import { createHash } from "node:crypto";
|
|
31
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { stateRoot } from "./state-dir.js";
|
|
34
|
+
/** Matches ORPHAN_THRESHOLD_MS in jobs.ts: one notion of "the holder is gone". */
|
|
35
|
+
export const LOCK_STALE_MS = 90_000;
|
|
36
|
+
/** How often a held lock refreshes its heartbeat. Comfortably inside the stale window. */
|
|
37
|
+
const HEARTBEAT_MS = 15_000;
|
|
38
|
+
/** Gap between attempts when another process holds the lock. */
|
|
39
|
+
const RETRY_MS = 100;
|
|
40
|
+
/**
|
|
41
|
+
* How long an EXISTING-but-unreadable lock file is given before it is treated
|
|
42
|
+
* as stealable. An unreadable record used to be stolen on sight, which made a
|
|
43
|
+
* torn read of a mid-rewrite heartbeat sufficient to take a LIVE holder's lock
|
|
44
|
+
* — two dispatches then edited the same workspace, the exact outcome
|
|
45
|
+
* `shared_locked` exists to prevent. Heartbeats are written atomically now, so
|
|
46
|
+
* within one build a torn read cannot happen; the grace keeps the steal honest
|
|
47
|
+
* against writers from older builds (in-place heartbeat rewrites during a
|
|
48
|
+
* rolling upgrade) and external interference.
|
|
49
|
+
*/
|
|
50
|
+
const UNREADABLE_GRACE_MS = 1_000;
|
|
51
|
+
const inProcessLocks = new Map();
|
|
52
|
+
function lockKey(workingDir) {
|
|
53
|
+
const resolved = path.resolve(workingDir || process.cwd());
|
|
54
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
55
|
+
}
|
|
56
|
+
function lockFileFor(key) {
|
|
57
|
+
const dir = stateRoot();
|
|
58
|
+
const digest = createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
59
|
+
return path.join(dir, "workspace-locks", `${digest}.json`);
|
|
60
|
+
}
|
|
61
|
+
function readRecord(file) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
64
|
+
if (typeof parsed?.beatMs !== "number")
|
|
65
|
+
return undefined;
|
|
66
|
+
return { pid: Number(parsed.pid) || 0, key: String(parsed.key ?? ""), beatMs: parsed.beatMs };
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Unreadable or half-written: treat as absent, and let the staleness
|
|
70
|
+
// check below decide. A corrupt lock must not wedge the directory.
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** True if the recorded holder is definitely gone. */
|
|
75
|
+
function isDead(record) {
|
|
76
|
+
if (Date.now() - record.beatMs > LOCK_STALE_MS)
|
|
77
|
+
return true;
|
|
78
|
+
if (record.pid > 0) {
|
|
79
|
+
try {
|
|
80
|
+
// Signal 0 tests for existence without touching the process.
|
|
81
|
+
process.kill(record.pid, 0);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
function tryCreate(file, key) {
|
|
90
|
+
try {
|
|
91
|
+
mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
92
|
+
writeFileSync(file, JSON.stringify({ pid: process.pid, key, beatMs: Date.now() }), {
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
mode: 0o600,
|
|
95
|
+
flag: "wx",
|
|
96
|
+
});
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Take a stale lock out of the way — by RENAME, not delete.
|
|
105
|
+
*
|
|
106
|
+
* Delete-then-recreate let two waiters both decide the same record was stale:
|
|
107
|
+
* the slower one's delete then removed the FASTER one's freshly created lock,
|
|
108
|
+
* and both ended up holding the directory. Rename is atomic and names a
|
|
109
|
+
* specific victim — whoever loses the rename gets ENOENT and simply goes
|
|
110
|
+
* round the acquire loop again. Returns true if this process performed the
|
|
111
|
+
* steal (the caller still has to win the `wx` create; a third waiter may get
|
|
112
|
+
* there first, which is an honest race, not a double hold).
|
|
113
|
+
*/
|
|
114
|
+
function stealLock(file) {
|
|
115
|
+
const tomb = `${file}.stolen-${process.pid}-${Date.now().toString(36)}`;
|
|
116
|
+
try {
|
|
117
|
+
renameSync(file, tomb);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false; // Another waiter stole it, or the holder released it first.
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
rmSync(tomb, { force: true });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// A leftover tombstone is inert: nothing reads `*.stolen-*` names.
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
async function acquireFileLock(key, timeoutMs) {
|
|
131
|
+
const file = lockFileFor(key);
|
|
132
|
+
const deadline = Date.now() + timeoutMs;
|
|
133
|
+
let unreadableSince;
|
|
134
|
+
for (;;) {
|
|
135
|
+
if (tryCreate(file, key))
|
|
136
|
+
break;
|
|
137
|
+
const record = readRecord(file);
|
|
138
|
+
if (record === undefined) {
|
|
139
|
+
// Exists but unreadable (or vanished between the two calls). Give it a
|
|
140
|
+
// grace window rather than stealing on sight — see UNREADABLE_GRACE_MS.
|
|
141
|
+
unreadableSince ??= Date.now();
|
|
142
|
+
if (Date.now() - unreadableSince >= UNREADABLE_GRACE_MS) {
|
|
143
|
+
unreadableSince = undefined;
|
|
144
|
+
stealLock(file);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
unreadableSince = undefined;
|
|
150
|
+
if (isDead(record)) {
|
|
151
|
+
stealLock(file);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (Date.now() >= deadline) {
|
|
156
|
+
// Proceeding unlocked would silently break the guarantee the caller
|
|
157
|
+
// asked for, so this is an error rather than a warning.
|
|
158
|
+
throw new Error(`workspace lock timed out after ${Math.round(timeoutMs / 1000)}s waiting for ` +
|
|
159
|
+
`pid ${record?.pid ?? "unknown"} to release ${key}. Another dispatch is still ` +
|
|
160
|
+
`using this workspace; use workspace_policy: copy to run them concurrently.`);
|
|
161
|
+
}
|
|
162
|
+
await new Promise((r) => setTimeout(r, RETRY_MS));
|
|
163
|
+
}
|
|
164
|
+
const beat = setInterval(() => {
|
|
165
|
+
// Refresh ATOMICALLY (tmp + rename), never by rewriting in place: an
|
|
166
|
+
// in-place rewrite truncates first, and a waiter reading in that window
|
|
167
|
+
// saw an empty record — grounds, under the old rules, to steal a lock
|
|
168
|
+
// whose holder was alive and mid-write.
|
|
169
|
+
//
|
|
170
|
+
// Ownership is checked first: if the record is no longer ours, this
|
|
171
|
+
// process froze past the stale window and was legitimately stolen.
|
|
172
|
+
// Overwriting would clobber the thief's record — and worse, make our own
|
|
173
|
+
// release() believe it still held the lock and delete it under the thief.
|
|
174
|
+
try {
|
|
175
|
+
const current = readRecord(file);
|
|
176
|
+
if (current !== undefined && current.pid !== process.pid) {
|
|
177
|
+
clearInterval(beat);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (current === undefined) {
|
|
181
|
+
// Missing or unreadable while we believe we hold it: a steal may be
|
|
182
|
+
// mid-flight. Recreating it could re-take a lock someone else now
|
|
183
|
+
// owns, so stop refreshing and let release()'s ownership check
|
|
184
|
+
// decide what to delete.
|
|
185
|
+
clearInterval(beat);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const tmp = `${file}.${process.pid}.beat`;
|
|
189
|
+
writeFileSync(tmp, JSON.stringify({ pid: process.pid, key, beatMs: Date.now() }), {
|
|
190
|
+
encoding: "utf8",
|
|
191
|
+
mode: 0o600,
|
|
192
|
+
});
|
|
193
|
+
renameSync(tmp, file);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// A failed refresh only risks the lock being stolen as stale, which is
|
|
197
|
+
// the correct outcome if this process really is in trouble. The next
|
|
198
|
+
// beat retries; a leftover .beat tmp file is inert.
|
|
199
|
+
}
|
|
200
|
+
}, HEARTBEAT_MS);
|
|
201
|
+
beat.unref?.();
|
|
202
|
+
let released = false;
|
|
203
|
+
return () => {
|
|
204
|
+
if (released)
|
|
205
|
+
return;
|
|
206
|
+
released = true;
|
|
207
|
+
clearInterval(beat);
|
|
208
|
+
try {
|
|
209
|
+
// Only remove it if we still hold it — a stolen lock now belongs to
|
|
210
|
+
// someone else and deleting it would hand the directory to a third
|
|
211
|
+
// party while the thief is mid-write.
|
|
212
|
+
const current = readRecord(file);
|
|
213
|
+
if (current === undefined || current.pid === process.pid) {
|
|
214
|
+
rmSync(file, { force: true });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Left behind; it ages out via the staleness rule.
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Take the lock for `workingDir`. Resolves once held; call the returned
|
|
224
|
+
* function to release.
|
|
225
|
+
*
|
|
226
|
+
* @param timeoutMs how long to wait for another process. Defaults to an hour,
|
|
227
|
+
* matching the job runtime ceiling — a legitimate holder can hold it that long.
|
|
228
|
+
*/
|
|
229
|
+
export async function acquireWorkspaceLock(workingDir, timeoutMs = 60 * 60 * 1000) {
|
|
230
|
+
const key = lockKey(workingDir);
|
|
231
|
+
// Layer 1: queue behind same-process waiters first, so a burst inside one
|
|
232
|
+
// supervisor costs one filesystem acquisition rather than N spinning ones.
|
|
233
|
+
const previous = inProcessLocks.get(key) ?? Promise.resolve();
|
|
234
|
+
let releaseLocal;
|
|
235
|
+
const current = previous.catch(() => undefined).then(() => new Promise((resolve) => {
|
|
236
|
+
releaseLocal = resolve;
|
|
237
|
+
}));
|
|
238
|
+
inProcessLocks.set(key, current);
|
|
239
|
+
await previous.catch(() => undefined);
|
|
240
|
+
// Layer 2: the cross-process lock.
|
|
241
|
+
let releaseFile;
|
|
242
|
+
try {
|
|
243
|
+
releaseFile = await acquireFileLock(key, timeoutMs);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
releaseLocal();
|
|
247
|
+
if (inProcessLocks.get(key) === current)
|
|
248
|
+
inProcessLocks.delete(key);
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
return () => {
|
|
252
|
+
releaseFile();
|
|
253
|
+
releaseLocal();
|
|
254
|
+
if (inProcessLocks.get(key) === current)
|
|
255
|
+
inProcessLocks.delete(key);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=workspace-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-lock.js","sourceRoot":"","sources":["../src/workspace-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What happens to isolated work AFTER the agent finishes.
|
|
3
|
+
*
|
|
4
|
+
* `copy` and `git_worktree` gave you a workspace path, a changed-file list and
|
|
5
|
+
* a `cleanupHint` — a string telling you to run `git worktree remove`
|
|
6
|
+
* yourself. Isolation worked and nothing was ever applied automatically, but
|
|
7
|
+
* there was no way to see the actual change, no way to keep it, and no way to
|
|
8
|
+
* clean up but by hand. Isolated dispatches were effectively write-only: you
|
|
9
|
+
* could look at a summary and then abandon the result.
|
|
10
|
+
*
|
|
11
|
+
* This module is the missing half — inspect, then keep or throw away.
|
|
12
|
+
*
|
|
13
|
+
* THE DANGEROUS PART IS `apply`, and it is dangerous in one specific way:
|
|
14
|
+
* applying a patch into a directory that has moved on since the agent started
|
|
15
|
+
* can conflict with, or silently overwrite, work the user did in the meantime.
|
|
16
|
+
* So apply refuses when the target has uncommitted changes unless the caller
|
|
17
|
+
* explicitly overrides, and it always writes the patch to disk first so there
|
|
18
|
+
* is a manual path (`git apply`) even when the automatic one declines.
|
|
19
|
+
*/
|
|
20
|
+
import type { WorkspaceRun } from "./types.js";
|
|
21
|
+
/** Patches are read into memory and returned over MCP, so they are bounded. */
|
|
22
|
+
export declare const MAX_PATCH_BYTES: number;
|
|
23
|
+
/** How much of a patch is returned inline before it is truncated for display. */
|
|
24
|
+
export declare const MAX_PATCH_CHARS = 60000;
|
|
25
|
+
export declare function isResolvable(run: WorkspaceRun | undefined): run is WorkspaceRun;
|
|
26
|
+
/**
|
|
27
|
+
* Produce a unified patch of what the agent changed, relative to the project
|
|
28
|
+
* root, so it applies with `git apply -p1` from `originalWorkingDir`.
|
|
29
|
+
*
|
|
30
|
+
* The two policies need different bases and getting this wrong is destructive:
|
|
31
|
+
*
|
|
32
|
+
* git_worktree — diff against the COMMIT the worktree was created from.
|
|
33
|
+
* A worktree starts at HEAD, not at the caller's working tree, so diffing
|
|
34
|
+
* it against the original directory of a dirty project would report the
|
|
35
|
+
* user's own uncommitted work as deletions.
|
|
36
|
+
* copy — diff against the original directory, which is exactly what the
|
|
37
|
+
* copy was made from, so the difference is the agent's work and nothing
|
|
38
|
+
* else.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildWorkspacePatch(run: WorkspaceRun): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Rewrite `git diff --no-index` headers so the patch is repo-relative.
|
|
43
|
+
*
|
|
44
|
+
* --no-index emits the absolute paths it was given (`--- a/C:/long/path/...`),
|
|
45
|
+
* which applies nowhere but this machine. Rewriting them to a/<rel> and
|
|
46
|
+
* b/<rel> makes the patch portable and applicable with -p1, which is what
|
|
47
|
+
* every other git patch in the world expects.
|
|
48
|
+
*/
|
|
49
|
+
export declare function normaliseNoIndexPaths(patch: string, origRoot: string, isoRoot: string): string;
|
|
50
|
+
export interface PatchResult {
|
|
51
|
+
jobId: string;
|
|
52
|
+
policy: WorkspaceRun["policy"];
|
|
53
|
+
/** Where the full patch was written, always, so there is a manual path. */
|
|
54
|
+
patchPath: string;
|
|
55
|
+
bytes: number;
|
|
56
|
+
/** Possibly truncated for display; patchPath always holds the whole thing. */
|
|
57
|
+
patch: string;
|
|
58
|
+
truncated: boolean;
|
|
59
|
+
changedFiles?: WorkspaceRun["changedFiles"];
|
|
60
|
+
}
|
|
61
|
+
/** Build the patch, cache it next to the job, and return it (bounded). */
|
|
62
|
+
export declare function workspaceDiff(jobId: string, jobDir: string, run: WorkspaceRun): Promise<PatchResult>;
|
|
63
|
+
export interface ApplyResult {
|
|
64
|
+
jobId: string;
|
|
65
|
+
applied: boolean;
|
|
66
|
+
patchPath: string;
|
|
67
|
+
message: string;
|
|
68
|
+
changedFiles?: WorkspaceRun["changedFiles"];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Apply the agent's work into the real project.
|
|
72
|
+
*
|
|
73
|
+
* Refuses by default when the target has uncommitted changes: the patch was
|
|
74
|
+
* built against a specific base, and applying it over work done since can
|
|
75
|
+
* conflict or clobber. `force` overrides, and the patch file is written
|
|
76
|
+
* either way so `git apply` by hand is always available.
|
|
77
|
+
*/
|
|
78
|
+
export declare function applyWorkspace(jobId: string, jobDir: string, run: WorkspaceRun, opts?: {
|
|
79
|
+
force?: boolean;
|
|
80
|
+
}): Promise<ApplyResult>;
|
|
81
|
+
export interface DiscardResult {
|
|
82
|
+
jobId: string;
|
|
83
|
+
discarded: boolean;
|
|
84
|
+
message: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Throw the isolated workspace away.
|
|
88
|
+
*
|
|
89
|
+
* A git worktree is not just a directory: `rm -rf` on it leaves the parent
|
|
90
|
+
* repository's worktree metadata behind, and git then complains about a
|
|
91
|
+
* missing worktree until someone runs `git worktree prune`. So worktrees are
|
|
92
|
+
* removed through git, with a filesystem sweep afterwards for the wrapper
|
|
93
|
+
* directory git does not own.
|
|
94
|
+
*/
|
|
95
|
+
export declare function discardWorkspace(jobId: string, run: WorkspaceRun): Promise<DiscardResult>;
|
|
96
|
+
/** Read a previously written patch, if one was cached. */
|
|
97
|
+
export declare function cachedPatch(jobDir: string): Promise<string | undefined>;
|
|
98
|
+
//# sourceMappingURL=workspace-resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-resolve.d.ts","sourceRoot":"","sources":["../src/workspace-resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,+EAA+E;AAC/E,eAAO,MAAM,eAAe,QAAkB,CAAC;AAE/C,iFAAiF;AACjF,eAAO,MAAM,eAAe,QAAS,CAAC;AA4BtC,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,GAAG,IAAI,YAAY,CAE/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CA0C5E;AAmFD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAwB9F;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC/B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;CAC7C;AAED,0EAA0E;AAC1E,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AA4BD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,YAAY,EACjB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,WAAW,CAAC,CAiEtB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED,0DAA0D;AAC1D,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7E"}
|