@sagentlab/navarch-runtime 0.1.59 → 0.1.61
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/git-worktree.cjs +64 -1
- package/dist/session.cjs +5 -1
- package/package.json +1 -1
package/dist/git-worktree.cjs
CHANGED
|
@@ -37,6 +37,8 @@ class GitWorktree {
|
|
|
37
37
|
sessionTokenMarker;
|
|
38
38
|
hostedCheckpointRef;
|
|
39
39
|
hostedCheckpointLocalRef;
|
|
40
|
+
/** Set when the resumed remote branch had lost its ownership commit and must be re-marked. */
|
|
41
|
+
adoptedUnmarkedBranch = false;
|
|
40
42
|
constructor(options) {
|
|
41
43
|
const projectKey = safePathSegment(options.projectId);
|
|
42
44
|
const sessionKey = safePathSegment(options.sessionId);
|
|
@@ -185,7 +187,9 @@ class GitWorktree {
|
|
|
185
187
|
this.worktreePath,
|
|
186
188
|
startRef,
|
|
187
189
|
], false);
|
|
188
|
-
|
|
190
|
+
// An adopted branch gets its marker back, or the pre-push guard refuses
|
|
191
|
+
// every delivery push and the next retry is stuck again.
|
|
192
|
+
if ((!taskRef && !staleTip) || this.adoptedUnmarkedBranch)
|
|
189
193
|
await this.createTaskOwnershipCommit();
|
|
190
194
|
if (checkpoint)
|
|
191
195
|
await this.applyHostedCheckpoint(checkpoint);
|
|
@@ -293,6 +297,7 @@ class GitWorktree {
|
|
|
293
297
|
if (tip.code !== 0 || !/^[0-9a-f]{40,64}$/i.test(staleSha))
|
|
294
298
|
return null;
|
|
295
299
|
const owned = this.branch.endsWith(this.legacyTaskBranchSuffix) ||
|
|
300
|
+
this.adoptedUnmarkedBranch ||
|
|
296
301
|
(await this.hasTaskOwnershipMarker(localRef));
|
|
297
302
|
if (!owned) {
|
|
298
303
|
throw new Error(`Local branch ${this.branch} already exists in the cached repository but does not ` +
|
|
@@ -360,6 +365,9 @@ class GitWorktree {
|
|
|
360
365
|
const matches = [...ownedShortMatches, ...legacyMatches];
|
|
361
366
|
if (matches.length === 0) {
|
|
362
367
|
if (shortMatches.length > 0) {
|
|
368
|
+
const adopted = await this.adoptUnmarkedOwnBranch(shortMatches);
|
|
369
|
+
if (adopted)
|
|
370
|
+
return adopted;
|
|
363
371
|
throw new Error(`Remote branch ${shortMatches[0]} shares task suffix ${this.taskBranchSuffix} but does not ` +
|
|
364
372
|
`contain ownership marker ${this.taskOwnershipTrailer}; refusing to resume or fork.`);
|
|
365
373
|
}
|
|
@@ -372,6 +380,36 @@ class GitWorktree {
|
|
|
372
380
|
this.branch = remoteRef.replace(/^refs\/remotes\/origin\//, "");
|
|
373
381
|
return remoteRef;
|
|
374
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* An agent that amends, resets, or rebases away the empty claim commit and
|
|
385
|
+
* pushes leaves this task's branch without its marker, and every retry then
|
|
386
|
+
* refused it (carova #10). The branch is still ours when it carries exactly
|
|
387
|
+
* the name this task allocates (slug and suffix) and none of its own commits
|
|
388
|
+
* claim another task; anything else stays refused.
|
|
389
|
+
*/
|
|
390
|
+
async adoptUnmarkedOwnBranch(shortMatches) {
|
|
391
|
+
const ownRef = `refs/remotes/origin/${this.branch}`;
|
|
392
|
+
if (shortMatches.length !== 1 || shortMatches[0] !== ownRef)
|
|
393
|
+
return null;
|
|
394
|
+
const base = await this.resolveStartRef().catch(() => null);
|
|
395
|
+
if (!base)
|
|
396
|
+
return null;
|
|
397
|
+
const log = await this.runner.run("git", [
|
|
398
|
+
"--git-dir",
|
|
399
|
+
this.repositoryPath,
|
|
400
|
+
"log",
|
|
401
|
+
"--format=%B",
|
|
402
|
+
ownRef,
|
|
403
|
+
"--not",
|
|
404
|
+
base,
|
|
405
|
+
]);
|
|
406
|
+
if (log.code !== 0)
|
|
407
|
+
return null;
|
|
408
|
+
if (log.stdout.split("\n").some((line) => /^Navarch-Task-ID:/i.test(line.trim())))
|
|
409
|
+
return null;
|
|
410
|
+
this.adoptedUnmarkedBranch = true;
|
|
411
|
+
return ownRef;
|
|
412
|
+
}
|
|
375
413
|
/** Short branch names are ambiguous without a full task-ID marker in their history. */
|
|
376
414
|
async hasTaskOwnershipMarker(ref) {
|
|
377
415
|
const log = await this.runGit([
|
|
@@ -387,6 +425,14 @@ class GitWorktree {
|
|
|
387
425
|
.split("\n")
|
|
388
426
|
.some((line) => line.trim() === this.taskOwnershipTrailer);
|
|
389
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* The ownership line a push to the delivery branch must keep, for the
|
|
430
|
+
* pre-push guard (`NAVARCH_DELIVERY_TASK_MARKER`). Null for a resumed legacy
|
|
431
|
+
* full-UUID branch, whose name alone proves ownership.
|
|
432
|
+
*/
|
|
433
|
+
get deliveryTaskMarker() {
|
|
434
|
+
return this.branch.endsWith(this.legacyTaskBranchSuffix) ? null : this.taskOwnershipTrailer;
|
|
435
|
+
}
|
|
390
436
|
/** Records full task ownership without lengthening the human-facing branch name. */
|
|
391
437
|
async createTaskOwnershipCommit() {
|
|
392
438
|
await this.runGit([
|
|
@@ -657,6 +703,11 @@ async function pathExists(value) {
|
|
|
657
703
|
* the allocated `navarch/...` branch (for example onto Codex's default
|
|
658
704
|
* `codex/...` prefix). `--no-verify` bypasses any hook, so
|
|
659
705
|
* `resolveDeliveryHead` also repairs the worktree before completion.
|
|
706
|
+
*
|
|
707
|
+
* With `NAVARCH_DELIVERY_TASK_MARKER` it also refuses a delivery-branch push
|
|
708
|
+
* whose history lost the task's ownership commit (an amend, reset, or rebase
|
|
709
|
+
* drops the empty claim commit). A retry only resumes a short-suffix branch
|
|
710
|
+
* that carries the marker, so an unmarked push strands the task.
|
|
660
711
|
*/
|
|
661
712
|
const DELIVERY_BRANCH_GUARD_HOOK = `#!/bin/sh
|
|
662
713
|
# Managed by @sagentlab/navarch-runtime (git-worktree.cts).
|
|
@@ -672,6 +723,18 @@ while read -r local_ref local_sha remote_ref remote_sha; do
|
|
|
672
723
|
if [ "$branch" != "$allowed" ]; then
|
|
673
724
|
printf '%s\\n' "navarch: refusing to push '$branch'; this task's delivery branch is '$allowed'." >&2
|
|
674
725
|
status=1
|
|
726
|
+
continue
|
|
727
|
+
fi
|
|
728
|
+
marker="\${NAVARCH_DELIVERY_TASK_MARKER:-}"
|
|
729
|
+
[ -n "$marker" ] || continue
|
|
730
|
+
case "$local_sha" in
|
|
731
|
+
*[!0]*) ;;
|
|
732
|
+
*) continue ;;
|
|
733
|
+
esac
|
|
734
|
+
if [ -z "$(git log -1 --format=%H --fixed-strings --grep="$marker" "$local_sha" 2>/dev/null)" ]; then
|
|
735
|
+
printf '%s\\n' "navarch: refusing to push '$branch'; its history no longer contains the task ownership line '$marker' (the empty 'chore(navarch): claim task branch' commit was amended, reset, or rebased away)." >&2
|
|
736
|
+
printf '%s\\n' "navarch: restore it with: git commit --allow-empty -m 'chore(navarch): claim task branch' -m '$marker' && git push" >&2
|
|
737
|
+
status=1
|
|
675
738
|
fi
|
|
676
739
|
done
|
|
677
740
|
exit "$status"
|
package/dist/session.cjs
CHANGED
|
@@ -420,8 +420,12 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
420
420
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workDir, "prompt.md"), promptText, "utf8");
|
|
421
421
|
}
|
|
422
422
|
// Preparation may resume an existing task branch with an older slug or ID format.
|
|
423
|
-
if (!allowBranchRename)
|
|
423
|
+
if (!allowBranchRename) {
|
|
424
424
|
sessionEnv.NAVARCH_DELIVERY_BRANCH = gitWorktree.branch;
|
|
425
|
+
const marker = gitWorktree.deliveryTaskMarker;
|
|
426
|
+
if (marker)
|
|
427
|
+
sessionEnv.NAVARCH_DELIVERY_TASK_MARKER = marker;
|
|
428
|
+
}
|
|
425
429
|
if (sandbox) {
|
|
426
430
|
await sandbox.create();
|
|
427
431
|
// Security-policy refusals (a mount outside the allowlist, an egress
|
package/package.json
CHANGED