@sagentlab/navarch-runtime 0.1.44 → 0.1.46
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/adapters/claude.cjs +2 -2
- package/dist/git-worktree.cjs +69 -0
- package/dist/github-pr.cjs +1 -1
- package/dist/session.cjs +14 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -490,7 +490,7 @@ set the standard `reasoning_effort` configuration option when it is available.
|
|
|
490
490
|
Machine-wide extra arguments still configure other CLI behavior; dispatched
|
|
491
491
|
model policy wins.
|
|
492
492
|
|
|
493
|
-
Codex defaults to `gpt-6-astra`; Claude Code defaults to `claude-
|
|
493
|
+
Codex defaults to `gpt-6-astra`; Claude Code defaults to `claude-opus-5`.
|
|
494
494
|
Explicit project and `steering/navarch.yaml` model choices still win. A task's
|
|
495
495
|
explicit execution profile wins over the repository's per-task-type profile.
|
|
496
496
|
Otherwise, claim-time classification uses the task summary and metadata:
|
|
@@ -505,7 +505,7 @@ The classifier is deterministic and conservative; it never chooses `critical`
|
|
|
505
505
|
or `deep` automatically. Set an explicit profile to override its assessment.
|
|
506
506
|
Existing explicit task profiles remain explicit.
|
|
507
507
|
|
|
508
|
-
Fable sessions pass `--fallback-model claude-opus-5` for Claude Code's native
|
|
508
|
+
Explicitly selected Fable sessions pass `--fallback-model claude-opus-5` for Claude Code's native
|
|
509
509
|
[availability fallback](https://code.claude.com/docs/en/model-config#fallback-model-chains).
|
|
510
510
|
Because native fallback excludes usage limits, a structured 429 usage-limit
|
|
511
511
|
rejection gets one additional Opus attempt within the original timeout. It
|
|
@@ -516,7 +516,7 @@ that retry. If Opus is also limited, the usual machine cooldown applies.
|
|
|
516
516
|
The runtime records the fallback in the transcript and reports Opus for the
|
|
517
517
|
retry and subsequent turns. Native multi-model turns still report the requested
|
|
518
518
|
primary model; their aggregate provider-reported usage includes both models.
|
|
519
|
-
|
|
519
|
+
Selecting Fable requires a Claude Code version supporting Fable 5.1 (v2.1.255 or later).
|
|
520
520
|
|
|
521
521
|
Astra token cost reporting uses a standard short-context API-equivalent
|
|
522
522
|
estimate from [OpenAI pricing](https://developers.openai.com/api/docs/pricing),
|
package/dist/adapters/claude.cjs
CHANGED
|
@@ -5,7 +5,7 @@ exports.runClaudeCodeAdapter = runClaudeCodeAdapter;
|
|
|
5
5
|
const node_child_process_1 = require("node:child_process");
|
|
6
6
|
const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
|
|
7
7
|
const adapter_capacity_cjs_1 = require("../adapter-capacity.cjs");
|
|
8
|
-
const DEFAULT_CLAUDE_MODEL = "claude-
|
|
8
|
+
const DEFAULT_CLAUDE_MODEL = "claude-opus-5";
|
|
9
9
|
const FALLBACK_CLAUDE_MODEL = "claude-opus-5";
|
|
10
10
|
function hasFlag(args, flag) {
|
|
11
11
|
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
@@ -35,7 +35,7 @@ async function runClaudeCodeAdapter(options) {
|
|
|
35
35
|
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
36
36
|
}
|
|
37
37
|
const model = options.model ?? DEFAULT_CLAUDE_MODEL;
|
|
38
|
-
const automaticFallback = [
|
|
38
|
+
const automaticFallback = ["claude-fable-5-1", "fable"].includes(model) &&
|
|
39
39
|
!hasFlag(options.extraArgs, "--fallback-model");
|
|
40
40
|
const startedAt = Date.now();
|
|
41
41
|
const primary = await runClaudeAttempt({ ...options, model }, automaticFallback);
|
package/dist/git-worktree.cjs
CHANGED
|
@@ -56,6 +56,7 @@ class GitWorktree {
|
|
|
56
56
|
await node_fs_1.promises.mkdir(this.sessionRoot, { recursive: true });
|
|
57
57
|
await withRepositoryLock(this.repositoryPath, async () => {
|
|
58
58
|
await this.ensureRepositoryCache();
|
|
59
|
+
await this.installDeliveryBranchGuard();
|
|
59
60
|
try {
|
|
60
61
|
await this.addSessionWorktree();
|
|
61
62
|
}
|
|
@@ -94,6 +95,22 @@ class GitWorktree {
|
|
|
94
95
|
await node_fs_1.promises.chmod(configPath, 0o600);
|
|
95
96
|
await node_fs_1.promises.appendFile(configPath, block, { encoding: "utf8", mode: 0o600 });
|
|
96
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Installs the delivery-branch guard shared by every worktree of this cache.
|
|
100
|
+
* The hook is inert unless the session exports `NAVARCH_DELIVERY_BRANCH`, so
|
|
101
|
+
* PR-continuation sessions that must push a reviewed PR's head branch are
|
|
102
|
+
* unaffected. Root tasks export the allocated `navarch/...` branch, which
|
|
103
|
+
* stops an agent from renaming the branch (e.g. Codex's default `codex/...`
|
|
104
|
+
* prefix) and stranding the PR on a branch the task does not own.
|
|
105
|
+
*/
|
|
106
|
+
async installDeliveryBranchGuard(repositoryPath = this.repositoryPath) {
|
|
107
|
+
const hooksDir = node_path_1.default.join(repositoryPath, "hooks");
|
|
108
|
+
await node_fs_1.promises.mkdir(hooksDir, { recursive: true });
|
|
109
|
+
await node_fs_1.promises.writeFile(node_path_1.default.join(hooksDir, "pre-push"), DELIVERY_BRANCH_GUARD_HOOK, {
|
|
110
|
+
encoding: "utf8",
|
|
111
|
+
mode: 0o755,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
97
114
|
/** Clones the bare cache if missing, repoints origin if needed, and fetches all branches. */
|
|
98
115
|
async ensureRepositoryCache() {
|
|
99
116
|
if (!(await pathExists(node_path_1.default.join(this.repositoryPath, "HEAD")))) {
|
|
@@ -312,6 +329,8 @@ class GitWorktree {
|
|
|
312
329
|
if (!/unknown option|usage: git worktree add/i.test(errorMessage(error)))
|
|
313
330
|
throw error;
|
|
314
331
|
await this.runGit(["init", "--initial-branch", this.branch, this.worktreePath], false);
|
|
332
|
+
// This fallback is a standalone repository, so it does not share cache hooks.
|
|
333
|
+
await this.installDeliveryBranchGuard(node_path_1.default.join(this.worktreePath, ".git"));
|
|
315
334
|
await this.runGit(["-C", this.worktreePath, "remote", "add", "origin", this.cloneUrl], false);
|
|
316
335
|
}
|
|
317
336
|
}
|
|
@@ -337,6 +356,30 @@ class GitWorktree {
|
|
|
337
356
|
sha,
|
|
338
357
|
};
|
|
339
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Resolves the head the session reports to the control plane after an agent
|
|
361
|
+
* turn.
|
|
362
|
+
*
|
|
363
|
+
* Root tasks own exactly one delivery branch. If an agent renamed or switched
|
|
364
|
+
* away from the allocated `navarch/...` branch, the worktree is moved back
|
|
365
|
+
* onto it so completion evidence and the PR land on the branch the task owns.
|
|
366
|
+
* PR-continuation tasks (review-fix, conflict resolution, stale-PR recovery)
|
|
367
|
+
* pass `allowRename` so they can check out and push the reviewed PR's head
|
|
368
|
+
* branch, which is the one case a different branch is expected.
|
|
369
|
+
*/
|
|
370
|
+
async resolveDeliveryHead(allowRename) {
|
|
371
|
+
const head = await this.resolveCurrentHead();
|
|
372
|
+
if (allowRename || head.branch === this.branch)
|
|
373
|
+
return head;
|
|
374
|
+
try {
|
|
375
|
+
await this.runGit(["-C", this.worktreePath, "checkout", "-B", this.branch, "HEAD"], false);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
throw new Error(`Could not restore task delivery branch ${this.branch} after the agent left ` +
|
|
379
|
+
`${head.branch ?? "a detached HEAD"}: ${errorMessage(error)}`);
|
|
380
|
+
}
|
|
381
|
+
return { branch: this.branch, sha: head.sha };
|
|
382
|
+
}
|
|
340
383
|
async cleanup() {
|
|
341
384
|
await withRepositoryLock(this.repositoryPath, async () => {
|
|
342
385
|
await this.removeRepoLocalGithubToken();
|
|
@@ -443,6 +486,32 @@ async function pathExists(value) {
|
|
|
443
486
|
return false;
|
|
444
487
|
}
|
|
445
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Pre-push guard installed in the repository cache's hooks directory. When
|
|
491
|
+
* `NAVARCH_DELIVERY_BRANCH` is present it refuses to create, update, or delete
|
|
492
|
+
* any other branch, so a coding agent cannot move a root task's delivery off
|
|
493
|
+
* the allocated `navarch/...` branch (for example onto Codex's default
|
|
494
|
+
* `codex/...` prefix). `--no-verify` bypasses any hook, so
|
|
495
|
+
* `resolveDeliveryHead` also repairs the worktree before completion.
|
|
496
|
+
*/
|
|
497
|
+
const DELIVERY_BRANCH_GUARD_HOOK = `#!/bin/sh
|
|
498
|
+
# Managed by @sagentlab/navarch-runtime (git-worktree.cts).
|
|
499
|
+
allowed="\${NAVARCH_DELIVERY_BRANCH:-}"
|
|
500
|
+
[ -n "$allowed" ] || exit 0
|
|
501
|
+
status=0
|
|
502
|
+
while read -r local_ref local_sha remote_ref remote_sha; do
|
|
503
|
+
case "$remote_ref" in
|
|
504
|
+
refs/heads/*) ;;
|
|
505
|
+
*) continue ;;
|
|
506
|
+
esac
|
|
507
|
+
branch="\${remote_ref#refs/heads/}"
|
|
508
|
+
if [ "$branch" != "$allowed" ]; then
|
|
509
|
+
printf '%s\\n' "navarch: refusing to push '$branch'; this task's delivery branch is '$allowed'." >&2
|
|
510
|
+
status=1
|
|
511
|
+
fi
|
|
512
|
+
done
|
|
513
|
+
exit "$status"
|
|
514
|
+
`;
|
|
446
515
|
/**
|
|
447
516
|
* Kebab-case slug for the task branch, derived from the task summary so
|
|
448
517
|
* branch names read like `navarch/fix-login-redirect-<task-id>` instead of a
|
package/dist/github-pr.cjs
CHANGED
|
@@ -85,7 +85,7 @@ async function findHeadBranchPullRequestUrl(options) {
|
|
|
85
85
|
(!normalizedHeadSha ||
|
|
86
86
|
(typeof candidate.head.sha === "string" &&
|
|
87
87
|
candidate.head.sha.toLowerCase() === normalizedHeadSha)));
|
|
88
|
-
if (branchMatch || !normalizedHeadSha)
|
|
88
|
+
if (branchMatch || !normalizedHeadSha || options.allowShaFallback === false)
|
|
89
89
|
return branchMatch;
|
|
90
90
|
const commitUrl = new URL(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${normalizedHeadSha}/pulls`);
|
|
91
91
|
commitUrl.searchParams.set("per_page", "10");
|
package/dist/session.cjs
CHANGED
|
@@ -69,7 +69,7 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
69
69
|
? "auto"
|
|
70
70
|
: runtime === "opencode" || runtime === "acp"
|
|
71
71
|
? "default"
|
|
72
|
-
: "claude-
|
|
72
|
+
: "claude-opus-5",
|
|
73
73
|
reasoning_effort: "medium",
|
|
74
74
|
};
|
|
75
75
|
const crashDetail = describeCompletionError(err).slice(0, 1800);
|
|
@@ -107,7 +107,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
107
107
|
? "auto"
|
|
108
108
|
: runtime === "opencode" || runtime === "acp"
|
|
109
109
|
? "default"
|
|
110
|
-
: "claude-
|
|
110
|
+
: "claude-opus-5",
|
|
111
111
|
reasoning_effort: "medium",
|
|
112
112
|
};
|
|
113
113
|
// Isolation posture reported on every completion so `sessions` records what
|
|
@@ -193,6 +193,11 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
193
193
|
githubToken,
|
|
194
194
|
repoLocalGithubToken: secrets["github-pat"],
|
|
195
195
|
});
|
|
196
|
+
// A root task owns one delivery branch, so the pre-push guard and the
|
|
197
|
+
// completion-time reconciliation keep the agent on the allocated
|
|
198
|
+
// `navarch/...` branch. PR-continuation tasks are the exception: their agent
|
|
199
|
+
// must check out and push the reviewed PR's existing head branch.
|
|
200
|
+
const allowBranchRename = Boolean(task.review_of_task_id);
|
|
196
201
|
const knownGuidanceIds = new Set((bundle.guidance ?? []).map((entry) => entry.id));
|
|
197
202
|
const deliveredGuidance = [...(bundle.guidance ?? [])];
|
|
198
203
|
let guidanceCursor = bundle.guidance_cursor ?? null;
|
|
@@ -344,6 +349,9 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
344
349
|
lifecycle.enteredMainSessionTry = true;
|
|
345
350
|
try {
|
|
346
351
|
await gitWorktree.prepare();
|
|
352
|
+
// Preparation may resume an existing task branch with an older slug or ID format.
|
|
353
|
+
if (!allowBranchRename)
|
|
354
|
+
sessionEnv.NAVARCH_DELIVERY_BRANCH = gitWorktree.branch;
|
|
347
355
|
if (sandbox) {
|
|
348
356
|
await sandbox.create();
|
|
349
357
|
// Security-policy refusals (a mount outside the allowlist, an egress
|
|
@@ -435,7 +443,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
435
443
|
let headBranch = gitWorktree.branch;
|
|
436
444
|
let headSha;
|
|
437
445
|
try {
|
|
438
|
-
const currentHead = await gitWorktree.
|
|
446
|
+
const currentHead = await gitWorktree.resolveDeliveryHead(allowBranchRename);
|
|
439
447
|
headBranch = currentHead.branch ?? headBranch;
|
|
440
448
|
headSha = currentHead.sha;
|
|
441
449
|
}
|
|
@@ -448,6 +456,9 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
448
456
|
repository: bundle.repository?.full_name ?? task.repo,
|
|
449
457
|
headBranch,
|
|
450
458
|
headSha,
|
|
459
|
+
// Keep SHA verification on the exact branch, but do not adopt a
|
|
460
|
+
// foreign-branch PR as delivery evidence for a root task.
|
|
461
|
+
allowShaFallback: allowBranchRename,
|
|
451
462
|
githubToken,
|
|
452
463
|
});
|
|
453
464
|
if (prUrl)
|
package/package.json
CHANGED