@bridge4dev/runner 0.52.0 → 0.54.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/dist/adapters/claude.js +111 -33
- package/dist/adapters/codex-protocol.d.ts +11 -0
- package/dist/adapters/codex-protocol.js +41 -3
- package/dist/adapters/codex.js +14 -26
- package/dist/adapters/types.d.ts +45 -1
- package/dist/adapters/types.js +53 -0
- package/dist/checkpoints.d.ts +62 -0
- package/dist/checkpoints.js +50 -1
- package/dist/git.d.ts +64 -0
- package/dist/git.js +487 -36
- package/dist/gitops.d.ts +5 -0
- package/dist/gitops.js +7 -8
- package/dist/host-load.d.ts +156 -0
- package/dist/host-load.js +223 -0
- package/dist/index.js +190 -40
- package/dist/policy.d.ts +38 -0
- package/dist/policy.js +228 -7
- package/dist/process-priority.d.ts +55 -0
- package/dist/process-priority.js +99 -0
- package/dist/protocol.d.ts +42 -20
- package/dist/recipe-schema.d.ts +6 -6
- package/dist/self-update.js +43 -2
- package/dist/service-unit.d.ts +232 -10
- package/dist/service-unit.js +372 -43
- package/dist/session-cage.d.ts +297 -0
- package/dist/session-cage.js +755 -0
- package/dist/supervisor.d.ts +156 -3
- package/dist/supervisor.js +351 -32
- package/dist/systemd-memory.d.ts +35 -0
- package/dist/systemd-memory.js +115 -0
- package/dist/verify.js +28 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/checkpoints.js
CHANGED
|
@@ -362,6 +362,16 @@ function decodeMeta(message) {
|
|
|
362
362
|
? { agentSession: parsed['agentSession'] }
|
|
363
363
|
: {}),
|
|
364
364
|
...(typeof parsed['messageSeq'] === 'number' ? { messageSeq: parsed['messageSeq'] } : {}),
|
|
365
|
+
// A whitelist, so a key absent from THIS list is a key that was written
|
|
366
|
+
// and is never read again. The mark is the whole point of #310, and a
|
|
367
|
+
// silently dropped mark reads exactly like «the folder was quiet».
|
|
368
|
+
...(Array.isArray(parsed['busySessions'])
|
|
369
|
+
? {
|
|
370
|
+
busySessions: parsed['busySessions']
|
|
371
|
+
.filter((id) => typeof id === 'string')
|
|
372
|
+
.slice(0, MAX_BUSY_SESSIONS),
|
|
373
|
+
}
|
|
374
|
+
: {}),
|
|
365
375
|
};
|
|
366
376
|
}
|
|
367
377
|
catch {
|
|
@@ -379,6 +389,8 @@ export async function createCheckpoint(input) {
|
|
|
379
389
|
try {
|
|
380
390
|
const store = await ensureStore(worktreePath);
|
|
381
391
|
const indexFile = tempIndexFile(sessionId, 'create');
|
|
392
|
+
// Both ends of the shutter — see `CreateCheckpointInput.busySessions`.
|
|
393
|
+
const busyBefore = input.busySessions?.() ?? [];
|
|
382
394
|
const built = await buildIndex(store, worktreePath, indexFile);
|
|
383
395
|
if (built.tooLarge) {
|
|
384
396
|
fs.rmSync(indexFile, { force: true });
|
|
@@ -386,6 +398,7 @@ export async function createCheckpoint(input) {
|
|
|
386
398
|
}
|
|
387
399
|
const { headSha, included, excluded: skippedFiles, byteCount } = built;
|
|
388
400
|
const tree = await gitStore(store, worktreePath, indexFile, 'write-tree');
|
|
401
|
+
const busySessions = [...new Set([...busyBefore, ...(input.busySessions?.() ?? [])])].slice(0, MAX_BUSY_SESSIONS);
|
|
389
402
|
const stagedPaths = splitZ(await gitIn(worktreePath, 'diff', '--cached', '--name-only', '-z')).filter((file) => !isSecretPath(path.join(worktreePath, file)));
|
|
390
403
|
const meta = {
|
|
391
404
|
kind,
|
|
@@ -397,6 +410,9 @@ export async function createCheckpoint(input) {
|
|
|
397
410
|
...(input.agentAnchor ? { agentAnchor: input.agentAnchor } : {}),
|
|
398
411
|
...(input.agentSession ? { agentSession: input.agentSession } : {}),
|
|
399
412
|
...(input.messageSeq === undefined ? {} : { messageSeq: input.messageSeq }),
|
|
413
|
+
// Written only when non-empty: an empty array in every ordinary record
|
|
414
|
+
// would be noise in a commit message that is read by eye during support.
|
|
415
|
+
...(busySessions.length > 0 ? { busySessions } : {}),
|
|
400
416
|
};
|
|
401
417
|
const commit = await gitStore(store, worktreePath, indexFile, 'commit-tree', tree, '-m', encodeMeta(meta));
|
|
402
418
|
const ordinal = await nextOrdinal(store, worktreePath, sessionId);
|
|
@@ -466,6 +482,15 @@ async function currentTree(store, worktreePath, indexFile) {
|
|
|
466
482
|
headSha: built.headSha,
|
|
467
483
|
};
|
|
468
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* How many neighbour ids a restore point carries (#310).
|
|
487
|
+
*
|
|
488
|
+
* The same number as the API's schema and the runner's event: the mark itself
|
|
489
|
+
* is the warning, the names are a courtesy, and a folder with eleven busy
|
|
490
|
+
* sessions is not eleven times more dangerous than one with ten. Gotcha 433 —
|
|
491
|
+
* a cap on a courtesy must degrade, never reject.
|
|
492
|
+
*/
|
|
493
|
+
export const MAX_BUSY_SESSIONS = 10;
|
|
469
494
|
const MAX_PREVIEW_ENTRIES = 5_000;
|
|
470
495
|
/** What a rewind to this checkpoint would do, without doing any of it. */
|
|
471
496
|
export async function previewRewind(input) {
|
|
@@ -538,6 +563,13 @@ export async function previewRewind(input) {
|
|
|
538
563
|
checkpointHeadSha: record.headSha,
|
|
539
564
|
treeOid: tree,
|
|
540
565
|
...(blockedReason ? { blockedReason } : {}),
|
|
566
|
+
// Presence, not length (gotcha 433): the KEY is the warning, the ids are a
|
|
567
|
+
// courtesy that may resolve to no names at all. A marked point whose list
|
|
568
|
+
// came back empty would otherwise look trustworthy here while `applyRewind`
|
|
569
|
+
// went on refusing it.
|
|
570
|
+
...(record.busySessions !== undefined
|
|
571
|
+
? { untrustedFiles: { sessions: record.busySessions } }
|
|
572
|
+
: {}),
|
|
541
573
|
...(commitsSince.length ? { commitsSince } : {}),
|
|
542
574
|
...(total > MAX_PREVIEW_ENTRIES ? { truncated: true, totalChanges: total } : {}),
|
|
543
575
|
};
|
|
@@ -574,6 +606,12 @@ export async function applyRewind(input) {
|
|
|
574
606
|
if (preview.blockedReason) {
|
|
575
607
|
throw new Error(rewindBlockMessage(preview.blockedReason));
|
|
576
608
|
}
|
|
609
|
+
// #310. Before the safety point, not after: taking one is a write into the
|
|
610
|
+
// folder, and refusing afterwards would leave a restore point nobody asked
|
|
611
|
+
// for behind every refusal.
|
|
612
|
+
if (preview.untrustedFiles) {
|
|
613
|
+
throw new Error(UNTRUSTED_FILES_MESSAGE);
|
|
614
|
+
}
|
|
577
615
|
const MOVED = 'The working tree changed while you were looking at it — open the preview again';
|
|
578
616
|
// The whole state, not just the deletions (QA-120 B1). `read-tree --reset -u`
|
|
579
617
|
// writes the RESTORE list as well, and that list is recomputed HERE — so a
|
|
@@ -586,7 +624,12 @@ export async function applyRewind(input) {
|
|
|
586
624
|
if (expected.length !== echoed.length || expected.some((p, i) => p !== echoed[i])) {
|
|
587
625
|
throw new Error(MOVED);
|
|
588
626
|
}
|
|
589
|
-
const safetyResult = await createCheckpoint({
|
|
627
|
+
const safetyResult = await createCheckpoint({
|
|
628
|
+
worktreePath,
|
|
629
|
+
sessionId,
|
|
630
|
+
kind: 'SAFETY',
|
|
631
|
+
...(input.busySessions ? { busySessions: input.busySessions } : {}),
|
|
632
|
+
});
|
|
590
633
|
if (!safetyResult.created) {
|
|
591
634
|
throw new Error(safetyResult.reason === 'too-large'
|
|
592
635
|
? 'The working tree is too large to take a safety point — the rewind was not started'
|
|
@@ -617,6 +660,12 @@ export async function applyRewind(input) {
|
|
|
617
660
|
rewoundToKind: record.kind,
|
|
618
661
|
};
|
|
619
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Said when the files of a restore point cannot be trusted (#310). Its own
|
|
665
|
+
* sentence rather than a `blockedReason`, for the reason written on
|
|
666
|
+
* `RewindPreview.untrustedFiles`.
|
|
667
|
+
*/
|
|
668
|
+
export const UNTRUSTED_FILES_MESSAGE = 'The files at this restore point cannot be trusted: another session was working in this folder when it was taken. Only the conversation can be rewound to it.';
|
|
620
669
|
export function rewindBlockMessage(reason) {
|
|
621
670
|
switch (reason) {
|
|
622
671
|
case 'head-moved':
|
package/dist/git.d.ts
CHANGED
|
@@ -1,8 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why git stopped, in a sentence a person can act on.
|
|
3
|
+
*
|
|
4
|
+
* `git worktree add` prints its progress to stderr as carriage-return-overwritten
|
|
5
|
+
* frames, so a process killed mid-checkout leaves «Updating files: 1% (51/2693)»
|
|
6
|
+
* as the last thing anybody sees — a number, with no verb. The real cause lives
|
|
7
|
+
* on the error object (`killed`, `signal`, `code`) and nobody was reading it
|
|
8
|
+
* (#360). Keep the last progress frame, because «how far did it get» is the one
|
|
9
|
+
* useful thing in it, and put the cause in front of it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function describeGitFailure(error: unknown, context?: {
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}): string;
|
|
14
|
+
/** What went wrong while building the session's workplace, in one machine-readable word. */
|
|
15
|
+
export type WorktreePrepareCode = 'worktree_prepare_failed' | 'branch_taken' | 'worktree_cleanup_failed';
|
|
16
|
+
/**
|
|
17
|
+
* A preparation failure that already knows its own fork point.
|
|
18
|
+
*
|
|
19
|
+
* The fork point matters precisely BECAUSE preparation failed: without it the
|
|
20
|
+
* API keeps `base_sha = null` for the session's whole life, and «Continue» can
|
|
21
|
+
* never prove that the branch it is about to adopt is the empty one this very
|
|
22
|
+
* session created a moment ago (#360, plan Р6).
|
|
23
|
+
*/
|
|
24
|
+
export declare class WorktreePrepareError extends Error {
|
|
25
|
+
readonly code: WorktreePrepareCode;
|
|
26
|
+
readonly baseBranch?: string;
|
|
27
|
+
readonly baseSha?: string;
|
|
28
|
+
constructor(message: string, code: WorktreePrepareCode, base?: {
|
|
29
|
+
baseBranch?: string;
|
|
30
|
+
baseSha?: string;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
1
33
|
export interface PathValidation {
|
|
2
34
|
ok: boolean;
|
|
3
35
|
exists: boolean;
|
|
4
36
|
isGitRepo: boolean;
|
|
5
37
|
branch?: string;
|
|
38
|
+
/** The repository's own main branch, as this machine can see it without the network. */
|
|
39
|
+
defaultBranch?: string;
|
|
6
40
|
error?: string;
|
|
7
41
|
}
|
|
8
42
|
/**
|
|
@@ -16,6 +50,23 @@ export interface PathValidation {
|
|
|
16
50
|
* '/opt/ids'». That sentence is true and unactionable.
|
|
17
51
|
*/
|
|
18
52
|
export declare function validateWorkspacePath(workspacePath: string): Promise<PathValidation>;
|
|
53
|
+
/**
|
|
54
|
+
* The repository's main branch, read locally (ADR 0004).
|
|
55
|
+
*
|
|
56
|
+
* `origin/HEAD` is the honest answer and it is already on disk — set by `clone`,
|
|
57
|
+
* refreshed by `git remote set-head`. Never asked over the network: this runs
|
|
58
|
+
* inside a 4-second probe budget, and a repository whose origin is unreachable
|
|
59
|
+
* must still bind.
|
|
60
|
+
*/
|
|
61
|
+
export declare function remoteDefaultBranch(workspacePath: string): Promise<string | null>;
|
|
62
|
+
/**
|
|
63
|
+
* `main`, then `master`, and only if the repository really has one.
|
|
64
|
+
*
|
|
65
|
+
* The order and the list are one decision, not two: `gitops.ts` asks the same
|
|
66
|
+
* question when «Apply» has no pinned base, and two copies would drift the day
|
|
67
|
+
* somebody adds `develop` to one of them (ADR 0004).
|
|
68
|
+
*/
|
|
69
|
+
export declare function firstConventionalBranch(workspacePath: string): Promise<string | null>;
|
|
19
70
|
export declare function sessionShortId(sessionId: string): string;
|
|
20
71
|
export declare function sessionWorktreePath(sessionId: string): string;
|
|
21
72
|
/**
|
|
@@ -69,6 +120,19 @@ export declare function ensureSessionWorktree(workspacePath: string, sessionId:
|
|
|
69
120
|
requireExistingBranch?: boolean;
|
|
70
121
|
plan?: BranchPlan;
|
|
71
122
|
}): Promise<SessionWorktree>;
|
|
123
|
+
/**
|
|
124
|
+
* Delete a session branch that provably carries no commits (#360).
|
|
125
|
+
*
|
|
126
|
+
* Shares its safety rungs with {@link deleteSessionBranch} — prune, then the
|
|
127
|
+
* folder's own branch, then the holder — and adds the one this caller needs:
|
|
128
|
+
* the tip must still be exactly the fork point we started from. `-D`, not `-d`,
|
|
129
|
+
* because `-d` asks «is it merged into the branch THIS folder is on», which is
|
|
130
|
+
* a different question and answers «no» for a branch that is empty.
|
|
131
|
+
*/
|
|
132
|
+
export declare function dropEmptySessionBranch(workspacePath: string, branch: string, expectedSha: string): Promise<{
|
|
133
|
+
removed: boolean;
|
|
134
|
+
leftover?: string;
|
|
135
|
+
}>;
|
|
72
136
|
/**
|
|
73
137
|
* DIRECT mode (session 16): the session's workplace IS the project folder.
|
|
74
138
|
*
|