@nexrall/code-core 1.4.59 → 1.4.60
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/dist/agent/crossProcessLock.d.ts +45 -0
- package/dist/agent/crossProcessLock.d.ts.map +1 -0
- package/dist/agent/crossProcessLock.js +270 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +114 -16
- package/dist/agent/peerRegistry.d.ts +64 -0
- package/dist/agent/peerRegistry.d.ts.map +1 -0
- package/dist/agent/peerRegistry.js +238 -0
- package/dist/agent/peerTransport.d.ts +122 -0
- package/dist/agent/peerTransport.d.ts.map +1 -0
- package/dist/agent/peerTransport.js +348 -0
- package/dist/agent/sharedTasks.d.ts +63 -0
- package/dist/agent/sharedTasks.d.ts.map +1 -0
- package/dist/agent/sharedTasks.js +284 -0
- package/dist/agent/worktree.d.ts +111 -0
- package/dist/agent/worktree.d.ts.map +1 -0
- package/dist/agent/worktree.js +502 -0
- package/dist/agent/worktreeEnforcement.d.ts +29 -0
- package/dist/agent/worktreeEnforcement.d.ts.map +1 -0
- package/dist/agent/worktreeEnforcement.js +203 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +5 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/tools/executor.d.ts +4 -1
- package/dist/tools/executor.d.ts.map +1 -1
- package/dist/tools/executor.js +194 -4
- package/dist/types.d.ts +83 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports._sharedTasksInternal = void 0;
|
|
37
|
+
exports.listSharedTasks = listSharedTasks;
|
|
38
|
+
exports.createSharedTask = createSharedTask;
|
|
39
|
+
exports.claimTask = claimTask;
|
|
40
|
+
exports.updateSharedTaskStatus = updateSharedTaskStatus;
|
|
41
|
+
/**
|
|
42
|
+
* Shared task list — lets MULTIPLE independent sessions (two CLI windows, or
|
|
43
|
+
* CLI + VS Code + Desktop, all working the same project) coordinate who is
|
|
44
|
+
* doing what, mirroring Claude Code's agent-teams task list
|
|
45
|
+
* (`~/.claude/tasks/{team-name}/`, `pending/in_progress/completed/blocked`
|
|
46
|
+
* status, atomic claim-by-write).
|
|
47
|
+
*
|
|
48
|
+
* WHERE THIS FITS relative to the other two isolation mechanisms already in
|
|
49
|
+
* this codebase — they solve DIFFERENT problems and this module deliberately
|
|
50
|
+
* does not try to replace either:
|
|
51
|
+
* - crossProcessLock.ts: prevents two processes from corrupting the SAME
|
|
52
|
+
* FILE if they write it at the same instant. Reactive, byte-level.
|
|
53
|
+
* - worktree.ts: prevents two sessions from touching the same
|
|
54
|
+
* directory tree AT ALL. Preventive, physical.
|
|
55
|
+
* - sharedTasks.ts (here): lets sessions agree in ADVANCE on who is
|
|
56
|
+
* working on what, so they mostly never NEED the other two mechanisms
|
|
57
|
+
* to fire — this is social coordination, not a hard technical
|
|
58
|
+
* guarantee. A session can still ignore the task list; nothing here
|
|
59
|
+
* enforces claims the way worktreeEnforcement.ts enforces isolation.
|
|
60
|
+
* That's intentional and matches Claude Code's own docs: the task list
|
|
61
|
+
* is a coordination aid ("Assign different folders/files to different
|
|
62
|
+
* agents" is still the human's job), not a lock.
|
|
63
|
+
*
|
|
64
|
+
* STORAGE
|
|
65
|
+
* -------
|
|
66
|
+
* ~/.nexrall/peers/tasks/<workDirHash>/<taskId>.json
|
|
67
|
+
*
|
|
68
|
+
* One file per task. `workDirHash` scopes tasks to the project they belong
|
|
69
|
+
* to (same reasoning as crossProcessLock's opaque-key scoping) so two
|
|
70
|
+
* unrelated projects on the same machine never see each other's task lists.
|
|
71
|
+
*
|
|
72
|
+
* CLAIMING (the one operation that must be race-free)
|
|
73
|
+
* ----------------------------------------------------
|
|
74
|
+
* `claimTask` reads the task, checks it is claimable (pending, not blocked
|
|
75
|
+
* by an incomplete task), and writes `status: in_progress` + `claimedBy` —
|
|
76
|
+
* all while holding crossProcessLock.ts's OS-level lock for that exact task
|
|
77
|
+
* file. Two sessions racing to claim the same task: exactly one lock winner
|
|
78
|
+
* proceeds through the read-check-write; the loser's lock acquisition
|
|
79
|
+
* unblocks AFTER the winner has already written `in_progress`, so the
|
|
80
|
+
* loser's own claimability check (re-read after acquiring the lock) sees the
|
|
81
|
+
* task is no longer pending and returns 'already-claimed'. This is the exact
|
|
82
|
+
* race Claude Code's docs describe being solved "via file locking at the
|
|
83
|
+
* write layer" for their own task list.
|
|
84
|
+
*
|
|
85
|
+
* STALE CLAIMS
|
|
86
|
+
* ------------
|
|
87
|
+
* Claude Code's own docs list "status can become stale... requiring manual
|
|
88
|
+
* intervention" as a known limitation of their task list. This module goes
|
|
89
|
+
* one step further with a TTL + liveness check (same pattern as
|
|
90
|
+
* crossProcessLock's stale-lock detection): a claim whose owning session's
|
|
91
|
+
* process is no longer running, or whose claim is older than
|
|
92
|
+
* CLAIM_STALE_MS with no heartbeat, is treated as abandoned and can be
|
|
93
|
+
* reclaimed — still requiring the reclaiming session to go through the
|
|
94
|
+
* same race-free claim path above.
|
|
95
|
+
*/
|
|
96
|
+
const fs = __importStar(require("fs"));
|
|
97
|
+
const os = __importStar(require("os"));
|
|
98
|
+
const path = __importStar(require("path"));
|
|
99
|
+
const crypto = __importStar(require("crypto"));
|
|
100
|
+
const crossProcessLock_1 = require("./crossProcessLock");
|
|
101
|
+
// A claim older than this with no update is treated the same as a dead-pid
|
|
102
|
+
// claim — reclaimable. Longer than crossProcessLock's STALE_MS (that guards a
|
|
103
|
+
// single tool call; this guards an entire unit of work a session might
|
|
104
|
+
// legitimately spend minutes on).
|
|
105
|
+
const CLAIM_STALE_MS = 30 * 60 * 1000; // 30 minutes
|
|
106
|
+
// Minimum age a claim must have before a DEAD-PID signal is trusted to mean
|
|
107
|
+
// "abandoned" (as opposed to CLAIM_STALE_MS above, which is the fallback for
|
|
108
|
+
// a claim whose pid is still alive but has gone quiet). Real bug this closes
|
|
109
|
+
// (caught by sharedTasks.test.mjs's genuine two-process claim race, which
|
|
110
|
+
// failed intermittently until this existed): a session can legitimately exit
|
|
111
|
+
// within milliseconds of successfully claiming a task — e.g. a short-lived
|
|
112
|
+
// worker that claims, does a tiny unit of work, and exits — and
|
|
113
|
+
// `process.kill(pid, 0)` reports that pid as dead the instant it's reaped, NOT
|
|
114
|
+
// only once it's been dead a while. A second session racing to claim the same
|
|
115
|
+
// task, arriving microseconds later, would see "pid dead" and treat a claim
|
|
116
|
+
// that is completely valid (its owner exited normally, having done exactly
|
|
117
|
+
// what it claimed the task to do) as abandoned trash to steal. A REAL crash
|
|
118
|
+
// is still caught by this — just after MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS have
|
|
119
|
+
// passed, not instantly — which is an acceptable trade because the failure
|
|
120
|
+
// mode of "wait a couple seconds longer to reclaim a genuinely dead claim" is
|
|
121
|
+
// far cheaper than "two sessions think they both own the same unit of work".
|
|
122
|
+
const MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS = 2000;
|
|
123
|
+
function tasksRoot() {
|
|
124
|
+
return process.env.NEXRALL_SHARED_TASKS_DIR || path.join(os.homedir(), '.nexrall', 'peers', 'tasks');
|
|
125
|
+
}
|
|
126
|
+
function scopeDir(workDir) {
|
|
127
|
+
const hash = crypto.createHash('sha256').update(path.resolve(workDir)).digest('hex').slice(0, 24);
|
|
128
|
+
return path.join(tasksRoot(), hash);
|
|
129
|
+
}
|
|
130
|
+
function taskPath(workDir, taskId) {
|
|
131
|
+
return path.join(scopeDir(workDir), `${taskId}.json`);
|
|
132
|
+
}
|
|
133
|
+
function readTask(workDir, taskId) {
|
|
134
|
+
try {
|
|
135
|
+
const raw = JSON.parse(fs.readFileSync(taskPath(workDir, taskId), 'utf-8'));
|
|
136
|
+
if (raw && typeof raw.id === 'string' && typeof raw.status === 'string')
|
|
137
|
+
return raw;
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function writeTask(workDir, task) {
|
|
145
|
+
const dir = scopeDir(workDir);
|
|
146
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
147
|
+
const p = taskPath(workDir, task.id);
|
|
148
|
+
// Same atomic-write-via-temp-then-rename reasoning as core's
|
|
149
|
+
// atomicWritePreservingMode: a crash mid-write must never leave a
|
|
150
|
+
// half-written JSON file that the next reader chokes on.
|
|
151
|
+
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
152
|
+
fs.writeFileSync(tmp, JSON.stringify(task));
|
|
153
|
+
fs.renameSync(tmp, p);
|
|
154
|
+
}
|
|
155
|
+
function isPidAlive(pid) {
|
|
156
|
+
try {
|
|
157
|
+
process.kill(pid, 0);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** A claimed task whose owning process is gone, or whose claim has gone stale with no update, is treated as abandoned. */
|
|
165
|
+
function isClaimAbandoned(task) {
|
|
166
|
+
if (task.status !== 'in_progress')
|
|
167
|
+
return false;
|
|
168
|
+
const age = typeof task.claimedAt === 'number' ? Date.now() - task.claimedAt : Infinity;
|
|
169
|
+
// See MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS's own comment for why a dead-pid
|
|
170
|
+
// verdict is withheld until the claim has existed for a minimum age —
|
|
171
|
+
// without this guard, a session that legitimately claims-then-exits
|
|
172
|
+
// quickly can have its still-valid claim stolen out from under it.
|
|
173
|
+
if (age >= MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS && typeof task.claimedByPid === 'number' && !isPidAlive(task.claimedByPid))
|
|
174
|
+
return true;
|
|
175
|
+
if (age > CLAIM_STALE_MS)
|
|
176
|
+
return true;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
/** Whether every task ID in `blockedBy` is `completed` — i.e. this task is actually claimable right now. */
|
|
180
|
+
function isUnblocked(workDir, task) {
|
|
181
|
+
if (!task.blockedBy?.length)
|
|
182
|
+
return true;
|
|
183
|
+
return task.blockedBy.every((id) => readTask(workDir, id)?.status === 'completed');
|
|
184
|
+
}
|
|
185
|
+
/** List every task registered for `workDir`, newest first isn't guaranteed — sort by createdAt if display order matters to the caller. */
|
|
186
|
+
function listSharedTasks(workDir) {
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = fs.readdirSync(scopeDir(workDir));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const name of entries) {
|
|
196
|
+
if (!name.endsWith('.json'))
|
|
197
|
+
continue;
|
|
198
|
+
const id = name.slice(0, -'.json'.length);
|
|
199
|
+
const t = readTask(workDir, id);
|
|
200
|
+
if (t)
|
|
201
|
+
out.push(t);
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
/** Create a new pending task. `id` auto-generated (short random) unless the caller supplies a stable one (e.g. re-registering from a static plan). */
|
|
206
|
+
function createSharedTask(workDir, content, opts = {}) {
|
|
207
|
+
const id = opts.id ?? crypto.randomBytes(6).toString('hex');
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
const task = {
|
|
210
|
+
id, content, status: 'pending',
|
|
211
|
+
blockedBy: opts.blockedBy?.length ? opts.blockedBy : undefined,
|
|
212
|
+
createdAt: now, updatedAt: now,
|
|
213
|
+
};
|
|
214
|
+
writeTask(workDir, task);
|
|
215
|
+
return task;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Atomically claim `taskId` for `claimedBy` (a peer/session name). Race-free
|
|
219
|
+
* against another process calling this at the same instant — see this
|
|
220
|
+
* module's header comment for exactly why.
|
|
221
|
+
*/
|
|
222
|
+
async function claimTask(workDir, taskId, claimedBy) {
|
|
223
|
+
// Lock scoped to (workDir, taskId) specifically — via crossProcessLock's
|
|
224
|
+
// opaque-key + workDir scoping (see its own header) — so claiming task A
|
|
225
|
+
// never blocks a concurrent claim of unrelated task B in the same project.
|
|
226
|
+
return (0, crossProcessLock_1.withCrossProcessLock)(`shared-task:${taskId}`, async () => {
|
|
227
|
+
const task = readTask(workDir, taskId);
|
|
228
|
+
if (!task)
|
|
229
|
+
return { ok: false, reason: 'not-found' };
|
|
230
|
+
// A task is claimable when it is 'pending' (the only status
|
|
231
|
+
// createSharedTask ever sets — 'blocked' is reserved for a future
|
|
232
|
+
// producer that wants to record the reason explicitly, but is treated
|
|
233
|
+
// identically to 'pending' here) OR an abandoned 'in_progress' claim
|
|
234
|
+
// (dead pid / stale timestamp — see isClaimAbandoned).
|
|
235
|
+
const claimable = task.status === 'pending' || task.status === 'blocked' || isClaimAbandoned(task);
|
|
236
|
+
if (!claimable) {
|
|
237
|
+
return { ok: false, reason: task.status === 'completed' ? 'not-pending' : 'already-claimed' };
|
|
238
|
+
}
|
|
239
|
+
if (!isUnblocked(workDir, task))
|
|
240
|
+
return { ok: false, reason: 'blocked' };
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
const updated = {
|
|
243
|
+
...task,
|
|
244
|
+
status: 'in_progress',
|
|
245
|
+
claimedBy,
|
|
246
|
+
claimedByPid: process.pid,
|
|
247
|
+
claimedAt: now,
|
|
248
|
+
updatedAt: now,
|
|
249
|
+
};
|
|
250
|
+
writeTask(workDir, updated);
|
|
251
|
+
return { ok: true, task: updated };
|
|
252
|
+
}, workDir);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Update a task's status. Used for both normal progress (in_progress →
|
|
256
|
+
* completed) and releasing a claim early (in_progress → pending, e.g. a
|
|
257
|
+
* session that claimed a task then decided not to do it after all). Also
|
|
258
|
+
* lock-guarded — a status update racing a claim attempt must not corrupt
|
|
259
|
+
* either operation's view of the task.
|
|
260
|
+
*/
|
|
261
|
+
async function updateSharedTaskStatus(workDir, taskId, status, opts = {}) {
|
|
262
|
+
return (0, crossProcessLock_1.withCrossProcessLock)(`shared-task:${taskId}`, async () => {
|
|
263
|
+
const task = readTask(workDir, taskId);
|
|
264
|
+
if (!task)
|
|
265
|
+
return { ok: false, reason: 'not-found' };
|
|
266
|
+
// A task can only be marked done/reopened by whoever holds the claim —
|
|
267
|
+
// prevents session B from marking session A's in-progress task
|
|
268
|
+
// completed out from under it. Not enforced for a task with NO current
|
|
269
|
+
// claim (status pending/blocked → anyone may set it, e.g. un-blocking).
|
|
270
|
+
if (task.status === 'in_progress' && task.claimedBy && opts.claimedBy && task.claimedBy !== opts.claimedBy) {
|
|
271
|
+
return { ok: false, reason: 'already-claimed' };
|
|
272
|
+
}
|
|
273
|
+
const updated = {
|
|
274
|
+
...task,
|
|
275
|
+
status,
|
|
276
|
+
updatedAt: Date.now(),
|
|
277
|
+
...(status === 'pending' || status === 'completed' ? { claimedBy: undefined, claimedByPid: undefined, claimedAt: undefined } : {}),
|
|
278
|
+
};
|
|
279
|
+
writeTask(workDir, updated);
|
|
280
|
+
return { ok: true, task: updated };
|
|
281
|
+
}, workDir);
|
|
282
|
+
}
|
|
283
|
+
exports._sharedTasksInternal = { tasksRoot, scopeDir, taskPath, isClaimAbandoned, isUnblocked, CLAIM_STALE_MS, MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS };
|
|
284
|
+
//# sourceMappingURL=sharedTasks.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export interface WorktreeState {
|
|
2
|
+
/** Absolute path to the isolated worktree directory the session is confined to. */
|
|
3
|
+
worktreePath: string;
|
|
4
|
+
/** Absolute path to the main checkout / original workDir the isolation protects. */
|
|
5
|
+
mainCheckout: string;
|
|
6
|
+
/** Which backend created this worktree — determines how EnterWorktree enforcement and cleanup behave. */
|
|
7
|
+
backend: 'git' | 'copy' | 'hook';
|
|
8
|
+
/** Branch name, only meaningful for the git backend. */
|
|
9
|
+
branch?: string;
|
|
10
|
+
/** Human-facing name (also the directory's basename under .nexrall/worktrees/). */
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
declare function isGitRepo(dir: string): {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
root?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Generate a readable random name, same shape Claude Code uses ("bright-running-fox"). */
|
|
18
|
+
export declare function randomWorktreeName(): string;
|
|
19
|
+
declare function safeName(name: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Look for a project-supplied override hook: `.nexrall/hooks/worktree-<phase>`
|
|
22
|
+
* (an executable script/command), same escape hatch Claude Code's docs
|
|
23
|
+
* describe for non-git version control. Returns the command string to run,
|
|
24
|
+
* or null if the project hasn't defined one for this phase.
|
|
25
|
+
*/
|
|
26
|
+
declare function findWorktreeHook(workDir: string, phase: 'create' | 'enter' | 'exit'): string | null;
|
|
27
|
+
declare function copyRecursive(src: string, dest: string): void;
|
|
28
|
+
export interface CreateWorktreeResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
state?: WorktreeState;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create an isolated worktree for `workDir`, choosing the best backend
|
|
35
|
+
* automatically:
|
|
36
|
+
* 1. A project-supplied `.nexrall/hooks` override for 'create', if defined
|
|
37
|
+
* (works for ANY VCS, or a fully custom isolation scheme).
|
|
38
|
+
* 2. Git-native `git worktree add`, if workDir is inside a git repo.
|
|
39
|
+
* 3. Plain recursive copy (skips COPY_SKIP_DIRS), as a zero-config
|
|
40
|
+
* fallback that always works.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createWorktree(workDir: string, requestedName?: string): CreateWorktreeResult;
|
|
43
|
+
export interface RemoveWorktreeOptions {
|
|
44
|
+
/** Force removal even if the worktree has uncommitted/untracked changes. Mirrors Claude Code's own confirm-before-destroy default. */
|
|
45
|
+
force?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface RemoveWorktreeResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
/** True when the worktree had real work in it (dirty git tree, or ANY file, for copy-based) and removal was skipped without `force`. */
|
|
50
|
+
hadWork?: boolean;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Whether a worktree has anything in it worth not silently discarding. */
|
|
54
|
+
export declare function worktreeHasWork(state: WorktreeState): boolean;
|
|
55
|
+
export declare function removeWorktree(state: WorktreeState, opts?: RemoveWorktreeOptions): RemoveWorktreeResult;
|
|
56
|
+
/**
|
|
57
|
+
* Called when a session ENTERS an already-created worktree (including
|
|
58
|
+
* re-entering across a resume). Fires the project's 'enter' hook if defined
|
|
59
|
+
* — informational only for the git/copy backends (nothing to actually do:
|
|
60
|
+
* the directory already exists and is already the session's cwd), but
|
|
61
|
+
* REQUIRED for the 'hook' backend, which may need to check out/refresh
|
|
62
|
+
* VCS-specific state each time.
|
|
63
|
+
*/
|
|
64
|
+
export declare function enterWorktree(state: WorktreeState): {
|
|
65
|
+
ok: boolean;
|
|
66
|
+
error?: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* True when `absPath` resolves to somewhere inside the MAIN CHECKOUT that
|
|
70
|
+
* isolation is meant to protect, while NOT being inside the currently active
|
|
71
|
+
* worktree — the check `blockOutsideWorktree` (in executor.ts) uses to
|
|
72
|
+
* refuse a write/bash-cwd that targets the wrong tree. This is exactly the
|
|
73
|
+
* check Claude Code's own worktree docs describe: "blocks an Edit, Write, or
|
|
74
|
+
* NotebookEdit that targets a path in the main checkout" — NOT "any path
|
|
75
|
+
* outside the worktree" (a write to some unrelated directory elsewhere on
|
|
76
|
+
* disk isn't the hazard this specific check exists to prevent; ordinary
|
|
77
|
+
* sandbox/permission rules still govern that separately).
|
|
78
|
+
*
|
|
79
|
+
* The git/copy backends nest the worktree INSIDE the main checkout
|
|
80
|
+
* (`<mainCheckout>/.nexrall/worktrees/<name>`), so "inside mainCheckout" on
|
|
81
|
+
* its own would wrongly flag every path inside a perfectly legitimate
|
|
82
|
+
* worktree — this function excludes the active worktree's own subtree first.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately resolves symlinks (fs.realpathSync) so a symlink planted
|
|
85
|
+
* INSIDE the worktree that points back at the main checkout can't be used to
|
|
86
|
+
* defeat the check — the same class of bypass Claude Code's own worktree
|
|
87
|
+
* docs call out for git redirects.
|
|
88
|
+
*/
|
|
89
|
+
export declare function pathEscapesWorktree(absPath: string, state: WorktreeState): boolean;
|
|
90
|
+
declare function metadataPath(worktreePath: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* List every worktree registered under `workDir`'s `.nexrall/worktrees/`,
|
|
93
|
+
* reconstructed from each one's metadata file. Skips (rather than throws on)
|
|
94
|
+
* a directory with no readable metadata — e.g. one a user created by hand,
|
|
95
|
+
* or debris from an older version — since one bad entry must not hide every
|
|
96
|
+
* legitimate one from a session picker.
|
|
97
|
+
*/
|
|
98
|
+
export declare function listWorktrees(workDir: string): WorktreeState[];
|
|
99
|
+
/** Find a previously created worktree by name, for resuming into it in a fresh process. */
|
|
100
|
+
export declare function findWorktreeByName(workDir: string, name: string): WorktreeState | null;
|
|
101
|
+
export declare const _worktreeInternal: {
|
|
102
|
+
isGitRepo: typeof isGitRepo;
|
|
103
|
+
safeName: typeof safeName;
|
|
104
|
+
copyRecursive: typeof copyRecursive;
|
|
105
|
+
findWorktreeHook: typeof findWorktreeHook;
|
|
106
|
+
COPY_SKIP_DIRS: Set<string>;
|
|
107
|
+
WORKTREE_ROOT_DIRNAME: string;
|
|
108
|
+
metadataPath: typeof metadataPath;
|
|
109
|
+
};
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=worktree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/agent/worktree.ts"],"names":[],"mappings":"AAgDA,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,YAAY,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,YAAY,EAAE,MAAM,CAAC;IACrB,yGAAyG;IACzG,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;CACd;AAiBD,iBAAS,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ9D;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,IAAI,MAAM,CAO3C;AAED,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtC;AAED;;;;;GAKG;AACH,iBAAS,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAW5F;AAuBD,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CActD;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,oBAAoB,CAiD5F;AAED,MAAM,WAAW,qBAAqB;IACpC,sIAAsI;IACtI,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,wIAAwI;IACxI,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAmC7D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,GAAE,qBAA0B,GAAG,oBAAoB,CAgD3G;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAMnF;AA6CD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAMlF;AAgBD,iBAAS,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElD;AA2BD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAY9D;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAEtF;AAED,eAAO,MAAM,iBAAiB;;;;;;;;CAAgH,CAAC"}
|