@sagentlab/navarch-runtime 0.1.116 → 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 +43 -17
- 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/config.cjs +8 -1
- package/dist/conversation/capability.cjs +1 -1
- package/dist/exit-conditions.cjs +1 -1
- package/dist/muse-mcp-bridge.cjs +167 -0
- package/dist/session.cjs +30 -8
- 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
|
@@ -40,7 +40,7 @@ function conversationCapability(config) {
|
|
|
40
40
|
}
|
|
41
41
|
/** Per-adapter support table for docs and diagnostics (host sandbox, guard on, feature enabled). */
|
|
42
42
|
function conversationSupportMatrix() {
|
|
43
|
-
const agents = ["claude-code", "codex", "gemini", "antigravity", "opencode", "acp", "qoder", "pi-local", "pi"];
|
|
43
|
+
const agents = ["claude-code", "codex", "gemini", "antigravity", "opencode", "acp", "qoder", "muse", "pi-local", "pi"];
|
|
44
44
|
return agents.map((agentType) => ({
|
|
45
45
|
agentType,
|
|
46
46
|
capability: conversationCapability({
|
package/dist/exit-conditions.cjs
CHANGED
|
@@ -22,7 +22,7 @@ exports.mapExitCondition = mapExitCondition;
|
|
|
22
22
|
exports.AGENT_AUTH_FAILURE_PREFIX = "Coding agent authentication failed";
|
|
23
23
|
function agentAuthFailureReport(detail) {
|
|
24
24
|
return `${exports.AGENT_AUTH_FAILURE_PREFIX}: ${detail} ` +
|
|
25
|
-
"Log the coding agent in again on this machine (`claude /login`, `codex login`,
|
|
25
|
+
"Log the coding agent in again on this machine (`claude /login`, `codex login`, an interactive `agy` session, or open `muse` and run `/login`), then resume the agent.";
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Best-effort parse of `claude -p --output-format json` (or `stream-json`,
|
|
@@ -0,0 +1,167 @@
|
|
|
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.bridgeMuseMcp = bridgeMuseMcp;
|
|
7
|
+
exports.main = main;
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
function object(value) {
|
|
13
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A tools-only MCP transport bridge. Muse receives only this executable and
|
|
17
|
+
* an expiring task-owned file path. Credentials never enter MSP commands or
|
|
18
|
+
* Muse's durable journal. No new listener, OAuth flow or persistent grant.
|
|
19
|
+
*/
|
|
20
|
+
async function bridgeMuseMcp(downstream, upstream) {
|
|
21
|
+
let initializeId;
|
|
22
|
+
let ending = false;
|
|
23
|
+
let failed = false;
|
|
24
|
+
let finish;
|
|
25
|
+
const done = new Promise((resolve) => { finish = resolve; });
|
|
26
|
+
const close = async () => {
|
|
27
|
+
if (ending)
|
|
28
|
+
return;
|
|
29
|
+
ending = true;
|
|
30
|
+
await Promise.allSettled([upstream.close(), downstream.close()]);
|
|
31
|
+
finish();
|
|
32
|
+
};
|
|
33
|
+
const fail = () => {
|
|
34
|
+
if (ending)
|
|
35
|
+
return;
|
|
36
|
+
failed = true;
|
|
37
|
+
// Upstream errors can contain endpoints, headers or provider response
|
|
38
|
+
// bodies. Never forward/log them to Muse or its retained transcript.
|
|
39
|
+
process.stderr.write("Navarch MCP bridge connection failed.\n");
|
|
40
|
+
void close();
|
|
41
|
+
};
|
|
42
|
+
const reject = (message, target) => {
|
|
43
|
+
if ("id" in message)
|
|
44
|
+
void target.send({ jsonrpc: "2.0", id: message.id, error: { code: -32601, message: "This Navarch bridge supports tools only" } }).catch(fail);
|
|
45
|
+
};
|
|
46
|
+
downstream.onmessage = (message) => {
|
|
47
|
+
if (!("method" in message)) {
|
|
48
|
+
reject(message, downstream);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!["initialize", "notifications/initialized", "notifications/cancelled", "ping", "tools/list", "tools/call"].includes(message.method)) {
|
|
52
|
+
reject(message, downstream);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (message.method === "initialize" && "id" in message) {
|
|
56
|
+
initializeId = message.id;
|
|
57
|
+
// Do not advertise sampling, elicitation, roots, or any other route for
|
|
58
|
+
// an upstream server to ask the unattended model to approve something.
|
|
59
|
+
message = { ...message, params: { ...message.params, capabilities: {} } };
|
|
60
|
+
}
|
|
61
|
+
void upstream.send(message).catch(fail);
|
|
62
|
+
};
|
|
63
|
+
upstream.onmessage = (message) => {
|
|
64
|
+
let outgoing = message;
|
|
65
|
+
if ("method" in message && "id" in message) {
|
|
66
|
+
reject(message, upstream);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if ("method" in message && !["notifications/tools/list_changed", "notifications/progress", "notifications/cancelled"].includes(message.method))
|
|
70
|
+
return;
|
|
71
|
+
if ("error" in message) {
|
|
72
|
+
outgoing = { jsonrpc: "2.0", id: message.id, error: { code: message.error.code, message: "Navarch MCP upstream request failed" } };
|
|
73
|
+
}
|
|
74
|
+
if ("result" in message && message.id === initializeId) {
|
|
75
|
+
const result = object(message.result);
|
|
76
|
+
if (!result || typeof result.protocolVersion !== "string") {
|
|
77
|
+
fail();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const http = upstream;
|
|
81
|
+
http.setProtocolVersion?.(result.protocolVersion);
|
|
82
|
+
outgoing = { ...message, result: { ...result, capabilities: { ...(object(result.capabilities)?.tools ? { tools: object(result.capabilities).tools } : {}) } } };
|
|
83
|
+
}
|
|
84
|
+
void downstream.send(outgoing).catch(fail);
|
|
85
|
+
};
|
|
86
|
+
upstream.onerror = fail;
|
|
87
|
+
downstream.onerror = fail;
|
|
88
|
+
upstream.onclose = () => { void close(); };
|
|
89
|
+
downstream.onclose = () => { void close(); };
|
|
90
|
+
const onEnd = () => { void close(); };
|
|
91
|
+
process.stdin.once("end", onEnd);
|
|
92
|
+
process.once("SIGTERM", onEnd);
|
|
93
|
+
process.once("SIGINT", onEnd);
|
|
94
|
+
try {
|
|
95
|
+
await upstream.start();
|
|
96
|
+
await downstream.start();
|
|
97
|
+
await done;
|
|
98
|
+
if (failed)
|
|
99
|
+
throw new Error("Navarch MCP bridge connection failed");
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
await close();
|
|
103
|
+
process.stdin.removeListener("end", onEnd);
|
|
104
|
+
process.removeListener("SIGTERM", onEnd);
|
|
105
|
+
process.removeListener("SIGINT", onEnd);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function main(args = process.argv.slice(2)) {
|
|
109
|
+
try {
|
|
110
|
+
const [configPath, name, digest] = args;
|
|
111
|
+
if (!configPath || !node_path_1.default.isAbsolute(configPath) || !name || !digest || !/^[a-f0-9]{64}$/.test(digest) || args.length !== 3)
|
|
112
|
+
throw new Error("Invalid bridge arguments");
|
|
113
|
+
const directory = await (0, promises_1.stat)(node_path_1.default.dirname(configPath));
|
|
114
|
+
if (!directory.isDirectory() || (directory.mode & 0o077) !== 0)
|
|
115
|
+
throw new Error("Non-private bridge directory");
|
|
116
|
+
const file = await (0, promises_1.open)(configPath, node_fs_1.constants.O_RDONLY | (node_fs_1.constants.O_NOFOLLOW ?? 0));
|
|
117
|
+
let content;
|
|
118
|
+
try {
|
|
119
|
+
const metadata = await file.stat();
|
|
120
|
+
if (!metadata.isFile() || (metadata.mode & 0o077) !== 0 || metadata.size > 1024 * 1024)
|
|
121
|
+
throw new Error("Non-private bridge file");
|
|
122
|
+
content = await file.readFile("utf8");
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await file.close();
|
|
126
|
+
}
|
|
127
|
+
// Bind the transport/endpoint/headers to the exact configuration chosen
|
|
128
|
+
// by Navarch. A later file replacement cannot redirect a credential.
|
|
129
|
+
if ((0, node_crypto_1.createHash)("sha256").update(content).digest("hex") !== digest)
|
|
130
|
+
throw new Error("Bridge configuration changed");
|
|
131
|
+
const source = object(JSON.parse(content));
|
|
132
|
+
const server = object(source?.[name]);
|
|
133
|
+
if (!server)
|
|
134
|
+
throw new Error("Missing MCP server");
|
|
135
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
136
|
+
let upstream;
|
|
137
|
+
if (server.transport === "streamableHttp" && typeof server.url === "string") {
|
|
138
|
+
const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
139
|
+
const endpoint = new URL(server.url);
|
|
140
|
+
if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password)
|
|
141
|
+
throw new Error("Invalid MCP URL");
|
|
142
|
+
upstream = new StreamableHTTPClientTransport(endpoint, {
|
|
143
|
+
requestInit: { headers: server.headers },
|
|
144
|
+
// The SDK's SSE GET path does not inherit requestInit.redirect.
|
|
145
|
+
// Enforce the exact authorized endpoint for every HTTP method.
|
|
146
|
+
fetch: (input, init) => {
|
|
147
|
+
const url = new URL(input);
|
|
148
|
+
if (url.href !== endpoint.href)
|
|
149
|
+
return Promise.reject(new Error("MCP endpoint changed"));
|
|
150
|
+
return fetch(input, { ...init, redirect: "error" });
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
else if (server.transport === "stdio" && typeof server.command === "string") {
|
|
155
|
+
const { StdioClientTransport, getDefaultEnvironment } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
156
|
+
upstream = new StdioClientTransport({ command: server.command, args: server.args,
|
|
157
|
+
env: { ...getDefaultEnvironment(), ...server.env }, stderr: "ignore" });
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
throw new Error("Unsupported MCP transport");
|
|
161
|
+
await bridgeMuseMcp(new StdioServerTransport(), upstream);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
process.stderr.write("Navarch MCP bridge could not start; check its task-owned configuration and installed runtime.\n");
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
package/dist/session.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runSession = runSession;
|
|
7
7
|
exports.toEnvMap = toEnvMap;
|
|
8
|
+
const muse_cjs_1 = require("./adapters/muse.cjs");
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const node_perf_hooks_1 = require("node:perf_hooks");
|
|
10
11
|
const node_fs_1 = require("node:fs");
|
|
@@ -50,7 +51,7 @@ function resolveSessionExecution(config, claimed) {
|
|
|
50
51
|
model: runtime === "codex" ? "gpt-6-astra"
|
|
51
52
|
: runtime === "pi-local" ? "qwen3.8-flash-next-iq2_xs"
|
|
52
53
|
: runtime === "gemini" ? "auto"
|
|
53
|
-
: runtime === "antigravity" || runtime === "opencode" || runtime === "acp" || runtime === "qoder" ? "default"
|
|
54
|
+
: runtime === "antigravity" || runtime === "opencode" || runtime === "acp" || runtime === "qoder" || runtime === "muse" ? "default"
|
|
54
55
|
: "claude-opus-5-5",
|
|
55
56
|
reasoning_effort: "medium",
|
|
56
57
|
};
|
|
@@ -447,7 +448,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
447
448
|
if (!config.mcpConfigPath && config.sandboxMode === "host"
|
|
448
449
|
&& ["build", "verify", "qa"].includes(task.task_type)
|
|
449
450
|
&& task.required_capabilities?.includes("browser-use")
|
|
450
|
-
&& ["codex", "claude-code", "antigravity", "opencode", "acp"].includes(runtime)) {
|
|
451
|
+
&& ["codex", "claude-code", "antigravity", "opencode", "acp", "muse"].includes(runtime)) {
|
|
451
452
|
browserEvidence = await (0, browser_evidence_cjs_1.startBrowserEvidence)({
|
|
452
453
|
apiBase: api.getBaseUrl(), machineToken: api.getToken() ?? "", leaseId,
|
|
453
454
|
isActive: () => !leaseLost && !sessionClosing && !deps.shutdownSignal?.aborted,
|
|
@@ -533,6 +534,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
533
534
|
let deliveryCorrectionRetries = 0;
|
|
534
535
|
let deliveredPullRequest = false;
|
|
535
536
|
let antigravityResume;
|
|
537
|
+
let museSessionId;
|
|
536
538
|
while (true) {
|
|
537
539
|
// Guidance can arrive while the worktree/sandbox is being prepared.
|
|
538
540
|
// It is already included in deliveredGuidance, so clear the pending
|
|
@@ -563,6 +565,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
563
565
|
: await (0, adapter_watchdog_cjs_1.runWithActivityWatchdog)(adapter, {
|
|
564
566
|
prompt: runPrompt,
|
|
565
567
|
mcpConfigPath,
|
|
568
|
+
mcpMetadataDir: workDir,
|
|
566
569
|
bin,
|
|
567
570
|
extraArgs,
|
|
568
571
|
model: execution.model,
|
|
@@ -573,6 +576,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
573
576
|
codexGuardArgs,
|
|
574
577
|
antigravityHostGuard: runtime === "antigravity" && config.sandboxMode === "host" && config.worktreeGuard,
|
|
575
578
|
antigravityResume,
|
|
579
|
+
museHostGuard: config.sandboxMode === "host" && config.worktreeGuard,
|
|
580
|
+
resumeSessionId: runtime === "muse" ? museSessionId : undefined,
|
|
576
581
|
opencodeHostGuard: runtime === "opencode" && config.sandboxMode === "host" && config.worktreeGuard,
|
|
577
582
|
// ACP requires an absolute session cwd. Docker mounts the worktree at
|
|
578
583
|
// this same absolute path, while the other Docker adapters ignore cwd.
|
|
@@ -596,6 +601,8 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
596
601
|
executionReport = { ...executionReport, model: turnResult.model };
|
|
597
602
|
}
|
|
598
603
|
antigravityResume = turnResult.antigravityResume;
|
|
604
|
+
if (runtime === "muse")
|
|
605
|
+
museSessionId = turnResult.agentSessionId;
|
|
599
606
|
attempts.push(turnResult);
|
|
600
607
|
liveStdout = "";
|
|
601
608
|
liveTranscript.markDirty();
|
|
@@ -626,14 +633,25 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
626
633
|
log.warn(`session ${leaseId} stopped without completion because its lease is no longer active.`);
|
|
627
634
|
return sessionOutcome;
|
|
628
635
|
}
|
|
629
|
-
if (!capacityLimit && !authFailure && !leaseLost && !turnResult.timedOut && pendingGuidance.length > 0)
|
|
636
|
+
if (!capacityLimit && !authFailure && !leaseLost && !turnResult.timedOut && pendingGuidance.length > 0) {
|
|
637
|
+
// Muse cancellation kills the whole task-owned container. Guidance
|
|
638
|
+
// still holds the lease, so restart that SAME container and restore
|
|
639
|
+
// its tmpfs environment before the next turn; never launch into a
|
|
640
|
+
// stopped sandbox or substitute an unisolated host process.
|
|
641
|
+
if (runtime === "muse" && sandbox && turnResult.killedByLeaseLoss) {
|
|
642
|
+
const restarted = await sandbox_cjs_1.nodeCommandRunner.run("docker", ["start", sandbox.name], { timeoutMs: 10_000 });
|
|
643
|
+
if (restarted.code !== 0)
|
|
644
|
+
throw new Error("Could not restart the task-owned Muse sandbox after guidance.");
|
|
645
|
+
await sandbox.injectEnv(sessionEnv);
|
|
646
|
+
}
|
|
630
647
|
continue;
|
|
648
|
+
}
|
|
631
649
|
const result = {
|
|
632
650
|
...turnResult,
|
|
633
|
-
tokensIn: sumReportedUsage(attempts, "tokensIn"),
|
|
634
|
-
tokensOut: sumReportedUsage(attempts, "tokensOut"),
|
|
635
|
-
cacheHitTokensIn: sumReportedUsage(attempts, "cacheHitTokensIn"),
|
|
636
|
-
costUsd: sumReportedUsage(attempts, "costUsd"),
|
|
651
|
+
tokensIn: sumReportedUsage(attempts, "tokensIn", runtime === "muse"),
|
|
652
|
+
tokensOut: sumReportedUsage(attempts, "tokensOut", runtime === "muse"),
|
|
653
|
+
cacheHitTokensIn: sumReportedUsage(attempts, "cacheHitTokensIn", runtime === "muse"),
|
|
654
|
+
costUsd: runtime === "muse" ? (0, muse_cjs_1.sumMuseSessionCost)(attempts) : sumReportedUsage(attempts, "costUsd"),
|
|
637
655
|
};
|
|
638
656
|
const mapping = (0, exit_conditions_cjs_1.mapExitCondition)({
|
|
639
657
|
...result,
|
|
@@ -947,6 +965,8 @@ function adapterCommand(config, runtime) {
|
|
|
947
965
|
return { bin: config.piBin, extraArgs: config.piExtraArgs };
|
|
948
966
|
case "qoder":
|
|
949
967
|
return { bin: config.qoderBin, extraArgs: config.qoderExtraArgs };
|
|
968
|
+
case "muse":
|
|
969
|
+
return { bin: config.museBin, extraArgs: config.museExtraArgs };
|
|
950
970
|
}
|
|
951
971
|
}
|
|
952
972
|
/**
|
|
@@ -964,7 +984,9 @@ function cacheSplitFields(tokensIn, cacheHitTokensIn) {
|
|
|
964
984
|
const hit = Math.min(cacheHitTokensIn, tokensIn);
|
|
965
985
|
return { cache_hit_input_tokens: hit, cache_miss_input_tokens: tokensIn - hit };
|
|
966
986
|
}
|
|
967
|
-
function sumReportedUsage(attempts, key) {
|
|
987
|
+
function sumReportedUsage(attempts, key, requireComplete = false) {
|
|
988
|
+
if (requireComplete && attempts.some((attempt) => attempt[key] === undefined))
|
|
989
|
+
return undefined;
|
|
968
990
|
const reported = attempts.flatMap((attempt) => {
|
|
969
991
|
const value = attempt[key];
|
|
970
992
|
return value === undefined ? [] : [value];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# `@muse-code/sdk`
|
|
2
|
+
|
|
3
|
+
The TypeScript SDK for Muse Code: a typed Node client for driving a Muse Code
|
|
4
|
+
agent over the Muse Session Protocol (MSP).
|
|
5
|
+
|
|
6
|
+
- Spawn a `muse serve` host, open and resume sessions, and send turns.
|
|
7
|
+
- Stream a turn's items and text deltas as async iterators; await the turn's
|
|
8
|
+
server-authored outcome.
|
|
9
|
+
- Answer tool-approval requests with a handler you register — or register
|
|
10
|
+
none and run under the server's own default-deny.
|
|
11
|
+
- A client-side fold keeps a live, typed view of the session — items, session
|
|
12
|
+
state, pending commands — with gap recovery after dropped deliveries and
|
|
13
|
+
safe same-command retry that can never double-submit.
|
|
14
|
+
|
|
15
|
+
**Zero runtime dependencies**: pure TypeScript over the Node standard
|
|
16
|
+
library. The MSP wire declarations are bundled into the package, so nothing
|
|
17
|
+
else is needed to typecheck against it.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @muse-code/sdk
|
|
23
|
+
npm install -D @types/node # required for TypeScript consumers
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Node 20 or newer. `@types/node` is declared as an **optional peer
|
|
27
|
+
dependency**: a JavaScript consumer needs nothing, while a TypeScript
|
|
28
|
+
consumer installs it explicitly — without it the declarations that name
|
|
29
|
+
`ChildProcess`, `Readable` and friends do not resolve.
|
|
30
|
+
|
|
31
|
+
You also need the Muse Code CLI: a `muse` binary on `PATH`, or an explicit
|
|
32
|
+
path passed at spawn time.
|
|
33
|
+
|
|
34
|
+
**This package versions in lockstep with Muse Code**: the SDK version is the
|
|
35
|
+
Muse Code release it ships with — `@muse-code/sdk X.Y.Z` pairs with Muse Code
|
|
36
|
+
`X.Y.Z`. The stable MSP surface follows the product's compatibility posture:
|
|
37
|
+
evolution is additive, and a breaking change follows a documented deprecation
|
|
38
|
+
and changelog path. Surfaces marked experimental (`x-msp-openness`)
|
|
39
|
+
may change or be removed with a changelog entry and no deprecation window. Pin an
|
|
40
|
+
exact version, and read the changelog before moving off it.
|
|
41
|
+
|
|
42
|
+
## Use
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { MuseClient } from "@muse-code/sdk";
|
|
46
|
+
|
|
47
|
+
const client = await MuseClient.spawn({
|
|
48
|
+
museBin: "muse",
|
|
49
|
+
args: ["serve"],
|
|
50
|
+
clientInfo: { name: "my-app", version: "1.0.0" },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const session = await client.startSession({ workspaceRoot: process.cwd() });
|
|
54
|
+
|
|
55
|
+
const turn = await session.sendUserTurn({
|
|
56
|
+
input: [{ type: "text", text: "Summarize this repository." }],
|
|
57
|
+
});
|
|
58
|
+
for await (const item of turn.items()) console.log(item);
|
|
59
|
+
await turn.completed;
|
|
60
|
+
|
|
61
|
+
await client.close();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`turn.items()` replays what the session already holds before its live tail;
|
|
65
|
+
`turn.deltas()` is live-only. `turn.completed` settles on every exit, so an
|
|
66
|
+
awaited turn can never hang on a session that ended without an answer.
|
|
67
|
+
|
|
68
|
+
To approve tool use programmatically, register a handler on the session;
|
|
69
|
+
approval choices are server-minted, and a decision the request never offered
|
|
70
|
+
is refused and reported rather than sent:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
session.onApproval(async (request) => ({
|
|
74
|
+
choiceId: request.availableChoices[0].choiceId,
|
|
75
|
+
}));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Status: developer preview. The facade — spawn, session open/resume, turns,
|
|
79
|
+
approvals, and recovery after dropped deliveries — is complete and covered by
|
|
80
|
+
the conformance suites in the source repository.
|
|
81
|
+
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
- Guides, cookbook recipes, and the protocol reference:
|
|
85
|
+
<https://meta-models.github.io/muse-code-sdk/>
|
|
86
|
+
- TypeScript API reference:
|
|
87
|
+
<https://meta-models.github.io/muse-code-sdk/next/generated/sdk/>
|
|
88
|
+
- Source and issues: <https://github.com/meta-models/muse-code-sdk>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duplex-agnostic MSP NDJSON connection (spec 14990 FR-012/014/015/016).
|
|
3
|
+
*
|
|
4
|
+
* The transport supplies decoded UTF-8 chunks. Chunks may be empty and may
|
|
5
|
+
* split anywhere — including mid surrogate pair: the byte accounting below
|
|
6
|
+
* reconciles split pairs (FR-012(a)), so transports need not align chunks
|
|
7
|
+
* to character boundaries. The Node child-process binding — which sets
|
|
8
|
+
* stdout's encoding before implementing this interface — is one
|
|
9
|
+
* well-behaved transport, not the general guarantee.
|
|
10
|
+
*/
|
|
11
|
+
import type { ErrorKind, ErrorObject, Notification, Request, RequestId } from "../msp.js";
|
|
12
|
+
/**
|
|
13
|
+
* The two-way text stream a `Connection` speaks MSP over.
|
|
14
|
+
*
|
|
15
|
+
* Implement it to run the protocol over any duplex channel: yield decoded
|
|
16
|
+
* UTF-8 chunks on `incoming` and accept outgoing frames through `write`.
|
|
17
|
+
* Chunks may be empty and may split anywhere — even mid surrogate pair — the
|
|
18
|
+
* connection reassembles them. If your transport decodes bytes itself, use a
|
|
19
|
+
* streaming decoder: a multi-byte character split across two byte chunks and
|
|
20
|
+
* decoded separately becomes a replacement character before the connection
|
|
21
|
+
* ever sees it. The SDK's spawned child-process stdio binding sets the stream
|
|
22
|
+
* encoding, so it is safe, and is one ready-made implementation.
|
|
23
|
+
*/
|
|
24
|
+
export interface DuplexTransport {
|
|
25
|
+
readonly incoming: AsyncIterable<string>;
|
|
26
|
+
write(chunk: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* `flushed` settles once every frame the connection had accepted when
|
|
29
|
+
* `close()` was called has been HANDED to `write()` — submission, not
|
|
30
|
+
* completion. A process-owning transport waits for it inside its own
|
|
31
|
+
* shutdown budget before ending the peer's input, so a frame still queued
|
|
32
|
+
* on a microtask is not dropped; it is never GATED by it, because
|
|
33
|
+
* completion is exactly what a wedged peer withholds. Optional and
|
|
34
|
+
* additive: a transport that ignores it behaves as before.
|
|
35
|
+
*/
|
|
36
|
+
close?(flushed?: Promise<void>): void | Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Module-private friend seam (never on the package barrel, like spawn.ts's
|
|
40
|
+
* `ownedTransport`): the submission tail a process-owning transport adopts as
|
|
41
|
+
* its DEFAULT `flushed`, so a close routed around this class — e.g. the
|
|
42
|
+
* public `child.close()` — gets the same step 0 (PR #22819 round 4).
|
|
43
|
+
*/
|
|
44
|
+
export declare const submissionTail: unique symbol;
|
|
45
|
+
/**
|
|
46
|
+
* Module-private friend seam, like {@link submissionTail}: a request whose
|
|
47
|
+
* promise also yields the exact response frame it settled on, so a caller
|
|
48
|
+
* that refuses a successful result (the handshake's schema-fingerprint
|
|
49
|
+
* check) can put the whole refused frame on `ProtocolError.line`, the same
|
|
50
|
+
* value every other refusal carries (#50588 review).
|
|
51
|
+
*/
|
|
52
|
+
export declare const requestWithFrame: unique symbol;
|
|
53
|
+
/** What {@link requestWithFrame} resolves with: the result and its wire line. */
|
|
54
|
+
export interface FramedResult {
|
|
55
|
+
readonly result: Record<string, unknown>;
|
|
56
|
+
readonly line: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Tuning knobs for a `Connection`. Every option is optional: the defaults
|
|
60
|
+
* suit a spawned host, so most applications never pass this at all.
|
|
61
|
+
*/
|
|
62
|
+
export interface ConnectionOptions {
|
|
63
|
+
readonly frameLimitBytes?: number;
|
|
64
|
+
readonly mintCommandId?: () => string;
|
|
65
|
+
/** Injectable for deterministic transcript clients; values must be unique in flight. */
|
|
66
|
+
readonly mintRequestId?: () => RequestId;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Per-command overrides for `Connection.command`: pin a command id so a
|
|
70
|
+
* reconnect can replay the same logical command, cap the attempt count, or
|
|
71
|
+
* supply your own retry delay.
|
|
72
|
+
*/
|
|
73
|
+
export interface CommandOptions {
|
|
74
|
+
/** Reuse this id for a reconnect/replay of the same logical command. */
|
|
75
|
+
readonly commandId?: string;
|
|
76
|
+
/** Total attempts, including the first. */
|
|
77
|
+
readonly maxAttempts?: number;
|
|
78
|
+
/** Injectable so deterministic tests do not sleep. */
|
|
79
|
+
readonly retryDelay?: (attempt: number, error: MspError) => Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
/** Handles one server-initiated request and resolves with the result to send back. */
|
|
82
|
+
export type ServerRequestHandler = (request: Request) => Promise<Record<string, unknown>>;
|
|
83
|
+
/** Receives every server notification, in arrival order. */
|
|
84
|
+
export type NotificationHandler = (notification: Notification) => void;
|
|
85
|
+
/** Receives each framing or correlation violation the connection detects on its inbound stream. */
|
|
86
|
+
export type ProtocolErrorHandler = (error: ProtocolError) => void;
|
|
87
|
+
/** The one typed error family consumers branch on (INV-012). */
|
|
88
|
+
export declare class MspError extends Error {
|
|
89
|
+
readonly code: number;
|
|
90
|
+
readonly kind: ErrorKind;
|
|
91
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
92
|
+
readonly retryable: boolean | undefined;
|
|
93
|
+
constructor(error: ErrorObject);
|
|
94
|
+
}
|
|
95
|
+
/** A local framing/correlation violation, never a server-authored MSP error. */
|
|
96
|
+
export declare class ProtocolError extends Error {
|
|
97
|
+
/**
|
|
98
|
+
* The inbound frame (wire line) the violation was detected on, when there
|
|
99
|
+
* is one — the whole line, e.g. the response a pending request refused;
|
|
100
|
+
* an oversized frame is cut to the frame limit. Absent for local state
|
|
101
|
+
* errors (closed connection, EOF) that no frame caused.
|
|
102
|
+
*/
|
|
103
|
+
readonly line: string | undefined;
|
|
104
|
+
constructor(message: string, line?: string);
|
|
105
|
+
}
|
|
106
|
+
/** Create the SDK-owned UUIDv7 command-id source for a connection/client. */
|
|
107
|
+
export declare function createUuidV7Mint(): () => string;
|
|
108
|
+
/**
|
|
109
|
+
* One MSP connection over newline-delimited JSON.
|
|
110
|
+
*
|
|
111
|
+
* The connection sends your requests and commands, correlates their
|
|
112
|
+
* responses, enforces the frame-size limit, and hands inbound notifications
|
|
113
|
+
* and server requests to the handlers you register. It is transport-agnostic:
|
|
114
|
+
* pair it with any `DuplexTransport`. Most applications never build one
|
|
115
|
+
* directly — `MuseClient.spawn` or `spawnMspConnection` does it for you;
|
|
116
|
+
* reach for a bare `Connection` when you need the raw notification feed or
|
|
117
|
+
* your own transport.
|
|
118
|
+
*/
|
|
119
|
+
export declare class Connection {
|
|
120
|
+
#private;
|
|
121
|
+
constructor(transport: DuplexTransport, options?: ConnectionOptions);
|
|
122
|
+
get closed(): Promise<void>;
|
|
123
|
+
/** See {@link submissionTail}. */
|
|
124
|
+
[submissionTail](): Promise<void>;
|
|
125
|
+
request(method: string, params?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
126
|
+
/** See {@link requestWithFrame}. */
|
|
127
|
+
[requestWithFrame](method: string, params?: Record<string, unknown>): Promise<FramedResult>;
|
|
128
|
+
/**
|
|
129
|
+
* Take one id from THIS connection's single `commandId` minter (INV-013).
|
|
130
|
+
*
|
|
131
|
+
* Exposed for the callers that need the id BEFORE the ack: `sendUserTurn`
|
|
132
|
+
* records its optimistic SS4.13 entry under it, and an entry created only on
|
|
133
|
+
* the ack is invisible for exactly the window it exists to cover. The
|
|
134
|
+
* alternative was a facade-side mint, which is the second minter INV-013
|
|
135
|
+
* forbids — delegating here keeps the single-minter property intact and
|
|
136
|
+
* preserves the injected `mintCommandId` seam for deterministic transcripts.
|
|
137
|
+
*
|
|
138
|
+
* Pair it with `command(..., { commandId })`, which reuses the id instead of
|
|
139
|
+
* minting a second one.
|
|
140
|
+
*/
|
|
141
|
+
mintCommandId(): string;
|
|
142
|
+
notify(method: string, params?: Record<string, unknown>): void;
|
|
143
|
+
onServerRequest(handler: ServerRequestHandler): void;
|
|
144
|
+
onNotification(handler: NotificationHandler): void;
|
|
145
|
+
onProtocolError(handler: ProtocolErrorHandler): void;
|
|
146
|
+
flush(): Promise<void>;
|
|
147
|
+
command(method: string, params: Record<string, unknown>, options?: CommandOptions): Promise<Record<string, unknown>>;
|
|
148
|
+
close(): Promise<void>;
|
|
149
|
+
}
|