@sagentlab/navarch-runtime 0.1.112 → 0.1.117
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 -18
- package/bin/muse-mcp-bridge.cjs +3 -0
- package/dist/adapters/index.cjs +6 -1
- package/dist/adapters/muse.cjs +433 -0
- package/dist/agent-auth.cjs +2 -1
- package/dist/cli.cjs +4 -1
- package/dist/config.cjs +9 -1
- package/dist/conversation/capability.cjs +1 -1
- package/dist/conversation/loop.cjs +16 -0
- package/dist/conversation/mirror.cjs +225 -0
- package/dist/conversation/workspace.cjs +11 -4
- package/dist/exit-conditions.cjs +1 -1
- package/dist/muse-mcp-bridge.cjs +167 -0
- package/dist/session.cjs +30 -8
- package/dist/worktree-guard.cjs +137 -3
- package/node_modules/@muse-code/sdk/LICENSE +21 -0
- package/node_modules/@muse-code/sdk/README.md +88 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/connection.d.ts +149 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/connection.js +581 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/spawn.d.ts +188 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/spawn.js +496 -0
- package/node_modules/@muse-code/sdk/dist/src/errors.d.ts +71 -0
- package/node_modules/@muse-code/sdk/dist/src/errors.js +76 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/approval.d.ts +96 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/approval.js +293 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/client.d.ts +171 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/client.js +442 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/discarded.d.ts +36 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/discarded.js +36 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/gap-fill.d.ts +80 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/gap-fill.js +388 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-death.d.ts +139 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-death.js +96 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-start.d.ts +62 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-start.js +81 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/session.d.ts +309 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/session.js +654 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-handle.d.ts +156 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-handle.js +271 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-submit.d.ts +72 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-submit.js +183 -0
- package/node_modules/@muse-code/sdk/dist/src/fingerprint.d.ts +37 -0
- package/node_modules/@muse-code/sdk/dist/src/fingerprint.js +54 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/item-store.d.ts +110 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/item-store.js +169 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/session-fold.d.ts +536 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/session-fold.js +427 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/state-store.d.ts +68 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/state-store.js +89 -0
- package/node_modules/@muse-code/sdk/dist/src/index.d.ts +65 -0
- package/node_modules/@muse-code/sdk/dist/src/index.js +57 -0
- package/node_modules/@muse-code/sdk/dist/src/msp.d.ts +3380 -0
- package/node_modules/@muse-code/sdk/dist/src/pending/pending-command-set.d.ts +361 -0
- package/node_modules/@muse-code/sdk/dist/src/pending/pending-command-set.js +468 -0
- package/node_modules/@muse-code/sdk/package.json +47 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# navarch-runtime
|
|
2
2
|
|
|
3
|
+
Conversation mirror isolation and lifecycle fixes are included in runtime 0.1.115.
|
|
4
|
+
|
|
3
5
|
## Managed visual evidence
|
|
4
6
|
|
|
5
7
|
Host-mode build, verify, and QA leases requiring `browser-use` receive a
|
|
@@ -93,7 +95,8 @@ when the run finishes, so the task view can follow a connected run live.
|
|
|
93
95
|
Live snapshots hold the current turn's stderr back until the turn ends.
|
|
94
96
|
|
|
95
97
|
The machine operator selects **Claude Code**, **OpenAI Codex**, **Google
|
|
96
|
-
Antigravity CLI**, **OpenCode**,
|
|
98
|
+
Antigravity CLI**, **OpenCode**, an **Agent Client Protocol (ACP)** agent, **Qoder**,
|
|
99
|
+
**Pi (local)**, or **Muse Code (experimental)** when connecting a worker. Any selected agent can
|
|
97
100
|
run any unpinned project task; task eligibility depends on capabilities and
|
|
98
101
|
project gates. A task pinned to a runtime waits for a worker that advertises
|
|
99
102
|
that adapter and runs on it — see "Choosing an agent" below.
|
|
@@ -116,7 +119,8 @@ Before generating the command, prepare the machine:
|
|
|
116
119
|
- install Node.js 20 or later (the standard installation includes `npm` and
|
|
117
120
|
`npx`) and Git;
|
|
118
121
|
- install the coding-agent CLI selected in Navarch — `claude`, `codex`,
|
|
119
|
-
`agy`, `opencode`, `dsh` for the default ACP integration,
|
|
122
|
+
`agy`, `opencode`, `dsh` for the default ACP integration, `qoder`, `pi`,
|
|
123
|
+
or `muse` — and complete its normal authentication flow; and
|
|
120
124
|
- for Docker-backed sessions, install and start Docker and use an image that
|
|
121
125
|
contains the selected agent CLI and the repository toolchain, with provider
|
|
122
126
|
credentials supplied through the approved machine or project configuration.
|
|
@@ -239,11 +243,11 @@ The from-source flow — `git clone` + `./install.sh` + `node bin/navarch.cjs
|
|
|
239
243
|
|
|
240
244
|
| Command | Purpose |
|
|
241
245
|
|---|---|
|
|
242
|
-
| `register --token <t> --name <n> [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder] […]` | Registers this machine, saves its local agent choice, and prints the token once. |
|
|
243
|
-
| `connect --token <t> --name <n> [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder] [--project <id>] […]` | Connects this machine to one project, saves its local agent choice, and prints the token once. |
|
|
244
|
-
| `start [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder]` | Runs the daemon. A start-time agent choice overrides the saved choice. |
|
|
245
|
-
| `supervise [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder]` | Runs the daemon under the update supervisor, enabling drain-safe automatic updates and rollback. |
|
|
246
|
-
| `doctor [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder]` | Prints resolved config + Docker/registration status; no side effects. |
|
|
246
|
+
| `register --token <t> --name <n> [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder\|muse\|pi-local] […]` | Registers this machine, saves its local agent choice, and prints the token once. |
|
|
247
|
+
| `connect --token <t> --name <n> [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder\|muse\|pi-local] [--project <id>] […]` | Connects this machine to one project, saves its local agent choice, and prints the token once. |
|
|
248
|
+
| `start [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder\|muse\|pi-local]` | Runs the daemon. A start-time agent choice overrides the saved choice. |
|
|
249
|
+
| `supervise [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder\|muse\|pi-local]` | Runs the daemon under the update supervisor, enabling drain-safe automatic updates and rollback. |
|
|
250
|
+
| `doctor [--agent claude-code\|codex\|antigravity\|opencode\|acp\|qoder\|muse\|pi-local]` | Prints resolved config + Docker/registration status; no side effects. |
|
|
247
251
|
|
|
248
252
|
### Running multiple agents on one machine
|
|
249
253
|
|
|
@@ -457,7 +461,7 @@ unchanged across the deployment.
|
|
|
457
461
|
| `NAVARCH_SANDBOX_PROFILE` | `trusted-development` | Named security profile for Docker sessions (`src/sandbox-profile.cts`): `trusted-development` (image-default user, uncapped, open egress), `untrusted-code` (non-root, 2 CPU / 4g / 512 PIDs, deny-by-default egress, read-only shared git), `elevated-verification` (non-root, 4 CPU / 8g / 2048 PIDs, egress limited to GitHub plus package registries). The default is the exact pre-profile flag set. |
|
|
458
462
|
| `NAVARCH_SANDBOX_EGRESS_NETWORK` | _(unset)_ | Docker network that enforces a profile's egress allowlist. Docker cannot filter by domain itself, so an allowlist profile without this fails closed to `--network=none` and records the denial. |
|
|
459
463
|
| `NAVARCH_DOCKER_IMAGE` | `ghcr.io/sagentlab/navarch-sandbox-agent:0.1.0` | Version-pinned per-session image with Node 20, git, GitHub CLI, ripgrep, jq, SSH, and Claude Code 2.1.218. Override with an image tag or digest you control. |
|
|
460
|
-
| `NAVARCH_AGENT` | saved choice, then `claude-code` | Local choice of agent adapter: `claude-code`, `codex`, `
|
|
464
|
+
| `NAVARCH_AGENT` | saved choice, then `claude-code` | Local choice of agent adapter: `claude-code`, `codex`, `antigravity`, `opencode`, `acp`, `qoder`, `muse`, or `pi-local`. Overrides the choice saved by `connect`/`register`; `start --agent` has highest priority. |
|
|
461
465
|
| `NAVARCH_RUNTIMES` | resolved local agent choice | Comma list of installed/authenticated adapters advertised to dispatch. Unpinned tasks run on the preferred agent; a task runtime pin selects the matching adapter. Unrecognized ids are dropped with a warning that names them, so a typo cannot silently shrink the advertised set. |
|
|
462
466
|
| `NAVARCH_UPDATE_CHANNEL` | `stable` | Release channel advertised by the worker (`stable` or `canary`); the server-managed machine channel remains authoritative. |
|
|
463
467
|
| `NAVARCH_AUTO_UPDATE` | on under `supervise` | Set `off`, `false`, or `0` to report releases without staging or activating them. Automatic activation is always off under plain `start`. |
|
|
@@ -473,11 +477,14 @@ unchanged across the deployment.
|
|
|
473
477
|
| `NAVARCH_ACP_EXTRA_ARGS` | `--profile,acp` | Comma list of arguments used to start the ACP server. Override this together with `NAVARCH_ACP_BIN` for another ACP-compatible coding agent. |
|
|
474
478
|
| `NAVARCH_QODER_BIN` | `qoder` | Path/name of the Qoder CLI binary. |
|
|
475
479
|
| `NAVARCH_QODER_EXTRA_ARGS` | — | Comma list of extra CLI args appended after the unattended defaults (`--setting-sources`, `--permission-mode auto`, `--output-format json`). An explicit `--output-format`, `--permission-mode`, `--settings`, `--model`, or `--reasoning-effort` here replaces the per-session default. Qoder selects its own account models; pin one via `--model` here or `connect --model` to override. |
|
|
480
|
+
| `NAVARCH_MUSE_BIN` | `muse` | Path/name of the native Muse Code CLI. Requires authenticated experimental `muse serve` SDK access; the npm SDK does not install the CLI. See [Muse Code setup](../docs/navarch/muse-code.md). |
|
|
481
|
+
| `NAVARCH_MUSE_EXTRA_ARGS` | — | Comma list of arguments passed to `muse serve`. Permission/sandbox bypass flags are rejected. Configure narrow native approval rules rather than enabling blanket approval. |
|
|
476
482
|
| `NAVARCH_MCP_CONFIG_PATH` | — | Path to the platform MCP config passed as `--mcp-config`. |
|
|
477
|
-
| `NAVARCH_WORKTREE_GUARD` | on | Host-mode sessions get an adapter-native per-session worktree boundary guard (see below). Set `off` to disable it for every runtime on the machine — required to run OpenCode on
|
|
483
|
+
| `NAVARCH_WORKTREE_GUARD` | on | Host-mode sessions get an adapter-native per-session worktree boundary guard (see below). Set `off` to disable it for every runtime on the machine — required to run OpenCode or Muse on an already-isolated host. |
|
|
478
484
|
| `NAVARCH_NAVI_CONVERSATIONS` | on | Serve read-only Navi repository conversations on this machine. Only the `claude-code` adapter on the host sandbox with the worktree guard on is supported; `navarch-runtime doctor` prints the `navi_chat:` state and reason. Set `off` to stop polling for conversations. |
|
|
479
485
|
| `NAVARCH_NAVI_WORKSPACE_CHAT` | off | Explicitly enable writable Navi chat on a supported Claude Code host runtime. Owners can choose **Edit and run commands**; automatic permission review and the worktree guard remain active. Commands run on this host, not in a hard sandbox. Changes stay under `<workspaceRoot>/chat-workspaces/<conversationId>/repo` until the operator removes them. See [workspace chat](../docs/navarch/navi-workspace-chat.md). |
|
|
480
486
|
| `NAVARCH_CONVERSATION_POLL_MS` | `5000` | Idle poll interval for queued conversation turns (1000–30000). |
|
|
487
|
+
| `NAVARCH_CONVERSATION_MIRROR` | on | Seed new conversation checkouts from a per-repository mirror under `<workspaceRoot>/conversation-mirrors/` (fetch + local clone with independent object files) instead of a full network clone. Set `off` to always clone directly. |
|
|
481
488
|
| `NAVARCH_CONVERSATION_TURN_TIMEOUT_MS` | `540000` | Time limit for one conversation turn's agent process (at most nine minutes). |
|
|
482
489
|
| `NAVARCH_GUARD_EXTRA_ROOTS` | — | `path.delimiter`-separated (`:` on POSIX) extra directories the worktree guard allows beyond the session worktree, shared bare repo, and temp dirs. |
|
|
483
490
|
|
|
@@ -542,6 +549,18 @@ metadata directory.
|
|
|
542
549
|
OpenCode receives a private, per-session config, project config discovery is
|
|
543
550
|
disabled, and lease MCP headers are referenced through child-only environment
|
|
544
551
|
variables instead of being copied into its config file or argv.
|
|
552
|
+
- **Muse Code (experimental):** guarded hosts do not advertise Muse; a
|
|
553
|
+
Muse-only guarded worker fails closed before claiming. Use a custom Docker
|
|
554
|
+
image with native Muse, its authentication, Node.js, and the matching Navarch
|
|
555
|
+
runtime's `navarch-muse-mcp-bridge` executable. Use a dedicated clean Muse
|
|
556
|
+
configuration with no unrelated MCP servers, plugins, or hooks: session MCP
|
|
557
|
+
is additive to native configuration. The default image contains
|
|
558
|
+
neither Muse nor its bridge. A host guard opt-out is an explicit operator
|
|
559
|
+
choice for an already-isolated worker and removes the boundary machine-wide;
|
|
560
|
+
generated Muse setup commands never make that choice. Muse uses
|
|
561
|
+
`denyUnmatched`, verified by a session readback, and rejects interactive
|
|
562
|
+
approval requests. Preconfigure narrow rules for required shell/write actions
|
|
563
|
+
and the actual Navarch MCP tool namespace. See [Muse Code setup](../docs/navarch/muse-code.md).
|
|
545
564
|
- **ACP agents:** the protocol client rejects every unattended permission
|
|
546
565
|
escalation. The agent remains responsible for enforcing its declared file
|
|
547
566
|
sandbox; use Navarch Docker mode when the ACP server itself does not provide
|
|
@@ -578,7 +597,16 @@ Because bubblewrap starts from an empty `/` and Codex re-executes its own binary
|
|
|
578
597
|
inside it, the profile also grants read access to the Codex executable's
|
|
579
598
|
install directory (resolved from `NAVARCH_CODEX_BIN` or `PATH`, following
|
|
580
599
|
symlinks) plus mise and nvm tool installs, so a Codex or Node installed under
|
|
581
|
-
`$HOME` still starts.
|
|
600
|
+
`$HOME` still starts. Codex also keeps per-run `codex-linux-sandbox` helper links in
|
|
601
|
+
`$CODEX_HOME/tmp/arg0` and re-executes them by name inside that view; without a
|
|
602
|
+
read grant on that one directory every sandboxed command fails with
|
|
603
|
+
`bwrap: execvp codex-linux-sandbox: No such file or directory` (reproduced on
|
|
604
|
+
Codex 0.158.0, 0.160.0, and 0.162.1). For hoisted npm installs the sibling
|
|
605
|
+
`@openai/codex-<platform>` package holding the native binary is granted too.
|
|
606
|
+
`$HOME` and `CODEX_HOME` themselves are never granted. Helper-directory aliases
|
|
607
|
+
and unresolvable paths are refused; native-package grants require the official
|
|
608
|
+
package layout, and canonical workspace overlap is rejected even through aliases.
|
|
609
|
+
These checks assume the operator trusts the installed CLI/package contents.
|
|
582
610
|
To verify the linked-worktree boundary on a Linux host with Codex installed,
|
|
583
611
|
run `NAVARCH_TEST_REAL_CODEX=1 npm test -- tests/codex-linux-sandbox.test.cts`
|
|
584
612
|
from `runtime/`. The smoke test uses local shell commands and makes no model calls.
|
|
@@ -631,10 +659,15 @@ export NAVARCH_AGENT=acp
|
|
|
631
659
|
# `connect --model` can pin one explicitly.
|
|
632
660
|
export NAVARCH_AGENT=qoder
|
|
633
661
|
|
|
634
|
-
#
|
|
635
|
-
#
|
|
636
|
-
#
|
|
637
|
-
export
|
|
662
|
+
# Muse Code (experimental, runtime 0.1.117+) — see the dedicated setup
|
|
663
|
+
# guide before starting: native authenticated `muse serve`, narrow approval
|
|
664
|
+
# rules, clean native configuration, and deliberate isolation are required.
|
|
665
|
+
export NAVARCH_AGENT=muse
|
|
666
|
+
|
|
667
|
+
# Advanced compatibility mode: advertise every installed, configured adapter.
|
|
668
|
+
# On a guarded host, `opencode`, `antigravity` and `muse` are dropped (see the
|
|
669
|
+
# boundary notes above); the rest are advertised as written.
|
|
670
|
+
export NAVARCH_RUNTIMES=claude-code,codex,antigravity,opencode,acp,qoder,muse
|
|
638
671
|
```
|
|
639
672
|
|
|
640
673
|
For the legacy single-runtime setting, priority is `start --agent` →
|
|
@@ -648,10 +681,10 @@ or `claude-code`-pinned tasks. List only adapters this worker can really run:
|
|
|
648
681
|
in Docker mode that means CLIs installed in `NAVARCH_DOCKER_IMAGE`, otherwise
|
|
649
682
|
pinned tasks for a missing adapter are claimed and then fail.
|
|
650
683
|
|
|
651
|
-
|
|
652
|
-
(`src/adapters/types.cts`)
|
|
653
|
-
`
|
|
654
|
-
|
|
684
|
+
The BYO adapters implement the same `AgentAdapter` interface
|
|
685
|
+
(`src/adapters/types.cts`). Supported isolation modes are adapter-specific;
|
|
686
|
+
Muse uses `muse serve` over stdio on an explicitly isolated host or through
|
|
687
|
+
`docker exec -i` in a prepared custom container — `session.cts` picks one (`src/adapters/index.cts`'s
|
|
655
688
|
`selectAdapter`) at the start of each session and passes `agent_type`
|
|
656
689
|
through to `complete()` unchanged by whatever happened during the run.
|
|
657
690
|
|
|
@@ -666,6 +699,10 @@ reference (the platform default is `opencode/x-preview-f-free`, Ox Alpha Free
|
|
|
666
699
|
(Unlimited) on OpenCode Zen), or `default` to preserve the authenticated
|
|
667
700
|
account's selection. ACP sessions use the agent's advertised default model and
|
|
668
701
|
set the standard `reasoning_effort` configuration option when it is available.
|
|
702
|
+
Muse uses the account-owned `default` model; Navarch adds no Muse project-model
|
|
703
|
+
column or public model catalog. Its SDK integration is experimental and has no
|
|
704
|
+
Navi conversation capability. Read [Muse Code setup and limitations](../docs/navarch/muse-code.md)
|
|
705
|
+
before using private repositories, especially Muse Contributor data settings.
|
|
669
706
|
Machine-wide extra arguments still configure other CLI behavior; dispatched
|
|
670
707
|
model policy wins.
|
|
671
708
|
|
|
@@ -826,6 +863,7 @@ cli.cts
|
|
|
826
863
|
- openCodeAdapter (adapters/opencode.cts) — `opencode run --format json < prompt`
|
|
827
864
|
- acpAdapter (adapters/acp.cts) — ACP v1 JSON-RPC/stdio; defaults to `dsh --profile acp`
|
|
828
865
|
- qoderAdapter (adapters/qoder.cts) — `qoder -p --output-format json < prompt`
|
|
866
|
+
- museAdapter (adapters/muse.cts) — experimental Muse Session Protocol via `muse serve`
|
|
829
867
|
heartbeating the lease every NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS throughout either;
|
|
830
868
|
a failed heartbeat aborts the run (kills the process) and marks the outcome as lease-lost
|
|
831
869
|
5. mapExitCondition (exit-conditions.cts) → redact.cts scrubs the transcript → upload.cts PUTs it
|
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.runLocalPiAdapter = exports.localPiAdapter = exports.runOpenCodeAdapter = exports.openCodeAdapter = exports.runGeminiAdapter = exports.geminiAdapter = exports.runAntigravityAdapter = exports.antigravityAdapter = exports.runCodexAdapter = exports.codexAdapter = exports.runClaudeCodeAdapter = exports.claudeCodeAdapter = void 0;
|
|
3
|
+
exports.runMuseAdapter = exports.museAdapter = 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.runAntigravityAdapter = exports.antigravityAdapter = 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; } });
|
|
@@ -29,6 +29,9 @@ Object.defineProperty(exports, "runAcpAdapter", { enumerable: true, get: functio
|
|
|
29
29
|
const qoder_cjs_1 = require("./qoder.cjs");
|
|
30
30
|
Object.defineProperty(exports, "qoderAdapter", { enumerable: true, get: function () { return qoder_cjs_1.qoderAdapter; } });
|
|
31
31
|
Object.defineProperty(exports, "runQoderAdapter", { enumerable: true, get: function () { return qoder_cjs_1.runQoderAdapter; } });
|
|
32
|
+
const muse_cjs_1 = require("./muse.cjs");
|
|
33
|
+
Object.defineProperty(exports, "museAdapter", { enumerable: true, get: function () { return muse_cjs_1.museAdapter; } });
|
|
34
|
+
Object.defineProperty(exports, "runMuseAdapter", { enumerable: true, get: function () { return muse_cjs_1.runMuseAdapter; } });
|
|
32
35
|
/**
|
|
33
36
|
* Picks the AgentAdapter (adapters/types.cts) session.cts should run a
|
|
34
37
|
* session with, keyed off config.cts's `agentType` (NAVARCH_AGENT). This is
|
|
@@ -50,6 +53,8 @@ function selectAdapter(agentType) {
|
|
|
50
53
|
return acp_cjs_1.acpAdapter;
|
|
51
54
|
case "qoder":
|
|
52
55
|
return qoder_cjs_1.qoderAdapter;
|
|
56
|
+
case "muse":
|
|
57
|
+
return muse_cjs_1.museAdapter;
|
|
53
58
|
case "claude-code":
|
|
54
59
|
return claude_cjs_1.claudeCodeAdapter;
|
|
55
60
|
case "pi-local":
|
|
@@ -0,0 +1,433 @@
|
|
|
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.museAdapter = exports.MUSE_HOST_GUARD_MESSAGE = void 0;
|
|
7
|
+
exports.runMuseAdapter = runMuseAdapter;
|
|
8
|
+
exports.sumMuseSessionCost = sumMuseSessionCost;
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
|
+
const kill_agent_cjs_1 = require("../kill-agent.cjs");
|
|
14
|
+
const version_cjs_1 = require("../version.cjs");
|
|
15
|
+
const CANCEL_GRACE_MS = 2_000;
|
|
16
|
+
const SHUTDOWN_GRACE_MS = 30_000;
|
|
17
|
+
const TERMINATE_GRACE_MS = 2_000;
|
|
18
|
+
exports.MUSE_HOST_GUARD_MESSAGE = "Muse Code cannot yet prove Navarch's host worktree boundary. Use a custom " +
|
|
19
|
+
"NAVARCH_SANDBOX_MODE=docker image with Muse installed, or explicitly set " +
|
|
20
|
+
"NAVARCH_WORKTREE_GUARD=off only on an otherwise isolated machine.";
|
|
21
|
+
function record(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
function count(value) {
|
|
25
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
function deferred() {
|
|
28
|
+
let resolve;
|
|
29
|
+
const promise = new Promise((done) => { resolve = done; });
|
|
30
|
+
return { promise, resolve };
|
|
31
|
+
}
|
|
32
|
+
async function waitWithin(promise, ms) {
|
|
33
|
+
let timer;
|
|
34
|
+
await Promise.race([promise, new Promise((resolve) => { timer = setTimeout(resolve, ms); })]);
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Muse Session Protocol through the pinned official SDK's framed Connection.
|
|
39
|
+
* Own the child here (rather than the facade's private child) so Navarch can
|
|
40
|
+
* register its PID, stream raw output and kill its complete process group.
|
|
41
|
+
* The facade intentionally does not forward session-scoped MCP configuration.
|
|
42
|
+
* No `exec --json` fallback: its event contract is a different surface.
|
|
43
|
+
*/
|
|
44
|
+
async function runMuseAdapter(options) {
|
|
45
|
+
const empty = { exitCode: null, timedOut: false, killedByLeaseLoss: false, stdout: "", stderr: "" };
|
|
46
|
+
if (options.signal?.aborted)
|
|
47
|
+
return { ...empty, killedByLeaseLoss: true };
|
|
48
|
+
if (!options.dockerExec && options.museHostGuard !== false)
|
|
49
|
+
throw new Error(exports.MUSE_HOST_GUARD_MESSAGE);
|
|
50
|
+
if (!options.cwd || !node_path_1.default.isAbsolute(options.cwd))
|
|
51
|
+
throw new Error("Muse requires an absolute session worktree.");
|
|
52
|
+
if (options.extraArgs.some((arg) => /^(--yolo|--disable-sandbox|--disable-approval)(=|$)/.test(arg))) {
|
|
53
|
+
throw new Error("Muse permission and sandbox bypass flags are not supported by Navarch.");
|
|
54
|
+
}
|
|
55
|
+
const sourceServers = await readMcpServers(options.mcpConfigPath, options.env);
|
|
56
|
+
const sdk = await import("@muse-code/sdk");
|
|
57
|
+
const bridge = await prepareBridge(sourceServers, options);
|
|
58
|
+
try {
|
|
59
|
+
return await runMuseProcess(options, bridge.servers, sdk);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
if (bridge.dir)
|
|
63
|
+
await node_fs_1.promises.rm(bridge.dir, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function runMuseProcess(options, mcpServers, sdk) {
|
|
67
|
+
const child = spawnMuse(options);
|
|
68
|
+
child.stdout.setEncoding("utf8");
|
|
69
|
+
child.stderr.setEncoding("utf8");
|
|
70
|
+
child.stdin.on("error", () => undefined); // A dead host is handled by its exit/EOF.
|
|
71
|
+
let stdout = "";
|
|
72
|
+
let stderr = "";
|
|
73
|
+
let timedOut = false;
|
|
74
|
+
let killedByLeaseLoss = false;
|
|
75
|
+
let processCode = null;
|
|
76
|
+
let sessionId;
|
|
77
|
+
let turnId;
|
|
78
|
+
let durable = false;
|
|
79
|
+
let terminal;
|
|
80
|
+
let authError;
|
|
81
|
+
let protocolFailed = false;
|
|
82
|
+
const closed = deferred();
|
|
83
|
+
const stopped = deferred();
|
|
84
|
+
const completed = deferred();
|
|
85
|
+
const items = new Map();
|
|
86
|
+
const usage = new Map();
|
|
87
|
+
const pending = [];
|
|
88
|
+
let cancelling;
|
|
89
|
+
const diagnostic = (message) => { stderr += `${stderr ? "\n" : ""}${message}`; };
|
|
90
|
+
child.on("error", (error) => { diagnostic(`Muse process failed: ${error.message}`); closed.resolve(); });
|
|
91
|
+
child.on("exit", (code) => {
|
|
92
|
+
processCode = code;
|
|
93
|
+
// A descendant may keep stdio open after its root exits.
|
|
94
|
+
if (!options.dockerExec)
|
|
95
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
96
|
+
});
|
|
97
|
+
child.on("close", (code) => { processCode = code; closed.resolve(); });
|
|
98
|
+
child.stderr.on("data", (chunk) => {
|
|
99
|
+
stderr += chunk;
|
|
100
|
+
options.onActivity?.();
|
|
101
|
+
options.onOutput?.("stderr", chunk);
|
|
102
|
+
});
|
|
103
|
+
const connection = new sdk.Connection({
|
|
104
|
+
incoming: (async function* () {
|
|
105
|
+
for await (const chunk of child.stdout) {
|
|
106
|
+
const text = String(chunk);
|
|
107
|
+
stdout += text;
|
|
108
|
+
options.onActivity?.();
|
|
109
|
+
options.onOutput?.("stdout", text);
|
|
110
|
+
yield text;
|
|
111
|
+
}
|
|
112
|
+
})(),
|
|
113
|
+
write: (chunk) => new Promise((resolve, reject) => {
|
|
114
|
+
child.stdin.write(chunk, (error) => error ? reject(error) : resolve());
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
const cancel = () => cancelling ??= (async () => {
|
|
118
|
+
if (sessionId && turnId) {
|
|
119
|
+
// Admission is not cancellation completion. Give the host a bounded
|
|
120
|
+
// chance to settle before killing; never keep a lost lease alive.
|
|
121
|
+
void connection.command("turn/cancel", { sessionId, turnId }, { maxAttempts: 1 }).catch(() => undefined);
|
|
122
|
+
await waitWithin(Promise.race([completed.promise, closed.promise]), CANCEL_GRACE_MS);
|
|
123
|
+
}
|
|
124
|
+
if (options.dockerExec) {
|
|
125
|
+
await options.dockerExec.runner.run("docker", ["kill", options.dockerExec.containerName], { timeoutMs: 10_000 }).catch(() => undefined);
|
|
126
|
+
}
|
|
127
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
128
|
+
})();
|
|
129
|
+
const stop = () => { stopped.resolve(); void cancel(); };
|
|
130
|
+
const onAbort = () => { killedByLeaseLoss = true; stop(); };
|
|
131
|
+
const timer = options.timeoutMs > 0 ? setTimeout(() => { timedOut = true; stop(); }, options.timeoutMs) : undefined;
|
|
132
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
133
|
+
if (options.signal?.aborted)
|
|
134
|
+
onAbort();
|
|
135
|
+
const consume = (notification) => {
|
|
136
|
+
const params = record(notification.params);
|
|
137
|
+
if (!params || params.sessionId !== sessionId)
|
|
138
|
+
return;
|
|
139
|
+
if (notification.method === "view/gap") {
|
|
140
|
+
protocolFailed = true;
|
|
141
|
+
diagnostic("Muse event stream has a gap; refusing an incomplete result.");
|
|
142
|
+
stop();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (notification.method === "item/completed") {
|
|
146
|
+
const item = record(params.item);
|
|
147
|
+
if (!item || item.turnId !== turnId || item.kind !== "agentMessage" || typeof item.itemId !== "string")
|
|
148
|
+
return;
|
|
149
|
+
const previous = items.get(item.itemId);
|
|
150
|
+
if (!previous || Number(item.revision) >= Number(previous.revision))
|
|
151
|
+
items.set(item.itemId, item);
|
|
152
|
+
}
|
|
153
|
+
else if (params.turnId === turnId && notification.method === "session/tokenUsage") {
|
|
154
|
+
if (typeof params.viewCursor === "string")
|
|
155
|
+
usage.set(params.viewCursor, params);
|
|
156
|
+
}
|
|
157
|
+
else if (params.turnId === turnId && notification.method === "turn/completed") {
|
|
158
|
+
terminal = params;
|
|
159
|
+
const error = record(params.error);
|
|
160
|
+
if (params.terminal === "failed" && error?.kind === "authRequired") {
|
|
161
|
+
authError = "Muse authentication is required. Open muse and run /login on this machine.";
|
|
162
|
+
}
|
|
163
|
+
completed.resolve();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
connection.onNotification((notification) => {
|
|
167
|
+
if (!turnId) {
|
|
168
|
+
// Events can precede the acknowledgement. Do not assume commandId is
|
|
169
|
+
// turnId; use the authoritative ack, then drain in wire order.
|
|
170
|
+
if (pending.length >= 10_000) {
|
|
171
|
+
protocolFailed = true;
|
|
172
|
+
diagnostic("Muse sent too many events before acknowledging the turn.");
|
|
173
|
+
stop();
|
|
174
|
+
}
|
|
175
|
+
else
|
|
176
|
+
pending.push(notification);
|
|
177
|
+
}
|
|
178
|
+
else
|
|
179
|
+
consume(notification);
|
|
180
|
+
});
|
|
181
|
+
connection.onProtocolError(() => {
|
|
182
|
+
protocolFailed = true;
|
|
183
|
+
diagnostic("Muse sent an invalid MSP frame; refusing to continue.");
|
|
184
|
+
stop();
|
|
185
|
+
});
|
|
186
|
+
// Unattended agents cannot invent approval or answer interactive questions.
|
|
187
|
+
// denyUnmatched is also selected below; unexpected requests abort the turn.
|
|
188
|
+
connection.onServerRequest(async () => {
|
|
189
|
+
protocolFailed = true;
|
|
190
|
+
diagnostic("Muse requested interactive approval or input; operator action is required.");
|
|
191
|
+
stop();
|
|
192
|
+
throw new sdk.MspError({ code: -32601, message: "Navarch cannot approve interactive requests", data: { kind: "methodNotFound", retryable: false } });
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
options.onProcessStarted?.(child.pid);
|
|
196
|
+
const run = async () => {
|
|
197
|
+
const initialized = await connection.request("initialize", {
|
|
198
|
+
clientInfo: { name: "navarch_runtime", version: version_cjs_1.RUNTIME_VERSION },
|
|
199
|
+
capabilities: { requestedCapabilities: Object.keys(mcpServers).length ? ["sessionMcp"] : [], userInputDialogs: false },
|
|
200
|
+
});
|
|
201
|
+
const schema = record(initialized.schema);
|
|
202
|
+
if (typeof schema?.fingerprint !== "string")
|
|
203
|
+
throw new Error("Muse initialization did not provide a schema fingerprint.");
|
|
204
|
+
if (sdk.checkServedFingerprint(schema.fingerprint))
|
|
205
|
+
diagnostic("Muse schema fingerprint differs from the pinned SDK; unknown fields are tolerated.");
|
|
206
|
+
if (Object.keys(mcpServers).length && (!Array.isArray(initialized.grantedCapabilities) || !initialized.grantedCapabilities.includes("sessionMcp"))) {
|
|
207
|
+
throw new Error("Muse did not grant sessionMcp; the task cannot run without its lease-scoped tools.");
|
|
208
|
+
}
|
|
209
|
+
durable = initialized.sessionDurability === undefined || initialized.sessionDurability === "durable";
|
|
210
|
+
connection.notify("initialized");
|
|
211
|
+
await connection.flush();
|
|
212
|
+
if (timedOut || killedByLeaseLoss || protocolFailed)
|
|
213
|
+
return;
|
|
214
|
+
const config = Object.keys(mcpServers).length ? { mcpServers } : undefined;
|
|
215
|
+
const opened = options.resumeSessionId
|
|
216
|
+
? await connection.command("session/resume", { sessionId: options.resumeSessionId, excludeItems: true, ...(config ? { config } : {}) }, { maxAttempts: 1 })
|
|
217
|
+
: await connection.command("session/start", {
|
|
218
|
+
workspaceRoot: options.cwd,
|
|
219
|
+
approvalMode: "denyUnmatched",
|
|
220
|
+
...(options.model && options.model !== "default" ? { modelId: options.model } : {}),
|
|
221
|
+
...(config ? { config } : {}),
|
|
222
|
+
}, { maxAttempts: 1 });
|
|
223
|
+
const session = record(opened.session);
|
|
224
|
+
if (typeof session?.sessionId !== "string" || typeof session.workspaceRoot !== "string"
|
|
225
|
+
|| !node_path_1.default.isAbsolute(session.workspaceRoot) || await node_fs_1.promises.realpath(session.workspaceRoot) !== await node_fs_1.promises.realpath(options.cwd)
|
|
226
|
+
|| session.activeTurnId !== null) {
|
|
227
|
+
throw new Error("Muse returned an unexpected session identity, worktree, or active turn.");
|
|
228
|
+
}
|
|
229
|
+
if (options.resumeSessionId && session.sessionId !== options.resumeSessionId)
|
|
230
|
+
throw new Error("Muse resumed a different session.");
|
|
231
|
+
sessionId = session.sessionId;
|
|
232
|
+
if (options.resumeSessionId) {
|
|
233
|
+
if (!durable)
|
|
234
|
+
throw new Error("Muse cannot resume this session on an ephemeral host.");
|
|
235
|
+
if (record(opened.history)?.mode !== "none")
|
|
236
|
+
throw new Error("Muse resume returned unexpected history; refusing replay.");
|
|
237
|
+
await connection.command("session/setApprovalMode", { sessionId, mode: "denyUnmatched" }, { maxAttempts: 1 });
|
|
238
|
+
if (options.model && options.model !== "default" && session.modelId !== options.model) {
|
|
239
|
+
throw new Error("Muse resumed with a different model than the dispatched policy.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// An older host may ignore additive fields. Prove the selected approval
|
|
243
|
+
// posture before sending any model-visible prompt.
|
|
244
|
+
let approvalMode = options.resumeSessionId ? undefined : record(session.approvalMode)?.mode;
|
|
245
|
+
if (approvalMode !== "denyUnmatched" && durable) {
|
|
246
|
+
const state = await connection.request("session/read", { sessionId, excludeItems: true });
|
|
247
|
+
approvalMode = record(record(state.session)?.approvalMode)?.mode;
|
|
248
|
+
}
|
|
249
|
+
if (approvalMode !== "denyUnmatched") {
|
|
250
|
+
throw new Error("Muse did not confirm denyUnmatched approval mode; refusing to start a turn.");
|
|
251
|
+
}
|
|
252
|
+
if (timedOut || killedByLeaseLoss || protocolFailed)
|
|
253
|
+
return;
|
|
254
|
+
const ack = await connection.command("turn/start", {
|
|
255
|
+
sessionId,
|
|
256
|
+
input: [{ type: "text", text: options.prompt }],
|
|
257
|
+
...(options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}),
|
|
258
|
+
}, { maxAttempts: 1 });
|
|
259
|
+
if (typeof ack.turnId !== "string" || ack.disposition !== "started")
|
|
260
|
+
throw new Error("Muse did not start the requested turn.");
|
|
261
|
+
turnId = ack.turnId;
|
|
262
|
+
for (const notification of pending.splice(0))
|
|
263
|
+
consume(notification);
|
|
264
|
+
await Promise.race([completed.promise, connection.closed]);
|
|
265
|
+
if (!terminal && !timedOut && !killedByLeaseLoss)
|
|
266
|
+
throw new Error("Muse closed before the turn completed.");
|
|
267
|
+
};
|
|
268
|
+
await Promise.race([run(), stopped.promise]);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
if (error instanceof sdk.MspError && error.kind === "authRequired") {
|
|
272
|
+
authError = "Muse authentication is required. Open muse and run /login on this machine.";
|
|
273
|
+
}
|
|
274
|
+
if (!timedOut && !killedByLeaseLoss)
|
|
275
|
+
diagnostic(error instanceof Error ? error.message : "Muse protocol request failed.");
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
await cancelling;
|
|
279
|
+
// EOF releases Muse's writer lease. A bounded shutdown always reaps the
|
|
280
|
+
// child, including a host that ignores EOF or died during a request.
|
|
281
|
+
child.stdin.end();
|
|
282
|
+
await waitWithin(Promise.race([closed.promise, stopped.promise]), SHUTDOWN_GRACE_MS);
|
|
283
|
+
await cancelling;
|
|
284
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
285
|
+
(0, kill_agent_cjs_1.killAgent)(child, "SIGTERM");
|
|
286
|
+
await waitWithin(closed.promise, TERMINATE_GRACE_MS);
|
|
287
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
288
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
289
|
+
if (options.dockerExec)
|
|
290
|
+
await options.dockerExec.runner.run("docker", ["kill", options.dockerExec.containerName], { timeoutMs: 10_000 }).catch(() => undefined);
|
|
291
|
+
await waitWithin(closed.promise, TERMINATE_GRACE_MS);
|
|
292
|
+
}
|
|
293
|
+
// The root may have exited while detached shell/MCP descendants remain.
|
|
294
|
+
// Reap our owned host process group even after a clean root exit.
|
|
295
|
+
if (!options.dockerExec)
|
|
296
|
+
(0, kill_agent_cjs_1.killAgent)(child);
|
|
297
|
+
await waitWithin(closed.promise, TERMINATE_GRACE_MS);
|
|
298
|
+
await connection.close().catch(() => undefined);
|
|
299
|
+
clearTimeout(timer);
|
|
300
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
301
|
+
}
|
|
302
|
+
if (processCode === 5)
|
|
303
|
+
diagnostic("This Muse build has the experimental SDK/serve surface disabled. No CLI fallback was attempted.");
|
|
304
|
+
const successful = !protocolFailed && terminal?.terminal === "completed" && processCode === 0;
|
|
305
|
+
if (terminal && terminal.terminal !== "completed" && !timedOut && !killedByLeaseLoss && !authError) {
|
|
306
|
+
diagnostic(`Muse turn did not complete successfully (${String(terminal.terminal)}).`);
|
|
307
|
+
}
|
|
308
|
+
const reportText = [...items.values()].flatMap((item) => typeof item.text === "string" ? [item.text] : []).join("\n").trim();
|
|
309
|
+
return {
|
|
310
|
+
exitCode: timedOut || killedByLeaseLoss ? null : successful ? 0 : processCode === null ? null : 1,
|
|
311
|
+
timedOut, killedByLeaseLoss, stdout, stderr,
|
|
312
|
+
...(reportText ? { reportText } : {}),
|
|
313
|
+
...(authError ? { authError } : {}),
|
|
314
|
+
...(durable && sessionId && terminal ? { agentSessionId: sessionId } : {}),
|
|
315
|
+
...normalizedUsage([...usage.values()], !options.resumeSessionId),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function normalizedUsage(events, fresh) {
|
|
319
|
+
if (!events.length)
|
|
320
|
+
return {};
|
|
321
|
+
let tokensIn = 0;
|
|
322
|
+
let tokensOut = 0;
|
|
323
|
+
let cacheHitTokensIn = 0;
|
|
324
|
+
let completeCache = true;
|
|
325
|
+
for (const event of events) {
|
|
326
|
+
const usage = record(event.usage);
|
|
327
|
+
const input = count(event.promptTokens);
|
|
328
|
+
const output = count(usage?.outputTokens);
|
|
329
|
+
if (input === undefined || output === undefined)
|
|
330
|
+
return {}; // No fabricated partial total.
|
|
331
|
+
tokensIn += input;
|
|
332
|
+
tokensOut += output;
|
|
333
|
+
const cache = count(usage?.cacheReadTokens);
|
|
334
|
+
if (cache === undefined || cache > input)
|
|
335
|
+
completeCache = false;
|
|
336
|
+
else
|
|
337
|
+
cacheHitTokensIn += cache;
|
|
338
|
+
}
|
|
339
|
+
if (!Number.isSafeInteger(tokensIn) || !Number.isSafeInteger(tokensOut))
|
|
340
|
+
return {};
|
|
341
|
+
const cost = record(record(events.at(-1)?.cumulative)?.cost);
|
|
342
|
+
return {
|
|
343
|
+
tokensIn, tokensOut,
|
|
344
|
+
...(completeCache ? { cacheHitTokensIn } : {}),
|
|
345
|
+
...(cost?.partial === false && typeof cost.usd === "number" && Number.isFinite(cost.usd) && cost.usd >= 0
|
|
346
|
+
? { sessionCostUsd: cost.usd, ...(fresh ? { costUsd: cost.usd } : {}) } : {}),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/** Replace each provider session's cumulative estimate, never sum it per turn. */
|
|
350
|
+
function sumMuseSessionCost(attempts) {
|
|
351
|
+
const sessions = new Map();
|
|
352
|
+
attempts.forEach((attempt, index) => sessions.set(attempt.agentSessionId ?? `attempt:${index}`, attempt.sessionCostUsd));
|
|
353
|
+
if (!sessions.size || [...sessions.values()].some((cost) => cost === undefined))
|
|
354
|
+
return undefined;
|
|
355
|
+
return [...sessions.values()].reduce((sum, cost) => sum + cost, 0);
|
|
356
|
+
}
|
|
357
|
+
async function prepareBridge(source, options) {
|
|
358
|
+
if (!Object.keys(source).length)
|
|
359
|
+
return { servers: {} };
|
|
360
|
+
if (!options.mcpMetadataDir || !node_path_1.default.isAbsolute(options.mcpMetadataDir))
|
|
361
|
+
throw new Error("Muse MCP needs a task-owned metadata directory.");
|
|
362
|
+
const dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(options.mcpMetadataDir, "muse-mcp-"));
|
|
363
|
+
try {
|
|
364
|
+
const configPath = node_path_1.default.join(dir, "servers.json");
|
|
365
|
+
const content = JSON.stringify(source);
|
|
366
|
+
await node_fs_1.promises.writeFile(configPath, content, { mode: 0o600 });
|
|
367
|
+
const digest = (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
|
|
368
|
+
const servers = Object.fromEntries(Object.keys(source).map((name) => [name, {
|
|
369
|
+
transport: "stdio", framing: "lineDelimitedJson", mode: "required",
|
|
370
|
+
command: options.dockerExec ? "navarch-muse-mcp-bridge" : process.execPath,
|
|
371
|
+
args: [...(options.dockerExec ? [] : [node_path_1.default.resolve(__dirname, "../../bin/muse-mcp-bridge.cjs")]), configPath, name, digest],
|
|
372
|
+
}]));
|
|
373
|
+
return { servers, dir };
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function readMcpServers(configPath, env) {
|
|
381
|
+
if (!configPath)
|
|
382
|
+
return {};
|
|
383
|
+
const parsed = record(JSON.parse(await node_fs_1.promises.readFile(configPath, "utf8")));
|
|
384
|
+
const source = record(parsed?.mcpServers);
|
|
385
|
+
if (!source)
|
|
386
|
+
throw new Error("Muse MCP configuration must contain mcpServers.");
|
|
387
|
+
const result = Object.create(null);
|
|
388
|
+
const strings = (value) => {
|
|
389
|
+
const entries = value === undefined ? {} : record(value);
|
|
390
|
+
if (!entries)
|
|
391
|
+
throw new Error("Muse MCP environment/headers must be string maps.");
|
|
392
|
+
return Object.fromEntries(Object.entries(entries).map(([key, raw]) => {
|
|
393
|
+
if (typeof raw !== "string")
|
|
394
|
+
throw new Error("Muse MCP environment/headers must be string maps.");
|
|
395
|
+
const name = raw.match(/^\{env:([A-Z_][A-Z0-9_]*)\}$/)?.[1];
|
|
396
|
+
const value = name ? env[name] ?? process.env[name] : raw;
|
|
397
|
+
if (value === undefined)
|
|
398
|
+
throw new Error(`Muse MCP configuration references missing environment variable ${name}.`);
|
|
399
|
+
return [key, value];
|
|
400
|
+
}));
|
|
401
|
+
};
|
|
402
|
+
for (const [name, value] of Object.entries(source)) {
|
|
403
|
+
const server = record(value);
|
|
404
|
+
if (!server)
|
|
405
|
+
throw new Error("Muse MCP server configuration is invalid.");
|
|
406
|
+
const url = server.httpUrl ?? server.url;
|
|
407
|
+
if (typeof url === "string" && /^https?:\/\//.test(url) && (!server.type || ["http", "streamable-http"].includes(String(server.type)))) {
|
|
408
|
+
result[name] = { transport: "streamableHttp", url, headers: strings(server.headers), mode: "required" };
|
|
409
|
+
}
|
|
410
|
+
else if (typeof server.command === "string" && (!server.type || server.type === "stdio") && (!server.args || Array.isArray(server.args) && server.args.every((arg) => typeof arg === "string"))) {
|
|
411
|
+
result[name] = { transport: "stdio", command: server.command, args: server.args, env: strings(server.env), mode: "required" };
|
|
412
|
+
}
|
|
413
|
+
else
|
|
414
|
+
throw new Error("Muse MCP server transport is unsupported; no servers were silently dropped.");
|
|
415
|
+
}
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
function spawnMuse(options) {
|
|
419
|
+
const env = { ...process.env, ...options.env };
|
|
420
|
+
delete env.MUSE_PERMISSION_PROFILE_FAIL_OPEN;
|
|
421
|
+
const args = ["serve", ...options.extraArgs];
|
|
422
|
+
if (!options.dockerExec)
|
|
423
|
+
return (0, node_child_process_1.spawn)(options.bin, args, { cwd: options.cwd, env, detached: process.platform !== "win32", stdio: "pipe" });
|
|
424
|
+
return (0, node_child_process_1.spawn)("docker", [
|
|
425
|
+
"exec", "-i", ...Object.keys(options.env).filter((name) => name !== "MUSE_PERMISSION_PROFILE_FAIL_OPEN").flatMap((name) => ["--env", name]),
|
|
426
|
+
options.dockerExec.containerName, "sh", "-c",
|
|
427
|
+
'[ ! -f /tmp/session.env ] || . /tmp/session.env; unset MUSE_PERMISSION_PROFILE_FAIL_OPEN; cd "$1" || exit 1; shift; exec "$@"',
|
|
428
|
+
"sh", options.cwd, options.bin, ...args,
|
|
429
|
+
], { env, detached: process.platform !== "win32", stdio: "pipe" });
|
|
430
|
+
}
|
|
431
|
+
// Read-only Navi conversations are deliberately absent: an approval mode is
|
|
432
|
+
// not a filesystem read boundary, and the SDK cannot yet prove that contract.
|
|
433
|
+
exports.museAdapter = { agentType: "muse", run: runMuseAdapter };
|
package/dist/agent-auth.cjs
CHANGED
|
@@ -43,7 +43,8 @@ function matchingLine(text) {
|
|
|
43
43
|
function detectAgentAuthFailure(result) {
|
|
44
44
|
if (result.killedByLeaseLoss || result.timedOut || result.capacityError)
|
|
45
45
|
return null;
|
|
46
|
-
|
|
46
|
+
// Muse exposes authRequired as a typed terminal error, never assistant prose.
|
|
47
|
+
if (result.authError && result.exitCode !== 0)
|
|
47
48
|
return { detail: result.authError };
|
|
48
49
|
const claude = (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stdout) ?? (0, exit_conditions_cjs_1.parseClaudeJsonResult)(result.stderr);
|
|
49
50
|
if (claude) {
|