@sagentlab/navarch-runtime 0.1.79 → 0.1.81
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 +37 -0
- package/dist/adapters/index.cjs +6 -1
- package/dist/adapters/pi-local.cjs +20 -0
- package/dist/adapters/pi.cjs +68 -10
- package/dist/config.cjs +9 -1
- package/dist/session.cjs +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -941,3 +941,40 @@ over the project's session model for the selected adapter only; switching agent
|
|
|
941
941
|
types does not reuse another adapter's pin. Reconnect without `--model` to return
|
|
942
942
|
to project-controlled model selection. Older identities without a model keep
|
|
943
943
|
using the control-plane execution policy.
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
### Pi on your own machine
|
|
947
|
+
|
|
948
|
+
Select **Pi (local)** in Connect an agent, or set `NAVARCH_AGENT=pi-local`
|
|
949
|
+
and `NAVARCH_RUNTIMES=pi-local`. Use a current Pi CLI with native MCP support
|
|
950
|
+
and Node.js 22.19 or newer. Host mode requires `NAVARCH_WORKTREE_GUARD=off`:
|
|
951
|
+
Pi's shell tools do not enforce the runtime's worktree boundary. The UI's
|
|
952
|
+
connect and supervise commands include this opt-out. Use a dedicated worker.
|
|
953
|
+
Docker is not advertised for this adapter yet.
|
|
954
|
+
|
|
955
|
+
The initial model is `qwen3.8-flash-next-iq2_xs`, served by a local
|
|
956
|
+
OpenAI-compatible endpoint at `http://127.0.0.1:8080/v1`. Start that model server
|
|
957
|
+
separately. Set `NAVARCH_PI_LOCAL_BASE_URL` in the worker environment for a
|
|
958
|
+
different endpoint (for example, Ollama's `http://127.0.0.1:11434/v1`).
|
|
959
|
+
`NAVARCH_PI_BIN` overrides the executable; `NAVARCH_PI_EXTRA_ARGS` adds CLI
|
|
960
|
+
arguments. `--model` at connect pins this machine's model as for other agents.
|
|
961
|
+
|
|
962
|
+
Bare model IDs first resolve a unique matching model in the operator's catalog;
|
|
963
|
+
ambiguous matches require an explicit `provider/model`. Without a match, they
|
|
964
|
+
use the generated `local` provider. Setting `NAVARCH_PI_LOCAL_BASE_URL` explicitly
|
|
965
|
+
selects that generated provider. Exact `provider/model`
|
|
966
|
+
references use the machine's existing Pi catalog and credentials from
|
|
967
|
+
`~/.pi/agent/models.json` and `auth.json` (or `PI_CODING_AGENT_DIR`). Run
|
|
968
|
+
`pi --list-models` to inspect that catalog. The UI puts the local default first
|
|
969
|
+
and labels public cloud suggestions by provider; suggestions do not imply
|
|
970
|
+
credentials or access. Custom model IDs remain editable.
|
|
971
|
+
|
|
972
|
+
Per-session configuration lives in a temporary agent directory, including
|
|
973
|
+
Navarch's authenticated `mcp.json`. Pi runs without persisted sessions,
|
|
974
|
+
automatically loaded extensions, skills, or project configuration. Temporary
|
|
975
|
+
configuration is removed at exit. Hosted Pi keeps its separate `pi` runtime,
|
|
976
|
+
DeepSeek provider, and billing policy; local sessions use `pi-local` and the
|
|
977
|
+
project's `pi_local_model` policy.
|
|
978
|
+
|
|
979
|
+
Pi CLI and model configuration reference:
|
|
980
|
+
https://github.com/earendil-works/pi/tree/main/packages/coding-agent/docs
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runQoderAdapter = exports.qoderAdapter = exports.runAcpAdapter = exports.acpAdapter = exports.runPiAdapter = exports.piAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
3
|
+
exports.runQoderAdapter = exports.qoderAdapter = exports.runAcpAdapter = exports.acpAdapter = exports.runPiAdapter = exports.piAdapter = exports.runLocalPiAdapter = exports.localPiAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
4
4
|
exports.selectAdapter = selectAdapter;
|
|
5
5
|
const claude_cjs_1 = require("./claude.cjs");
|
|
6
6
|
Object.defineProperty(exports, "claudeCodeAdapter", { enumerable: true, get: function () { return claude_cjs_1.claudeCodeAdapter; } });
|
|
@@ -14,6 +14,9 @@ Object.defineProperty(exports, "runGeminiAdapter", { enumerable: true, get: func
|
|
|
14
14
|
const opencode_cjs_1 = require("./opencode.cjs");
|
|
15
15
|
Object.defineProperty(exports, "openCodeAdapter", { enumerable: true, get: function () { return opencode_cjs_1.openCodeAdapter; } });
|
|
16
16
|
Object.defineProperty(exports, "runOpenCodeAdapter", { enumerable: true, get: function () { return opencode_cjs_1.runOpenCodeAdapter; } });
|
|
17
|
+
const pi_local_cjs_1 = require("./pi-local.cjs");
|
|
18
|
+
Object.defineProperty(exports, "localPiAdapter", { enumerable: true, get: function () { return pi_local_cjs_1.localPiAdapter; } });
|
|
19
|
+
Object.defineProperty(exports, "runLocalPiAdapter", { enumerable: true, get: function () { return pi_local_cjs_1.runLocalPiAdapter; } });
|
|
17
20
|
const pi_cjs_1 = require("./pi.cjs");
|
|
18
21
|
Object.defineProperty(exports, "piAdapter", { enumerable: true, get: function () { return pi_cjs_1.piAdapter; } });
|
|
19
22
|
Object.defineProperty(exports, "runPiAdapter", { enumerable: true, get: function () { return pi_cjs_1.runPiAdapter; } });
|
|
@@ -44,6 +47,8 @@ function selectAdapter(agentType) {
|
|
|
44
47
|
return qoder_cjs_1.qoderAdapter;
|
|
45
48
|
case "claude-code":
|
|
46
49
|
return claude_cjs_1.claudeCodeAdapter;
|
|
50
|
+
case "pi-local":
|
|
51
|
+
return pi_local_cjs_1.localPiAdapter;
|
|
47
52
|
case "pi":
|
|
48
53
|
// Managed hosted runtime (#885): reachable only on a managed runner
|
|
49
54
|
// executing a `runtime: "pi"` claim — config.cts never produces "pi"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.localPiAdapter = void 0;
|
|
4
|
+
exports.runLocalPiAdapter = runLocalPiAdapter;
|
|
5
|
+
const pi_cjs_1 = require("./pi.cjs");
|
|
6
|
+
/** BYO Pi: native MCP, operator provider catalog, and a local OpenAI-compatible default. */
|
|
7
|
+
async function runLocalPiAdapter(options) {
|
|
8
|
+
if (options.dockerExec)
|
|
9
|
+
throw new Error("Pi (local) requires host mode");
|
|
10
|
+
if (options.signal?.aborted)
|
|
11
|
+
return { exitCode: null, timedOut: false, killedByLeaseLoss: true, stdout: "", stderr: "" };
|
|
12
|
+
const model = options.model ?? "qwen3.8-flash-next-iq2_xs";
|
|
13
|
+
const args = ["--mode", "json", "--no-session", "--no-extensions", "--no-skills", "--no-approve", ...options.extraArgs,
|
|
14
|
+
"--model", model.includes("/") ? model : `local/${model}`];
|
|
15
|
+
if (options.reasoningEffort)
|
|
16
|
+
args.push("--thinking", options.reasoningEffort === "max" ? "xhigh" : options.reasoningEffort);
|
|
17
|
+
args.push("-p", options.prompt);
|
|
18
|
+
return (0, pi_cjs_1.attachPiOutput)(await (0, pi_cjs_1.runOnHost)(options, args, true));
|
|
19
|
+
}
|
|
20
|
+
exports.localPiAdapter = { agentType: "pi-local", run: runLocalPiAdapter };
|
package/dist/adapters/pi.cjs
CHANGED
|
@@ -8,7 +8,9 @@ exports.buildHostedPiBudgetExtension = buildHostedPiBudgetExtension;
|
|
|
8
8
|
exports.resolveHostedPiModel = resolveHostedPiModel;
|
|
9
9
|
exports.buildPiProviderConfig = buildPiProviderConfig;
|
|
10
10
|
exports.parsePiJsonEvents = parsePiJsonEvents;
|
|
11
|
+
exports.attachPiOutput = attachPiOutput;
|
|
11
12
|
exports.runPiAdapter = runPiAdapter;
|
|
13
|
+
exports.runOnHost = runOnHost;
|
|
12
14
|
const node_child_process_1 = require("node:child_process");
|
|
13
15
|
const node_fs_1 = require("node:fs");
|
|
14
16
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -163,10 +165,20 @@ function attachPiOutput(result) {
|
|
|
163
165
|
let costUsd;
|
|
164
166
|
let sawUsage = false;
|
|
165
167
|
let reportText = "";
|
|
168
|
+
let terminalError;
|
|
166
169
|
for (const event of events) {
|
|
167
170
|
const message = event.message;
|
|
171
|
+
if (event.type === "error") {
|
|
172
|
+
terminalError = typeof event.error === "string" ? event.error :
|
|
173
|
+
typeof event.errorMessage === "string" ? event.errorMessage : "Pi provider error";
|
|
174
|
+
}
|
|
168
175
|
if (event.type !== "message_end" || !message || message.role !== "assistant")
|
|
169
176
|
continue;
|
|
177
|
+
// Pi can settle an exhausted provider retry with process exit code zero.
|
|
178
|
+
// A later successful assistant message supersedes a recovered error.
|
|
179
|
+
terminalError = message.stopReason === "error" || message.errorMessage
|
|
180
|
+
? typeof message.errorMessage === "string" ? message.errorMessage : "Pi provider error"
|
|
181
|
+
: undefined;
|
|
170
182
|
const text = extractText(message.content);
|
|
171
183
|
if (text.trim())
|
|
172
184
|
reportText = text.trim();
|
|
@@ -191,6 +203,9 @@ function attachPiOutput(result) {
|
|
|
191
203
|
}
|
|
192
204
|
return {
|
|
193
205
|
...result,
|
|
206
|
+
...(terminalError && result.exitCode === 0 && !result.timedOut && !result.killedByLeaseLoss
|
|
207
|
+
? { exitCode: 1, stderr: `${result.stderr}${result.stderr ? "\n" : ""}Pi provider failed: ${terminalError}` }
|
|
208
|
+
: {}),
|
|
194
209
|
...(sawUsage
|
|
195
210
|
? {
|
|
196
211
|
// AdapterResult semantics: tokensIn folds every input category
|
|
@@ -245,18 +260,61 @@ async function runPiAdapter(options) {
|
|
|
245
260
|
: await runOnHost(options, args);
|
|
246
261
|
return attachPiOutput(raw);
|
|
247
262
|
}
|
|
248
|
-
async function runOnHost(options, args) {
|
|
249
|
-
//
|
|
250
|
-
//
|
|
263
|
+
async function runOnHost(options, args, local = false) {
|
|
264
|
+
// Hosted sessions never read operator auth; BYO sessions copy only provider
|
|
265
|
+
// config and credentials. Both paths clean up their isolated agent directory.
|
|
251
266
|
const agentDir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "navarch-pi-agent-"));
|
|
252
|
-
await node_fs_1.promises.writeFile(node_path_1.default.join(agentDir, "models.json"), buildPiProviderConfig(), { mode: 0o600 });
|
|
253
|
-
const budgetExtensionPath = node_path_1.default.join(agentDir, BUDGET_EXTENSION_NAME);
|
|
254
|
-
await node_fs_1.promises.writeFile(budgetExtensionPath, buildHostedPiBudgetExtension(), {
|
|
255
|
-
mode: 0o600,
|
|
256
|
-
});
|
|
257
|
-
const env = { ...options.env, PI_CODING_AGENT_DIR: agentDir };
|
|
258
|
-
const boundedArgs = withBudgetExtension(args, budgetExtensionPath);
|
|
259
267
|
try {
|
|
268
|
+
if (local) {
|
|
269
|
+
const sourceDir = options.env.PI_CODING_AGENT_DIR ?? process.env.PI_CODING_AGENT_DIR ?? node_path_1.default.join(node_os_1.default.homedir(), ".pi", "agent");
|
|
270
|
+
for (const name of ["models.json", "auth.json"]) {
|
|
271
|
+
try {
|
|
272
|
+
await node_fs_1.promises.copyFile(node_path_1.default.join(sourceDir, name), node_path_1.default.join(agentDir, name));
|
|
273
|
+
await node_fs_1.promises.chmod(node_path_1.default.join(agentDir, name), 0o600);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error.code !== "ENOENT")
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const model = options.model ?? "qwen3.8-flash-next-iq2_xs";
|
|
281
|
+
if (!model.includes("/")) {
|
|
282
|
+
const configPath = node_path_1.default.join(agentDir, "models.json");
|
|
283
|
+
const config = JSON.parse(await node_fs_1.promises.readFile(configPath, "utf8").catch(() => "{}"));
|
|
284
|
+
config.providers ??= {};
|
|
285
|
+
const endpointOverride = options.env.NAVARCH_PI_LOCAL_BASE_URL ?? process.env.NAVARCH_PI_LOCAL_BASE_URL;
|
|
286
|
+
const matches = endpointOverride ? [] : Object.entries(config.providers).filter(([, provider]) => provider.models?.some((entry) => entry.id === model)).map(([name]) => name);
|
|
287
|
+
if (matches.length > 1)
|
|
288
|
+
throw new Error(`Pi model ${model} matches multiple providers; select an explicit provider/model.`);
|
|
289
|
+
if (matches.length === 1) {
|
|
290
|
+
args = [...args];
|
|
291
|
+
args[args.lastIndexOf("--model") + 1] = `${matches[0]}/${model}`;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// Without a catalog match, retain the default local endpoint behavior.
|
|
295
|
+
config.providers.local = {
|
|
296
|
+
baseUrl: endpointOverride ?? "http://127.0.0.1:8080/v1",
|
|
297
|
+
api: "openai-completions", apiKey: "local",
|
|
298
|
+
models: [{ id: model, reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }],
|
|
299
|
+
};
|
|
300
|
+
await node_fs_1.promises.writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (options.mcpConfigPath) {
|
|
304
|
+
await node_fs_1.promises.copyFile(options.mcpConfigPath, node_path_1.default.join(agentDir, "mcp.json"));
|
|
305
|
+
await node_fs_1.promises.chmod(node_path_1.default.join(agentDir, "mcp.json"), 0o600);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
await node_fs_1.promises.writeFile(node_path_1.default.join(agentDir, "models.json"), buildPiProviderConfig(), { mode: 0o600 });
|
|
310
|
+
}
|
|
311
|
+
const budgetExtensionPath = node_path_1.default.join(agentDir, BUDGET_EXTENSION_NAME);
|
|
312
|
+
if (!local)
|
|
313
|
+
await node_fs_1.promises.writeFile(budgetExtensionPath, buildHostedPiBudgetExtension(), {
|
|
314
|
+
mode: 0o600,
|
|
315
|
+
});
|
|
316
|
+
const env = { ...options.env, PI_CODING_AGENT_DIR: agentDir };
|
|
317
|
+
const boundedArgs = local ? args : withBudgetExtension(args, budgetExtensionPath);
|
|
260
318
|
return await new Promise((resolve) => {
|
|
261
319
|
let stdout = "";
|
|
262
320
|
let stderr = "";
|
package/dist/config.cjs
CHANGED
|
@@ -25,6 +25,7 @@ exports.RUNTIME_AGENT_TYPES = [
|
|
|
25
25
|
"opencode",
|
|
26
26
|
"acp",
|
|
27
27
|
"qoder",
|
|
28
|
+
"pi-local",
|
|
28
29
|
];
|
|
29
30
|
function isRuntimeAgentType(value) {
|
|
30
31
|
return typeof value === "string" && exports.RUNTIME_AGENT_TYPES.includes(value);
|
|
@@ -105,7 +106,12 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
105
106
|
// fails closed there (adapters/opencode.cts). Advertising it anyway meant
|
|
106
107
|
// the machine claimed OpenCode tasks it could only fail, one lease at a
|
|
107
108
|
// time; keep it out of the claim instead of discovering this per session.
|
|
108
|
-
const runnableRuntimes = requestedRuntimes.filter((runtime) => runtime
|
|
109
|
+
const runnableRuntimes = requestedRuntimes.filter((runtime) => runtime === "pi-local"
|
|
110
|
+
? sandboxMode === "host" && !worktreeGuard
|
|
111
|
+
: runtime !== "opencode" || !opencodeBlockedOnHost(sandboxMode, worktreeGuard));
|
|
112
|
+
if (requestedRuntimes.includes("pi-local") && runnableRuntimes.length === 0) {
|
|
113
|
+
throw new Error("Pi (local) requires NAVARCH_SANDBOX_MODE=host and NAVARCH_WORKTREE_GUARD=off");
|
|
114
|
+
}
|
|
109
115
|
if (requestedRuntimes.includes("opencode") && runnableRuntimes.length === 0) {
|
|
110
116
|
throw new Error(exports.OPENCODE_HOST_GUARD_MESSAGE);
|
|
111
117
|
}
|
|
@@ -143,6 +149,8 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
143
149
|
opencodeExtraArgs: envList(env, "NAVARCH_OPENCODE_EXTRA_ARGS", []),
|
|
144
150
|
acpBin: env.NAVARCH_ACP_BIN ?? "dsh",
|
|
145
151
|
acpExtraArgs: envList(env, "NAVARCH_ACP_EXTRA_ARGS", ["--profile", "acp"]),
|
|
152
|
+
piBin: env.NAVARCH_PI_BIN ?? "pi",
|
|
153
|
+
piExtraArgs: envList(env, "NAVARCH_PI_EXTRA_ARGS", []),
|
|
146
154
|
qoderBin: env.NAVARCH_QODER_BIN ?? "qoder",
|
|
147
155
|
qoderExtraArgs: envList(env, "NAVARCH_QODER_EXTRA_ARGS", []),
|
|
148
156
|
gitAuthorName: env.NAVARCH_GIT_AUTHOR_NAME ?? "sagentlab",
|
package/dist/session.cjs
CHANGED
|
@@ -46,9 +46,10 @@ function resolveSessionExecution(config, claimed) {
|
|
|
46
46
|
const execution = claimed.context_bundle.execution ?? {
|
|
47
47
|
profile: claimed.task.execution_profile ?? "standard",
|
|
48
48
|
model: runtime === "codex" ? "gpt-6-astra"
|
|
49
|
-
: runtime === "
|
|
50
|
-
: runtime === "
|
|
51
|
-
: "
|
|
49
|
+
: runtime === "pi-local" ? "qwen3.8-flash-next-iq2_xs"
|
|
50
|
+
: runtime === "gemini" ? "auto"
|
|
51
|
+
: runtime === "opencode" || runtime === "acp" || runtime === "qoder" ? "default"
|
|
52
|
+
: "claude-opus-5-5",
|
|
52
53
|
reasoning_effort: "medium",
|
|
53
54
|
};
|
|
54
55
|
return {
|
|
@@ -865,6 +866,8 @@ function adapterCommand(config, runtime) {
|
|
|
865
866
|
return { bin: config.opencodeBin, extraArgs: config.opencodeExtraArgs };
|
|
866
867
|
case "acp":
|
|
867
868
|
return { bin: config.acpBin, extraArgs: config.acpExtraArgs };
|
|
869
|
+
case "pi-local":
|
|
870
|
+
return { bin: config.piBin, extraArgs: config.piExtraArgs };
|
|
868
871
|
case "qoder":
|
|
869
872
|
return { bin: config.qoderBin, extraArgs: config.qoderExtraArgs };
|
|
870
873
|
}
|
package/package.json
CHANGED