@sagentlab/navarch-runtime 0.1.38 → 0.1.40
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 +56 -0
- package/dist/adapters/claude.cjs +67 -0
- package/dist/cli.cjs +6 -0
- package/dist/code-graph.cjs +198 -0
- package/dist/codex-pricing.cjs +14 -5
- package/dist/prompt.cjs +3 -1
- package/dist/session.cjs +9 -5
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -473,6 +473,39 @@ account's selection.
|
|
|
473
473
|
Machine-wide extra arguments still configure other CLI behavior; dispatched
|
|
474
474
|
model policy wins.
|
|
475
475
|
|
|
476
|
+
Codex defaults to `gpt-6-astra`; Claude Code defaults to `claude-fable-5-1`.
|
|
477
|
+
Explicit project and `steering/navarch.yaml` model choices still win. A task's
|
|
478
|
+
explicit execution profile wins over the repository's per-task-type profile.
|
|
479
|
+
Otherwise, claim-time classification uses the task summary and metadata:
|
|
480
|
+
|
|
481
|
+
- Clearly scoped typo, spelling, link, wording, or label fixes use `fast` / `low`.
|
|
482
|
+
- Planning, retries, video work, tasks with at least three dependencies,
|
|
483
|
+
production operations, and descriptions indicating architectural or sensitive
|
|
484
|
+
changes use `complex` / `high`.
|
|
485
|
+
- Ambiguous work uses `standard` / the project effort baseline (`medium` by default).
|
|
486
|
+
|
|
487
|
+
The classifier is deterministic and conservative; it never chooses `critical`
|
|
488
|
+
or `deep` automatically. Set an explicit profile to override its assessment.
|
|
489
|
+
Existing explicit task profiles remain explicit.
|
|
490
|
+
|
|
491
|
+
Fable sessions pass `--fallback-model claude-opus-5` for Claude Code's native
|
|
492
|
+
[availability fallback](https://code.claude.com/docs/en/model-config#fallback-model-chains).
|
|
493
|
+
Because native fallback excludes usage limits, a structured 429 usage-limit
|
|
494
|
+
rejection gets one additional Opus attempt within the original timeout. It
|
|
495
|
+
resumes the saved conversation, or starts afresh only for a confirmed zero-turn
|
|
496
|
+
rejection. Missing transcripts after possible work, authentication/policy
|
|
497
|
+
errors, cancellation, and explicit fallback/session arguments do not trigger
|
|
498
|
+
that retry. If Opus is also limited, the usual machine cooldown applies.
|
|
499
|
+
The runtime records the fallback in the transcript and reports Opus for the
|
|
500
|
+
retry and subsequent turns. Native multi-model turns still report the requested
|
|
501
|
+
primary model; their aggregate provider-reported usage includes both models.
|
|
502
|
+
Requires a Claude Code version supporting Fable 5.1 (v2.1.255 or later).
|
|
503
|
+
|
|
504
|
+
Astra token cost reporting uses a standard short-context API-equivalent
|
|
505
|
+
estimate from [OpenAI pricing](https://developers.openai.com/api/docs/pricing),
|
|
506
|
+
not the account's subscription charge or long-context/fast-mode uplifts.
|
|
507
|
+
|
|
508
|
+
|
|
476
509
|
The Codex CLI invocation was verified against `codex-cli 0.144.1` on
|
|
477
510
|
2026-07-18. The runtime uses `codex exec "<prompt>" --json` and translates
|
|
478
511
|
the existing per-session MCP JSON into one-off `-c mcp_servers.*` overrides.
|
|
@@ -683,3 +716,26 @@ not match, making it a clean, zero-touch way to keep this package fully
|
|
|
683
716
|
isolated. Vite/Vitest's default esbuild transform filter also excludes
|
|
684
717
|
`.cts` by default — `vitest.config.cts` overrides it (`esbuild.include`) so
|
|
685
718
|
tests are actually type-stripped and run.
|
|
719
|
+
|
|
720
|
+
## Inspect code dependencies
|
|
721
|
+
|
|
722
|
+
`navarch-runtime code-graph --query saveInvoice --mode impact --commit HEAD`
|
|
723
|
+
returns JSON with the indexed commit, checkout HEAD, dirty-worktree warning,
|
|
724
|
+
source paths and line numbers, and bounded dependency edges. Modes are `search`,
|
|
725
|
+
`callers`, `callees`, and `impact`; impact accepts `--depth 1` through `5`.
|
|
726
|
+
Use an exact node ID to disambiguate duplicate symbol names. A file query includes
|
|
727
|
+
its functions and file import dependencies. `--repo` defaults to the current
|
|
728
|
+
working directory. The CLI works without a Navarch login or network access.
|
|
729
|
+
|
|
730
|
+
This first implementation analyzes committed JavaScript and TypeScript with the
|
|
731
|
+
TypeScript compiler. It resolves static imports and named functions/methods,
|
|
732
|
+
including imported aliases. It does not execute repository code, follow symlinks,
|
|
733
|
+
load tsconfig plugins, or upload source. Root tsconfig path aliases are supported;
|
|
734
|
+
extended configs, dynamic dispatch, external dependencies and other languages
|
|
735
|
+
are not fully resolved. Warnings report omissions. No graph is an assurance that
|
|
736
|
+
a change has no other effects: inspect the actual diff and run relevant tests.
|
|
737
|
+
|
|
738
|
+
Each invocation rebuilds from Git blobs at the requested commit, avoiding stale
|
|
739
|
+
persistent indexes. Bounds are 2,000 files, 10 MB total source, 500 KB per file,
|
|
740
|
+
100 returned nodes and 300 edges. A later persistent index can optimize large
|
|
741
|
+
repositories without changing the query format.
|
package/dist/adapters/claude.cjs
CHANGED
|
@@ -4,6 +4,12 @@ exports.claudeCodeAdapter = void 0;
|
|
|
4
4
|
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
|
+
const adapter_capacity_cjs_1 = require("../adapter-capacity.cjs");
|
|
8
|
+
const DEFAULT_CLAUDE_MODEL = "claude-fable-5-1";
|
|
9
|
+
const FALLBACK_CLAUDE_MODEL = "claude-opus-5";
|
|
10
|
+
function hasFlag(args, flag) {
|
|
11
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
12
|
+
}
|
|
7
13
|
/**
|
|
8
14
|
* Headless Claude Code adapter (project-plan.md §3.9 / implementation-plan.md
|
|
9
15
|
* WP-07): `claude -p "<context bundle>" --mcp-config platform-mcp.json
|
|
@@ -25,6 +31,65 @@ const exit_conditions_cjs_1 = require("../exit-conditions.cjs");
|
|
|
25
31
|
* default) rather than throwing -- see runtime/README.md.
|
|
26
32
|
*/
|
|
27
33
|
async function runClaudeCodeAdapter(options) {
|
|
34
|
+
if (options.signal?.aborted) {
|
|
35
|
+
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
36
|
+
}
|
|
37
|
+
const model = options.model ?? DEFAULT_CLAUDE_MODEL;
|
|
38
|
+
const automaticFallback = [DEFAULT_CLAUDE_MODEL, "fable"].includes(model) &&
|
|
39
|
+
!hasFlag(options.extraArgs, "--fallback-model");
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
const primary = await runClaudeAttempt({ ...options, model }, automaticFallback);
|
|
42
|
+
const remainingMs = options.timeoutMs - (Date.now() - startedAt);
|
|
43
|
+
if (!automaticFallback || primary.timedOut || primary.killedByLeaseLoss ||
|
|
44
|
+
options.signal?.aborted || remainingMs <= 0)
|
|
45
|
+
return primary;
|
|
46
|
+
const parsed = (0, exit_conditions_cjs_1.parseClaudeJsonResult)(primary.stdout) ?? (0, exit_conditions_cjs_1.parseClaudeJsonResult)(primary.stderr);
|
|
47
|
+
// Native fallback handles overload/unavailability, but explicitly excludes
|
|
48
|
+
// 429 usage limits. Try Opus once; if the limit is account-wide, the ordinary
|
|
49
|
+
// capacity cooldown still sees the fallback's failure.
|
|
50
|
+
if (!parsed?.is_error || !(0, adapter_capacity_cjs_1.detectAdapterCapacityLimit)(primary))
|
|
51
|
+
return primary;
|
|
52
|
+
const canResume = typeof parsed.session_id === "string" &&
|
|
53
|
+
/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(parsed.session_id) &&
|
|
54
|
+
!hasFlag(options.extraArgs, "--no-session-persistence");
|
|
55
|
+
// Never replay a task that may already have side effects without its saved
|
|
56
|
+
// conversation. A structured zero-turn rejection is safe to start afresh.
|
|
57
|
+
if (!canResume && parsed.num_turns !== 0)
|
|
58
|
+
return primary;
|
|
59
|
+
if (["--resume", "-r", "--continue", "-c", "--session-id", "--fork-session"].some((flag) => hasFlag(options.extraArgs, flag)))
|
|
60
|
+
return primary;
|
|
61
|
+
const fallback = await runClaudeAttempt({
|
|
62
|
+
...options,
|
|
63
|
+
model: FALLBACK_CLAUDE_MODEL,
|
|
64
|
+
timeoutMs: remainingMs,
|
|
65
|
+
prompt: canResume
|
|
66
|
+
? "Continue the interrupted task from the saved conversation. Check existing work before taking further action."
|
|
67
|
+
: options.prompt,
|
|
68
|
+
extraArgs: canResume
|
|
69
|
+
? [...options.extraArgs, "--resume", parsed.session_id]
|
|
70
|
+
: options.extraArgs,
|
|
71
|
+
}, false);
|
|
72
|
+
return {
|
|
73
|
+
...fallback,
|
|
74
|
+
model: FALLBACK_CLAUDE_MODEL,
|
|
75
|
+
// Keep the provider rejection in the transcript without letting the old
|
|
76
|
+
// JSON result override the final attempt's exit condition/capacity signal.
|
|
77
|
+
stderr: [
|
|
78
|
+
`Navarch model fallback: ${model} -> ${FALLBACK_CLAUDE_MODEL}`,
|
|
79
|
+
...primary.stderr.split("\n").map((line) => `Primary stderr: ${line}`),
|
|
80
|
+
...primary.stdout.split("\n").map((line) => `Primary stdout: ${line}`),
|
|
81
|
+
fallback.stderr,
|
|
82
|
+
].join("\n"),
|
|
83
|
+
tokensIn: addUsage(primary.tokensIn, fallback.tokensIn),
|
|
84
|
+
tokensOut: addUsage(primary.tokensOut, fallback.tokensOut),
|
|
85
|
+
cacheHitTokensIn: addUsage(primary.cacheHitTokensIn, fallback.cacheHitTokensIn),
|
|
86
|
+
costUsd: addUsage(primary.costUsd, fallback.costUsd),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function addUsage(first, second) {
|
|
90
|
+
return first === undefined && second === undefined ? undefined : (first ?? 0) + (second ?? 0);
|
|
91
|
+
}
|
|
92
|
+
async function runClaudeAttempt(options, automaticFallback) {
|
|
28
93
|
const args = ["-p", options.prompt];
|
|
29
94
|
const hasSettingSources = options.extraArgs.some((arg) => arg === "--setting-sources" || arg.startsWith("--setting-sources="));
|
|
30
95
|
const hasExplicitPermissionMode = options.extraArgs.some((arg) => ["--permission-mode", "--permission-prompt-tool", "--dangerously-skip-permissions"].some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
@@ -76,6 +141,8 @@ async function runClaudeCodeAdapter(options) {
|
|
|
76
141
|
// dispatched session consistently uses the settings recorded by Navarch.
|
|
77
142
|
if (options.model)
|
|
78
143
|
args.push("--model", options.model);
|
|
144
|
+
if (automaticFallback)
|
|
145
|
+
args.push("--fallback-model", FALLBACK_CLAUDE_MODEL);
|
|
79
146
|
if (options.reasoningEffort)
|
|
80
147
|
args.push("--effort", options.reasoningEffort);
|
|
81
148
|
const runOptions = options.reasoningEffort
|
package/dist/cli.cjs
CHANGED
|
@@ -307,6 +307,7 @@ Usage:
|
|
|
307
307
|
navarch-runtime start [--config-dir <path>] [--agent claude-code|codex|gemini|opencode]
|
|
308
308
|
navarch-runtime supervise [--config-dir <path>] [--agent claude-code|codex|gemini|opencode]
|
|
309
309
|
navarch-runtime doctor [--config-dir <path>]
|
|
310
|
+
navarch-runtime code-graph --query <symbol-or-file> [--mode search|callers|callees|impact] [--commit HEAD] [--depth 1..5] [--repo <path>]
|
|
310
311
|
|
|
311
312
|
Use a different --config-dir (or NAVARCH_CONFIG_DIR) for every agent instance.
|
|
312
313
|
Other configuration is via NAVARCH_* environment variables; see runtime/README.md.
|
|
@@ -328,6 +329,11 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
328
329
|
case "supervise":
|
|
329
330
|
await superviseCommand(flags);
|
|
330
331
|
break;
|
|
332
|
+
case "code-graph": {
|
|
333
|
+
const { codeGraphCommand } = await import("./code-graph.cjs");
|
|
334
|
+
console.log(JSON.stringify(codeGraphCommand(flags), null, 2));
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
331
337
|
case "doctor":
|
|
332
338
|
await doctorCommand(flags);
|
|
333
339
|
break;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readCodeSnapshot = readCodeSnapshot;
|
|
7
|
+
exports.buildCodeGraph = buildCodeGraph;
|
|
8
|
+
exports.queryCodeGraph = queryCodeGraph;
|
|
9
|
+
exports.codeGraphCommand = codeGraphCommand;
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
13
|
+
const ROOT = "/navarch-repo/";
|
|
14
|
+
const MAX_BYTES = 10_000_000;
|
|
15
|
+
const MAX_FILES = 2_000;
|
|
16
|
+
/** Reads committed blobs only. Never follows worktree symlinks, runs project code, or loads plugins. */
|
|
17
|
+
function readCodeSnapshot(cwd, ref = "HEAD") {
|
|
18
|
+
const git = (args, input) => (0, node_child_process_1.execFileSync)("git", args, {
|
|
19
|
+
cwd, input, encoding: "utf8", maxBuffer: 16_000_000, timeout: 15_000,
|
|
20
|
+
});
|
|
21
|
+
const commit = git(["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`]).trim();
|
|
22
|
+
const head = git(["rev-parse", "HEAD"]).trim();
|
|
23
|
+
const dirty = Boolean(git(["status", "--porcelain", "--untracked-files=no"]).trim());
|
|
24
|
+
const warnings = [];
|
|
25
|
+
if (dirty)
|
|
26
|
+
warnings.push("Tracked worktree edits are not indexed; this graph describes committed code.");
|
|
27
|
+
if (commit !== head)
|
|
28
|
+
warnings.push("Indexed commit differs from checkout HEAD.");
|
|
29
|
+
const files = [];
|
|
30
|
+
let bytes = 0;
|
|
31
|
+
for (const entry of git(["ls-tree", "--full-tree", "-rlz", commit]).split("\0")) {
|
|
32
|
+
const match = /^(100644|100755) blob ([a-f0-9]+) +([0-9]+)\t(.+)$/s.exec(entry);
|
|
33
|
+
if (!match)
|
|
34
|
+
continue;
|
|
35
|
+
const file = match[4];
|
|
36
|
+
if ((!/\.(?:[cm]?[jt]sx?)$/.test(file) && file !== "tsconfig.json") ||
|
|
37
|
+
/(^|\/)(node_modules|dist|build|vendor|\.next)\//.test(file))
|
|
38
|
+
continue;
|
|
39
|
+
const size = Number(match[3]);
|
|
40
|
+
if (/[\r\n]/.test(file) || size > 500_000 || files.length >= MAX_FILES || bytes + size > MAX_BYTES) {
|
|
41
|
+
warnings.push(`Skipped source: ${file}`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
files.push({ file, sha: match[2], size });
|
|
45
|
+
bytes += size;
|
|
46
|
+
}
|
|
47
|
+
const contents = {};
|
|
48
|
+
if (files.length) {
|
|
49
|
+
// Object IDs, rather than paths, form the batch protocol input.
|
|
50
|
+
const output = (0, node_child_process_1.execFileSync)("git", ["cat-file", "--batch"], {
|
|
51
|
+
cwd, input: files.map((f) => f.sha).join("\n") + "\n", maxBuffer: 16_000_000, timeout: 15_000,
|
|
52
|
+
});
|
|
53
|
+
let offset = 0;
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const end = output.indexOf(10, offset);
|
|
56
|
+
if (end < 0 || output.subarray(offset, end).toString() !== `${file.sha} blob ${file.size}`) {
|
|
57
|
+
throw new Error("Invalid git object batch");
|
|
58
|
+
}
|
|
59
|
+
offset = end + 1;
|
|
60
|
+
contents[file.file] = output.subarray(offset, offset + file.size).toString("utf8");
|
|
61
|
+
offset += file.size + 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { commit, head, dirty, contents, warnings };
|
|
65
|
+
}
|
|
66
|
+
function buildCodeGraph(snapshot) {
|
|
67
|
+
const warnings = [...snapshot.warnings];
|
|
68
|
+
const sources = new Map(Object.entries(snapshot.contents).map(([file, text]) => [ROOT + file, text]));
|
|
69
|
+
const config = sources.get(ROOT + "tsconfig.json");
|
|
70
|
+
const raw = config ? typescript_1.default.parseConfigFileTextToJson("tsconfig.json", config) : { config: {} };
|
|
71
|
+
if (raw.error)
|
|
72
|
+
warnings.push("Could not parse tsconfig.json; using default module resolution.");
|
|
73
|
+
if (raw.config?.extends)
|
|
74
|
+
warnings.push("Inherited tsconfig options are not loaded; unresolved aliases may be omitted.");
|
|
75
|
+
const converted = typescript_1.default.convertCompilerOptionsFromJson(raw.config?.compilerOptions ?? {}, ROOT);
|
|
76
|
+
const options = { ...converted.options, allowJs: true, noLib: true, noEmit: true };
|
|
77
|
+
const canonical = (file) => node_path_1.default.posix.normalize(file);
|
|
78
|
+
const host = {
|
|
79
|
+
fileExists: (file) => sources.has(canonical(file)),
|
|
80
|
+
readFile: (file) => sources.get(canonical(file)),
|
|
81
|
+
getSourceFile: (file, languageVersion) => {
|
|
82
|
+
const text = sources.get(canonical(file));
|
|
83
|
+
return text === undefined ? undefined : typescript_1.default.createSourceFile(file, text, languageVersion, true);
|
|
84
|
+
},
|
|
85
|
+
getDefaultLibFileName: () => "", writeFile: () => { }, getCurrentDirectory: () => ROOT,
|
|
86
|
+
getDirectories: () => [], directoryExists: (dir) => [...sources.keys()].some((f) => f.startsWith(dir.replace(/\/$/, "") + "/")),
|
|
87
|
+
getCanonicalFileName: canonical, useCaseSensitiveFileNames: () => true, getNewLine: () => "\n",
|
|
88
|
+
};
|
|
89
|
+
const program = typescript_1.default.createProgram([...sources.keys()].filter((f) => f !== ROOT + "tsconfig.json"), options, host);
|
|
90
|
+
const checker = program.getTypeChecker();
|
|
91
|
+
const nodes = [];
|
|
92
|
+
const declarations = new Map();
|
|
93
|
+
const ownedFunctions = new Map();
|
|
94
|
+
const sourceFiles = program.getSourceFiles().filter((f) => sources.has(f.fileName));
|
|
95
|
+
for (const source of sourceFiles) {
|
|
96
|
+
const file = source.fileName.slice(ROOT.length);
|
|
97
|
+
const fileId = `file:${file}`;
|
|
98
|
+
nodes.push({ id: fileId, name: file, file, line: 1, kind: "file" });
|
|
99
|
+
declarations.set(source, fileId);
|
|
100
|
+
const collect = (node) => {
|
|
101
|
+
let name;
|
|
102
|
+
let body;
|
|
103
|
+
if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isGetAccessorDeclaration(node) || typescript_1.default.isSetAccessorDeclaration(node)) {
|
|
104
|
+
name = node.name;
|
|
105
|
+
body = node.body;
|
|
106
|
+
}
|
|
107
|
+
else if (typescript_1.default.isVariableDeclaration(node) && node.initializer &&
|
|
108
|
+
(typescript_1.default.isArrowFunction(node.initializer) || typescript_1.default.isFunctionExpression(node.initializer))) {
|
|
109
|
+
name = node.name;
|
|
110
|
+
body = node.initializer;
|
|
111
|
+
}
|
|
112
|
+
if (name && body) {
|
|
113
|
+
const position = source.getLineAndCharacterOfPosition(name.getStart(source));
|
|
114
|
+
const line = position.line + 1;
|
|
115
|
+
const id = `${file}:${line}:${position.character + 1}:${name.getText(source)}`;
|
|
116
|
+
nodes.push({ id, name: name.getText(source), file, line, kind: "function" });
|
|
117
|
+
declarations.set(node, id);
|
|
118
|
+
ownedFunctions.set(body, id);
|
|
119
|
+
}
|
|
120
|
+
typescript_1.default.forEachChild(node, collect);
|
|
121
|
+
};
|
|
122
|
+
collect(source);
|
|
123
|
+
}
|
|
124
|
+
const edges = [];
|
|
125
|
+
let unresolved = 0;
|
|
126
|
+
const target = (node) => {
|
|
127
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
128
|
+
if (symbol && symbol.flags & typescript_1.default.SymbolFlags.Alias)
|
|
129
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
130
|
+
const targets = new Set(symbol?.declarations?.map((d) => declarations.get(d)).filter((id) => Boolean(id)));
|
|
131
|
+
return targets.size === 1 ? [...targets][0] : undefined;
|
|
132
|
+
};
|
|
133
|
+
for (const source of sourceFiles) {
|
|
134
|
+
const visit = (node, parent) => {
|
|
135
|
+
const owner = ownedFunctions.get(node) ?? parent;
|
|
136
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
137
|
+
const expression = typescript_1.default.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression;
|
|
138
|
+
const to = target(expression);
|
|
139
|
+
if (to)
|
|
140
|
+
edges.push({ from: owner, to, kind: "call" });
|
|
141
|
+
else
|
|
142
|
+
unresolved++;
|
|
143
|
+
}
|
|
144
|
+
if (typescript_1.default.isImportDeclaration(node) || (typescript_1.default.isExportDeclaration(node) && node.moduleSpecifier)) {
|
|
145
|
+
const to = node.moduleSpecifier ? target(node.moduleSpecifier) : undefined;
|
|
146
|
+
if (to)
|
|
147
|
+
edges.push({ from: declarations.get(source), to, kind: "import" });
|
|
148
|
+
}
|
|
149
|
+
typescript_1.default.forEachChild(node, (child) => visit(child, owner));
|
|
150
|
+
};
|
|
151
|
+
visit(source, declarations.get(source));
|
|
152
|
+
}
|
|
153
|
+
warnings.push(`Static JS/TS analysis only; ${unresolved} external or unresolved calls omitted. Dynamic dispatch and runtime dependencies may be missing.`);
|
|
154
|
+
return { commit: snapshot.commit, head: snapshot.head, dirty: snapshot.dirty, nodes,
|
|
155
|
+
edges: [...new Map(edges.map((e) => [`${e.from}\0${e.to}\0${e.kind}`, e])).values()], warnings };
|
|
156
|
+
}
|
|
157
|
+
function queryCodeGraph(graph, query, mode = "search", depth = 3) {
|
|
158
|
+
if (!query.trim() || query.length > 500)
|
|
159
|
+
throw new Error("Provide a non-empty --query of at most 500 characters");
|
|
160
|
+
if (!["search", "callers", "callees", "impact"].includes(mode))
|
|
161
|
+
throw new Error("--mode must be search, callers, callees, or impact");
|
|
162
|
+
if (!Number.isInteger(depth) || depth < 1 || depth > 5)
|
|
163
|
+
throw new Error("--depth must be 1 through 5");
|
|
164
|
+
const exact = graph.nodes.filter((n) => n.id === query || n.name === query || n.file === query);
|
|
165
|
+
const matches = exact.length ? exact : graph.nodes.filter((n) => n.name.toLowerCase().includes(query.toLowerCase()));
|
|
166
|
+
const selected = new Set(matches.slice(0, 100).map((n) => n.id));
|
|
167
|
+
const paths = [];
|
|
168
|
+
let truncated = matches.length > 100;
|
|
169
|
+
if (mode !== "search") {
|
|
170
|
+
for (let hop = 0; hop < (mode === "impact" ? depth : 1); hop++) {
|
|
171
|
+
const frontier = new Set(selected);
|
|
172
|
+
for (const edge of graph.edges) {
|
|
173
|
+
const from = mode === "callees" ? edge.from : edge.to;
|
|
174
|
+
const to = mode === "callees" ? edge.to : edge.from;
|
|
175
|
+
if (!frontier.has(from))
|
|
176
|
+
continue;
|
|
177
|
+
if (!selected.has(to) && selected.size >= 100) {
|
|
178
|
+
truncated = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (paths.length >= 300) {
|
|
182
|
+
truncated = true;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
selected.add(to);
|
|
186
|
+
if (!paths.some((p) => p.from === edge.from && p.to === edge.to && p.kind === edge.kind))
|
|
187
|
+
paths.push(edge);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { commit: graph.commit, checkout_head: graph.head, dirty: graph.dirty, mode,
|
|
192
|
+
nodes: graph.nodes.filter((n) => selected.has(n.id)), edges: paths,
|
|
193
|
+
truncated, warnings: graph.warnings };
|
|
194
|
+
}
|
|
195
|
+
function codeGraphCommand(flags) {
|
|
196
|
+
const graph = buildCodeGraph(readCodeSnapshot(flags.repo ?? process.cwd(), flags.commit ?? "HEAD"));
|
|
197
|
+
return queryCodeGraph(graph, flags.query ?? "", flags.mode ?? "search", Number(flags.depth ?? 3));
|
|
198
|
+
}
|
package/dist/codex-pricing.cjs
CHANGED
|
@@ -7,14 +7,23 @@ exports.estimateCodexCostUsd = estimateCodexCostUsd;
|
|
|
7
7
|
* usage into project spend. Keep this deliberately limited to models Navarch
|
|
8
8
|
* offers rather than silently applying the wrong price to custom/gateway ids.
|
|
9
9
|
*
|
|
10
|
-
* Source (checked 2026-
|
|
10
|
+
* Source (checked 2026-09-07): https://developers.openai.com/api/docs/pricing
|
|
11
11
|
*/
|
|
12
12
|
const CODEX_RATES = {
|
|
13
|
+
// Standard short-context estimate, verified 2026-09-07 against the pricing
|
|
14
|
+
// source above. As with other runtime estimates, this excludes tier uplifts.
|
|
15
|
+
"gpt-6-astra": {
|
|
16
|
+
input: 10,
|
|
17
|
+
cachedInput: 1,
|
|
18
|
+
cacheWrite: 12.5,
|
|
19
|
+
output: 50,
|
|
20
|
+
},
|
|
21
|
+
// Promotional standard rates, available at least through 2026-11-21.
|
|
13
22
|
"gpt-5.6-sol": {
|
|
14
|
-
input:
|
|
15
|
-
cachedInput: 0.
|
|
16
|
-
cacheWrite:
|
|
17
|
-
output:
|
|
23
|
+
input: 4,
|
|
24
|
+
cachedInput: 0.4,
|
|
25
|
+
cacheWrite: 5,
|
|
26
|
+
output: 20,
|
|
18
27
|
},
|
|
19
28
|
};
|
|
20
29
|
function nonnegative(value) {
|
package/dist/prompt.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderPrompt = renderPrompt;
|
|
4
4
|
exports.renderGuidanceCorrectionPrompt = renderGuidanceCorrectionPrompt;
|
|
5
|
+
const CODE_GRAPH_GUIDANCE = "For JS/TS dependency inspection, the runtime provides `navarch-runtime code-graph --query <symbol-or-file> --mode search|callers|callees|impact --commit HEAD`. If the runtime is not on PATH, use its installed entry point; do not install another copy mid-task. Results describe committed source and include commit/freshness warnings; inspect current files for uncommitted changes and unresolved dependencies.";
|
|
5
6
|
/**
|
|
6
7
|
* Renders the four-layer context bundle (project-plan.md §3.8: steering docs,
|
|
7
8
|
* task itself, task-type playbook, depends_on reports) into the single prompt
|
|
@@ -14,7 +15,7 @@ function renderPrompt(task, bundle) {
|
|
|
14
15
|
const repositoryContext = bundle.repository
|
|
15
16
|
? `Repository: ${bundle.repository.full_name} (${bundle.repository.url})\nLocal checkout: this session's isolated git worktree (current working directory).`
|
|
16
17
|
: null;
|
|
17
|
-
return [repositoryContext, bundle.task_context.trim(), bundle.retry_context?.trim()]
|
|
18
|
+
return [repositoryContext, bundle.task_context.trim(), CODE_GRAPH_GUIDANCE, bundle.retry_context?.trim()]
|
|
18
19
|
.filter((section) => Boolean(section))
|
|
19
20
|
.join("\n\n") + "\n";
|
|
20
21
|
}
|
|
@@ -42,6 +43,7 @@ function renderPrompt(task, bundle) {
|
|
|
42
43
|
}
|
|
43
44
|
sections.push("## Playbook");
|
|
44
45
|
sections.push(bundle.playbook);
|
|
46
|
+
sections.push(CODE_GRAPH_GUIDANCE);
|
|
45
47
|
return sections.join("\n\n");
|
|
46
48
|
}
|
|
47
49
|
/**
|
package/dist/session.cjs
CHANGED
|
@@ -64,12 +64,12 @@ async function runSession(deps, claimed, sessionId) {
|
|
|
64
64
|
const execution = bundle.execution ?? {
|
|
65
65
|
profile: task.execution_profile ?? "standard",
|
|
66
66
|
model: runtime === "codex"
|
|
67
|
-
? "gpt-
|
|
67
|
+
? "gpt-6-astra"
|
|
68
68
|
: runtime === "gemini"
|
|
69
69
|
? "auto"
|
|
70
70
|
: runtime === "opencode"
|
|
71
71
|
? "default"
|
|
72
|
-
: "
|
|
72
|
+
: "claude-fable-5-1",
|
|
73
73
|
reasoning_effort: "medium",
|
|
74
74
|
};
|
|
75
75
|
const crashDetail = describeCompletionError(err).slice(0, 1800);
|
|
@@ -102,12 +102,12 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
102
102
|
const execution = bundle.execution ?? {
|
|
103
103
|
profile: task.execution_profile ?? "standard",
|
|
104
104
|
model: runtime === "codex"
|
|
105
|
-
? "gpt-
|
|
105
|
+
? "gpt-6-astra"
|
|
106
106
|
: runtime === "gemini"
|
|
107
107
|
? "auto"
|
|
108
108
|
: runtime === "opencode"
|
|
109
109
|
? "default"
|
|
110
|
-
: "
|
|
110
|
+
: "claude-fable-5-1",
|
|
111
111
|
reasoning_effort: "medium",
|
|
112
112
|
};
|
|
113
113
|
// Isolation posture reported on every completion so `sessions` records what
|
|
@@ -118,7 +118,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
118
118
|
sandbox_profile: config.sandboxMode === "docker" ? config.sandboxProfile : "host",
|
|
119
119
|
...(config.sandboxMode === "docker" ? { sandbox_image: config.dockerImage } : {}),
|
|
120
120
|
};
|
|
121
|
-
|
|
121
|
+
let executionReport = {
|
|
122
122
|
model: execution.model,
|
|
123
123
|
execution_profile: execution.profile,
|
|
124
124
|
reasoning_effort: execution.reasoning_effort,
|
|
@@ -397,6 +397,10 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
397
397
|
signal: activeAbortController.signal,
|
|
398
398
|
});
|
|
399
399
|
activeAbortController = null;
|
|
400
|
+
if (turnResult.model) {
|
|
401
|
+
execution.model = turnResult.model;
|
|
402
|
+
executionReport = { ...executionReport, model: turnResult.model };
|
|
403
|
+
}
|
|
400
404
|
attempts.push(turnResult);
|
|
401
405
|
const capacityLimit = (0, adapter_capacity_cjs_1.detectAdapterCapacityLimit)(turnResult);
|
|
402
406
|
if (capacityLimit) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sagentlab/navarch-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"description": "Navarch machine-side session manager: claims delivery tasks and runs them through Claude Code, Codex, Gemini, or OpenCode.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.14.0",
|
|
50
|
-
"typescript": "^5.7.2",
|
|
51
50
|
"vitest": "^4.1.10"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"typescript": "^5.7.2"
|
|
52
54
|
}
|
|
53
55
|
}
|