codeam-cli 2.73.10 → 2.74.1
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/CHANGELOG.md +16 -0
- package/dist/index.js +323 -61
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `codeam-cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.74.0] — 2026-09-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Tool router (4 schemas instead of hundreds) + house agent's real context window (#700)
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
|
|
15
|
+
- **cli:** Two of today's tests were not Windows-portable — main went red (#699)
|
|
16
|
+
|
|
17
|
+
## [2.73.10] — 2026-09-03
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **cli:** Re-resolve the integrations manifest from the backend at session start (#697)
|
|
22
|
+
|
|
7
23
|
## [2.73.9] — 2026-09-03
|
|
8
24
|
|
|
9
25
|
### Tests
|
package/dist/index.js
CHANGED
|
@@ -271,8 +271,8 @@ var init_stdio_proxy = __esm({
|
|
|
271
271
|
}
|
|
272
272
|
async spawnChild(stdout, preResolved) {
|
|
273
273
|
const spec = preResolved ?? await this.opts.spawnSpec();
|
|
274
|
-
const
|
|
275
|
-
const child =
|
|
274
|
+
const spawn48 = this.opts.spawnImpl ?? import_node_child_process33.spawn;
|
|
275
|
+
const child = spawn48(spec.command, spec.args, {
|
|
276
276
|
env: { ...process.env, ...spec.env },
|
|
277
277
|
// env only — never argv
|
|
278
278
|
stdio: ["pipe", "pipe", "inherit"]
|
|
@@ -8136,7 +8136,7 @@ function readAnonId() {
|
|
|
8136
8136
|
}
|
|
8137
8137
|
function superProperties() {
|
|
8138
8138
|
return {
|
|
8139
|
-
cliVersion: true ? "2.
|
|
8139
|
+
cliVersion: true ? "2.74.1" : "0.0.0-dev",
|
|
8140
8140
|
nodeVersion: process.version,
|
|
8141
8141
|
platform: process.platform,
|
|
8142
8142
|
arch: process.arch,
|
|
@@ -8378,7 +8378,7 @@ var http = __toESM(require("http"));
|
|
|
8378
8378
|
// package.json
|
|
8379
8379
|
var package_default = {
|
|
8380
8380
|
name: "codeam-cli",
|
|
8381
|
-
version: "2.
|
|
8381
|
+
version: "2.74.1",
|
|
8382
8382
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
8383
8383
|
type: "commonjs",
|
|
8384
8384
|
main: "dist/index.js",
|
|
@@ -9971,7 +9971,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
9971
9971
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
9972
9972
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
9973
9973
|
// pair/reconnect). Older backends ignore the extra field.
|
|
9974
|
-
..."2.
|
|
9974
|
+
..."2.74.1" ? { ideVersion: "2.74.1" } : {}
|
|
9975
9975
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
9976
9976
|
}
|
|
9977
9977
|
/**
|
|
@@ -22037,11 +22037,35 @@ function readHouseProxyChildEnv() {
|
|
|
22037
22037
|
return {};
|
|
22038
22038
|
}
|
|
22039
22039
|
}
|
|
22040
|
+
var HOUSE_MODEL_CONTEXT_TOKENS = "512000";
|
|
22040
22041
|
function buildHouseProxyChildEnv(cfg) {
|
|
22041
22042
|
const env = {
|
|
22042
22043
|
ANTHROPIC_BASE_URL: cfg.baseUrl,
|
|
22043
22044
|
ANTHROPIC_AUTH_TOKEN: cfg.token,
|
|
22044
|
-
|
|
22045
|
+
// ⚠️ MAX_CONTEXT_TOKENS, not (only) AUTO_COMPACT_WINDOW — and this is
|
|
22046
|
+
// HOUSE-AGENT ONLY: it lives in this builder precisely so a user's own
|
|
22047
|
+
// Claude Code (a real Anthropic model claude knows) is never touched.
|
|
22048
|
+
//
|
|
22049
|
+
// claude does not know `MiniMax-M3`, so it falls back to its
|
|
22050
|
+
// unknown-model default (200k). `CLAUDE_CODE_AUTO_COMPACT_WINDOW` is then
|
|
22051
|
+
// applied as `Math.min(assumedWindow, env)` — it can only SHRINK the
|
|
22052
|
+
// window, never grow it — so the 512000 we used to set here was inert:
|
|
22053
|
+
// claude kept believing 200k. Meanwhile a session with a dozen linked
|
|
22054
|
+
// integrations carries ~175k tokens of MCP tool schemas on EVERY turn
|
|
22055
|
+
// (measured: 475 tools ≈ 698 KB on a real box), so the context started
|
|
22056
|
+
// ~88% full and a 28-character prompt triggered a compaction, then the
|
|
22057
|
+
// next small tool result triggered another: "Autocompact is thrashing:
|
|
22058
|
+
// the context refilled to the limit within 3 turns", 13 compactions in
|
|
22059
|
+
// one session (rafaelph90.br@gmail.com, 2026-09-03).
|
|
22060
|
+
//
|
|
22061
|
+
// claude's own unknown-model notice names the fix: "set
|
|
22062
|
+
// CLAUDE_CODE_MAX_CONTEXT_TOKENS to its real window" — that variable is
|
|
22063
|
+
// what `wL()` returns for an unrecognised model. MiniMax-M3 is a 1M-context
|
|
22064
|
+
// model with a guaranteed minimum of 512K, so 512000 here is the
|
|
22065
|
+
// conservative truth, not a wish. AUTO_COMPACT_WINDOW stays as the
|
|
22066
|
+
// compaction threshold beneath it.
|
|
22067
|
+
CLAUDE_CODE_MAX_CONTEXT_TOKENS: HOUSE_MODEL_CONTEXT_TOKENS,
|
|
22068
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: HOUSE_MODEL_CONTEXT_TOKENS,
|
|
22045
22069
|
API_TIMEOUT_MS: "3000000"
|
|
22046
22070
|
};
|
|
22047
22071
|
if (cfg.openRouter === true) {
|
|
@@ -22066,6 +22090,7 @@ var HOUSE_PROXY_ENV_KEYS = [
|
|
|
22066
22090
|
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
22067
22091
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
22068
22092
|
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
|
22093
|
+
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
|
22069
22094
|
"API_TIMEOUT_MS",
|
|
22070
22095
|
"CLAUDE_CONFIG_DIR"
|
|
22071
22096
|
];
|
|
@@ -22247,7 +22272,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
22247
22272
|
if (process.env.NODE_ENV === "test") return;
|
|
22248
22273
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
22249
22274
|
if (process.env.CI) return;
|
|
22250
|
-
const current2 = true ? "2.
|
|
22275
|
+
const current2 = true ? "2.74.1" : null;
|
|
22251
22276
|
if (!current2) return;
|
|
22252
22277
|
const cache = readCache();
|
|
22253
22278
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -22264,7 +22289,7 @@ function checkForUpdates() {
|
|
|
22264
22289
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
22265
22290
|
if (process.env.CI) return;
|
|
22266
22291
|
if (!process.stdout.isTTY) return;
|
|
22267
|
-
const current2 = true ? "2.
|
|
22292
|
+
const current2 = true ? "2.74.1" : null;
|
|
22268
22293
|
if (!current2) return;
|
|
22269
22294
|
const cache = readCache();
|
|
22270
22295
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -22287,7 +22312,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
|
22287
22312
|
var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
|
|
22288
22313
|
var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
|
|
22289
22314
|
function currentCliVersion() {
|
|
22290
|
-
return true ? "2.
|
|
22315
|
+
return true ? "2.74.1" : null;
|
|
22291
22316
|
}
|
|
22292
22317
|
async function installedVersion(deps) {
|
|
22293
22318
|
const ls = await deps.run(
|
|
@@ -23464,20 +23489,7 @@ var HostAgentSupervisor = class {
|
|
|
23464
23489
|
if (payload.houseProxy) {
|
|
23465
23490
|
const { baseUrl, token, agentKind, openRouter } = payload.houseProxy;
|
|
23466
23491
|
childEnv = {
|
|
23467
|
-
|
|
23468
|
-
ANTHROPIC_AUTH_TOKEN: token,
|
|
23469
|
-
// House pins MiniMax; OpenRouter routes the real Claude model names the
|
|
23470
|
-
// agent sends, so it sets NO model overrides — but it DOES need
|
|
23471
|
-
// ANTHROPIC_API_KEY empty so a stale key can't override the Bearer auth
|
|
23472
|
-
// token (OpenRouter's Claude Code setup requires ANTHROPIC_API_KEY="").
|
|
23473
|
-
...openRouter ? { ANTHROPIC_API_KEY: "" } : {
|
|
23474
|
-
ANTHROPIC_MODEL: "MiniMax-M3",
|
|
23475
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M3",
|
|
23476
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M3",
|
|
23477
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M3"
|
|
23478
|
-
},
|
|
23479
|
-
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "512000",
|
|
23480
|
-
API_TIMEOUT_MS: "3000000",
|
|
23492
|
+
...buildHouseProxyChildEnv({ baseUrl, token, openRouter: openRouter === true }),
|
|
23481
23493
|
CODEAM_AUTO_TOKEN: payload.autoPairToken
|
|
23482
23494
|
};
|
|
23483
23495
|
const houseConfigDir = path52.join(
|
|
@@ -36832,12 +36844,12 @@ function buildRelaunchProxyEnv(baseEnv) {
|
|
|
36832
36844
|
return env;
|
|
36833
36845
|
}
|
|
36834
36846
|
var relaunchProxyWithoutBudget = async () => {
|
|
36835
|
-
const { spawn:
|
|
36847
|
+
const { spawn: spawn48 } = await import("child_process");
|
|
36836
36848
|
killHeadroomProxy();
|
|
36837
36849
|
await new Promise((r) => setTimeout(r, 500));
|
|
36838
36850
|
const proxyEnv = buildRelaunchProxyEnv(process.env);
|
|
36839
36851
|
try {
|
|
36840
|
-
const proxy =
|
|
36852
|
+
const proxy = spawn48(
|
|
36841
36853
|
"headroom",
|
|
36842
36854
|
["proxy", "--port", "8787"],
|
|
36843
36855
|
{ stdio: "ignore", detached: true, env: proxyEnv }
|
|
@@ -39580,6 +39592,172 @@ var packStatusH = async (ctx) => {
|
|
|
39580
39592
|
await ctx.relay.sendResult(ctx.cmd.id, "completed", { state });
|
|
39581
39593
|
};
|
|
39582
39594
|
|
|
39595
|
+
// src/integrations/mcp-router.ts
|
|
39596
|
+
var MCP_ROUTER_SERVER_NAME = "codeam";
|
|
39597
|
+
var CALL_TIMEOUT_MS = (() => {
|
|
39598
|
+
const raw = Number(process.env.CODEAM_MCP_TOOL_TIMEOUT_MS);
|
|
39599
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 12e4;
|
|
39600
|
+
})();
|
|
39601
|
+
var CHILD_START_TIMEOUT_MS = 9e4;
|
|
39602
|
+
function qualifiedName(integration, tool) {
|
|
39603
|
+
return `${integration}/${tool}`;
|
|
39604
|
+
}
|
|
39605
|
+
function splitQualifiedName(q2) {
|
|
39606
|
+
const i = q2.indexOf("/");
|
|
39607
|
+
if (i <= 0 || i === q2.length - 1) return null;
|
|
39608
|
+
return { integration: q2.slice(0, i), tool: q2.slice(i + 1) };
|
|
39609
|
+
}
|
|
39610
|
+
function searchTools(tools, query, integration) {
|
|
39611
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
39612
|
+
return tools.filter((t2) => !integration || t2.integration === integration).filter((t2) => {
|
|
39613
|
+
if (words.length === 0) return true;
|
|
39614
|
+
const hay = `${t2.integration} ${t2.name} ${t2.description}`.toLowerCase();
|
|
39615
|
+
return words.every((w3) => hay.includes(w3));
|
|
39616
|
+
}).sort((a, b) => {
|
|
39617
|
+
const an = words.some((w3) => a.name.toLowerCase().includes(w3)) ? 0 : 1;
|
|
39618
|
+
const bn = words.some((w3) => b.name.toLowerCase().includes(w3)) ? 0 : 1;
|
|
39619
|
+
return an - bn || a.integration.localeCompare(b.integration) || a.name.localeCompare(b.name);
|
|
39620
|
+
});
|
|
39621
|
+
}
|
|
39622
|
+
function routerToolDefinitions() {
|
|
39623
|
+
return [
|
|
39624
|
+
{
|
|
39625
|
+
name: "list_integrations",
|
|
39626
|
+
description: "List the integrations attached to this session (Jira, Linear, ClickUp, Postman, \u2026) with how many tools each exposes and whether it is available. Call this first when the user asks what you can do with an external service.",
|
|
39627
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
39628
|
+
},
|
|
39629
|
+
{
|
|
39630
|
+
name: "search_tools",
|
|
39631
|
+
description: 'Search the tools of the attached integrations by keywords (e.g. "create task", "list boards", "send email"). Returns each match as `<integration>/<tool>` with a one-line description. Use `describe_tool` for the full argument schema, then `call_tool` to run it.',
|
|
39632
|
+
inputSchema: {
|
|
39633
|
+
type: "object",
|
|
39634
|
+
properties: {
|
|
39635
|
+
query: { type: "string", description: "Keywords to match against tool names and descriptions." },
|
|
39636
|
+
integration: { type: "string", description: 'Optional: restrict to one integration id, e.g. "clickup".' },
|
|
39637
|
+
limit: { type: "integer", minimum: 1, maximum: 50, default: 20 }
|
|
39638
|
+
},
|
|
39639
|
+
required: ["query"],
|
|
39640
|
+
additionalProperties: false
|
|
39641
|
+
}
|
|
39642
|
+
},
|
|
39643
|
+
{
|
|
39644
|
+
name: "describe_tool",
|
|
39645
|
+
description: "Return the full JSON input schema of one tool, given its `<integration>/<tool>` name from `search_tools`.",
|
|
39646
|
+
inputSchema: {
|
|
39647
|
+
type: "object",
|
|
39648
|
+
properties: { tool: { type: "string", description: '`<integration>/<tool>`, e.g. "clickup/create_task".' } },
|
|
39649
|
+
required: ["tool"],
|
|
39650
|
+
additionalProperties: false
|
|
39651
|
+
}
|
|
39652
|
+
},
|
|
39653
|
+
{
|
|
39654
|
+
name: "call_tool",
|
|
39655
|
+
description: "Invoke one integration tool by its `<integration>/<tool>` name with the arguments its schema expects. The result is returned verbatim from the integration.",
|
|
39656
|
+
inputSchema: {
|
|
39657
|
+
type: "object",
|
|
39658
|
+
properties: {
|
|
39659
|
+
tool: { type: "string", description: "`<integration>/<tool>`." },
|
|
39660
|
+
arguments: { type: "object", description: "Arguments matching the schema from `describe_tool`.", additionalProperties: true }
|
|
39661
|
+
},
|
|
39662
|
+
required: ["tool"],
|
|
39663
|
+
additionalProperties: false
|
|
39664
|
+
}
|
|
39665
|
+
}
|
|
39666
|
+
];
|
|
39667
|
+
}
|
|
39668
|
+
async function startChild(id, env, sdk) {
|
|
39669
|
+
const state = { id, client: null, tools: [], error: null };
|
|
39670
|
+
const timer = new Promise((_2, rej) => {
|
|
39671
|
+
const t2 = setTimeout(
|
|
39672
|
+
() => rej(new Error(`timed out after ${CHILD_START_TIMEOUT_MS / 1e3}s starting`)),
|
|
39673
|
+
CHILD_START_TIMEOUT_MS
|
|
39674
|
+
);
|
|
39675
|
+
t2.unref?.();
|
|
39676
|
+
});
|
|
39677
|
+
try {
|
|
39678
|
+
const transport = new sdk.StdioClientTransport({
|
|
39679
|
+
command: process.execPath,
|
|
39680
|
+
args: [process.argv[1], "mcp-run", id],
|
|
39681
|
+
env: Object.fromEntries(
|
|
39682
|
+
Object.entries(env).filter((e) => typeof e[1] === "string")
|
|
39683
|
+
),
|
|
39684
|
+
stderr: "inherit"
|
|
39685
|
+
});
|
|
39686
|
+
const client3 = new sdk.Client({ name: "codeam-router", version: "1.0.0" }, { capabilities: {} });
|
|
39687
|
+
await Promise.race([client3.connect(transport), timer]);
|
|
39688
|
+
const listed = await Promise.race([client3.listTools(), timer]);
|
|
39689
|
+
state.client = client3;
|
|
39690
|
+
state.tools = listed.tools.map((t2) => ({
|
|
39691
|
+
integration: id,
|
|
39692
|
+
name: t2.name,
|
|
39693
|
+
description: t2.description ?? "",
|
|
39694
|
+
inputSchema: t2.inputSchema
|
|
39695
|
+
}));
|
|
39696
|
+
log.info("mcpRouter", `${id}: ${state.tools.length} tool(s) ready`);
|
|
39697
|
+
} catch (err) {
|
|
39698
|
+
state.error = err instanceof Error ? err.message : String(err);
|
|
39699
|
+
log.warn("mcpRouter", `${id}: unavailable \u2014 ${state.error}`);
|
|
39700
|
+
}
|
|
39701
|
+
return state;
|
|
39702
|
+
}
|
|
39703
|
+
async function mcpRouter() {
|
|
39704
|
+
const manifest = readIntegrationsManifest();
|
|
39705
|
+
const ids = (manifest?.integrations ?? []).filter((e) => e.delivery?.mcp).map((e) => e.id);
|
|
39706
|
+
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
39707
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
39708
|
+
const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
39709
|
+
const { Client: ClientCtor } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
39710
|
+
const { StdioClientTransport: TransportCtor } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
39711
|
+
const sdk = { Client: ClientCtor, StdioClientTransport: TransportCtor };
|
|
39712
|
+
const children = await Promise.all(ids.map((id) => startChild(id, process.env, sdk)));
|
|
39713
|
+
const byId = new Map(children.map((c2) => [c2.id, c2]));
|
|
39714
|
+
const allTools = children.flatMap((c2) => c2.tools);
|
|
39715
|
+
log.info("mcpRouter", `fronting ${children.filter((c2) => c2.client).length}/${ids.length} integration server(s), ${allTools.length} tools behind 4`);
|
|
39716
|
+
const server = new Server({ name: MCP_ROUTER_SERVER_NAME, version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
39717
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: routerToolDefinitions() }));
|
|
39718
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
39719
|
+
const name = req.params.name;
|
|
39720
|
+
const args2 = req.params.arguments ?? {};
|
|
39721
|
+
const text = (v, isError2 = false) => ({ content: [{ type: "text", text: typeof v === "string" ? v : JSON.stringify(v, null, 2) }], isError: isError2 });
|
|
39722
|
+
if (name === "list_integrations") {
|
|
39723
|
+
return text(children.map((c2) => ({ integration: c2.id, available: c2.client !== null, tools: c2.tools.length, ...c2.error ? { error: c2.error } : {} })));
|
|
39724
|
+
}
|
|
39725
|
+
if (name === "search_tools") {
|
|
39726
|
+
const q2 = typeof args2.query === "string" ? args2.query : "";
|
|
39727
|
+
const integration = typeof args2.integration === "string" ? args2.integration : void 0;
|
|
39728
|
+
const limit = typeof args2.limit === "number" ? Math.max(1, Math.min(50, args2.limit)) : 20;
|
|
39729
|
+
const hits = searchTools(allTools, q2, integration).slice(0, limit);
|
|
39730
|
+
return text(hits.map((t2) => ({ tool: qualifiedName(t2.integration, t2.name), description: t2.description })));
|
|
39731
|
+
}
|
|
39732
|
+
if (name === "describe_tool" || name === "call_tool") {
|
|
39733
|
+
const q2 = typeof args2.tool === "string" ? args2.tool : "";
|
|
39734
|
+
const parts = splitQualifiedName(q2);
|
|
39735
|
+
if (!parts) return text(`Expected "<integration>/<tool>", got "${q2}". Use search_tools to find the name.`, true);
|
|
39736
|
+
const child = byId.get(parts.integration);
|
|
39737
|
+
if (!child) return text(`No integration "${parts.integration}" is attached to this session. Use list_integrations.`, true);
|
|
39738
|
+
if (!child.client) return text(`Integration "${parts.integration}" is unavailable: ${child.error ?? "failed to start"}.`, true);
|
|
39739
|
+
const tool = child.tools.find((t2) => t2.name === parts.tool);
|
|
39740
|
+
if (!tool) return text(`No tool "${parts.tool}" in "${parts.integration}". Use search_tools.`, true);
|
|
39741
|
+
if (name === "describe_tool") return text({ tool: q2, description: tool.description, inputSchema: tool.inputSchema });
|
|
39742
|
+
const timer = new Promise((_2, rej) => setTimeout(() => rej(new Error(`"${q2}" did not answer within ${CALL_TIMEOUT_MS / 1e3}s`)), CALL_TIMEOUT_MS).unref?.());
|
|
39743
|
+
try {
|
|
39744
|
+
const res = await Promise.race([
|
|
39745
|
+
child.client.callTool({ name: parts.tool, arguments: args2.arguments ?? {} }),
|
|
39746
|
+
timer
|
|
39747
|
+
]);
|
|
39748
|
+
return { content: res.content ?? [], isError: res.isError === true };
|
|
39749
|
+
} catch (err) {
|
|
39750
|
+
return text(`${q2} failed: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
39751
|
+
}
|
|
39752
|
+
}
|
|
39753
|
+
return text(`Unknown tool "${name}".`, true);
|
|
39754
|
+
});
|
|
39755
|
+
await server.connect(new StdioServerTransport());
|
|
39756
|
+
process.stdin.on("close", () => {
|
|
39757
|
+
for (const c2 of children) void c2.client?.close().catch(() => void 0);
|
|
39758
|
+
});
|
|
39759
|
+
}
|
|
39760
|
+
|
|
39583
39761
|
// src/integrations/provision.ts
|
|
39584
39762
|
function buildMcpServersForStart(ctx) {
|
|
39585
39763
|
const manifest = readIntegrationsManifest();
|
|
@@ -39608,6 +39786,30 @@ function buildMcpServersForStart(ctx) {
|
|
|
39608
39786
|
env
|
|
39609
39787
|
});
|
|
39610
39788
|
}
|
|
39789
|
+
if (manifest.toolRouter === true && servers.length > 0) {
|
|
39790
|
+
const routed = servers.map((s) => s.name);
|
|
39791
|
+
log.info(
|
|
39792
|
+
"integrations",
|
|
39793
|
+
`tool router ON \u2014 fronting ${routed.length} MCP server(s) behind one: ${routed.join(", ")}`
|
|
39794
|
+
);
|
|
39795
|
+
return [
|
|
39796
|
+
{
|
|
39797
|
+
name: MCP_ROUTER_SERVER_NAME,
|
|
39798
|
+
command: process.execPath,
|
|
39799
|
+
args: [process.argv[1], "mcp-router"],
|
|
39800
|
+
// The router re-spawns each shim with THIS env: the same session /
|
|
39801
|
+
// plugin id / token / secret the shims got when injected directly.
|
|
39802
|
+
// No per-integration id — each child receives its own via
|
|
39803
|
+
// `mcp-run <id>` argv, which the shim prefers.
|
|
39804
|
+
env: [
|
|
39805
|
+
{ name: "CODEAM_MCP_SESSION_ID", value: ctx.sessionId },
|
|
39806
|
+
{ name: "CODEAM_MCP_PLUGIN_ID", value: ctx.pluginId },
|
|
39807
|
+
{ name: "CODEAM_MCP_PLUGIN_TOKEN", value: ctx.pluginAuthToken },
|
|
39808
|
+
...ctx.pollSecret ? [{ name: "CODEAM_MCP_POLL_SECRET", value: ctx.pollSecret }] : []
|
|
39809
|
+
]
|
|
39810
|
+
}
|
|
39811
|
+
];
|
|
39812
|
+
}
|
|
39611
39813
|
if (servers.length) {
|
|
39612
39814
|
log.info(
|
|
39613
39815
|
"integrations",
|
|
@@ -43281,7 +43483,11 @@ async function refreshIntegrationsManifest(ctx, fetchImpl = fetch, timeoutMs = 8
|
|
|
43281
43483
|
const resp = await fetchImpl(url2, {
|
|
43282
43484
|
method: "POST",
|
|
43283
43485
|
headers: { "Content-Type": "application/json", "X-Plugin-Auth-Token": ctx.pluginAuthToken },
|
|
43284
|
-
body: JSON.stringify({
|
|
43486
|
+
body: JSON.stringify({
|
|
43487
|
+
sessionId: ctx.sessionId,
|
|
43488
|
+
pluginId: ctx.pluginId,
|
|
43489
|
+
...ctx.agent ? { agent: ctx.agent } : {}
|
|
43490
|
+
}),
|
|
43285
43491
|
signal: ac.signal
|
|
43286
43492
|
});
|
|
43287
43493
|
if (!resp.ok) {
|
|
@@ -44376,7 +44582,11 @@ async function start(requestedAgent, presetSession) {
|
|
|
44376
44582
|
await refreshIntegrationsManifest({
|
|
44377
44583
|
sessionId: session.id,
|
|
44378
44584
|
pluginId,
|
|
44379
|
-
pluginAuthToken: session.pluginAuthToken
|
|
44585
|
+
pluginAuthToken: session.pluginAuthToken,
|
|
44586
|
+
// The house agent runs the `claude` runtime, so `session.agent` cannot
|
|
44587
|
+
// tell it apart; the managed-proxy env can (`isHouseProxyEnv`). The
|
|
44588
|
+
// backend turns the tool router on for the house agent ONLY.
|
|
44589
|
+
agent: isHouseProxyEnv(process.env) ? HOUSE_AGENT_ID : session.agent
|
|
44380
44590
|
});
|
|
44381
44591
|
}
|
|
44382
44592
|
const mcpServers = mergeWithLocalMcpServers(
|
|
@@ -47227,7 +47437,7 @@ function checkChokidar() {
|
|
|
47227
47437
|
}
|
|
47228
47438
|
async function doctor(args2 = []) {
|
|
47229
47439
|
const json = args2.includes("--json");
|
|
47230
|
-
const cliVersion = true ? "2.
|
|
47440
|
+
const cliVersion = true ? "2.74.1" : "0.0.0-dev";
|
|
47231
47441
|
const apiBase2 = resolveApiBaseUrl();
|
|
47232
47442
|
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
47233
47443
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -47425,6 +47635,15 @@ async function completion(args2) {
|
|
|
47425
47635
|
|
|
47426
47636
|
// src/integrations/mcp-run.ts
|
|
47427
47637
|
var import_node_child_process34 = require("child_process");
|
|
47638
|
+
|
|
47639
|
+
// src/integrations/launcher-env.ts
|
|
47640
|
+
var LAUNCHER_ENV = Object.freeze({
|
|
47641
|
+
npm_config_audit: "false",
|
|
47642
|
+
npm_config_fund: "false",
|
|
47643
|
+
npm_config_update_notifier: "false"
|
|
47644
|
+
});
|
|
47645
|
+
|
|
47646
|
+
// src/integrations/mcp-run.ts
|
|
47428
47647
|
var import_node_fs14 = require("fs");
|
|
47429
47648
|
var import_node_os14 = __toESM(require("os"));
|
|
47430
47649
|
var import_node_path13 = __toESM(require("path"));
|
|
@@ -47554,6 +47773,14 @@ function ensureCommand(command2) {
|
|
|
47554
47773
|
);
|
|
47555
47774
|
}
|
|
47556
47775
|
}
|
|
47776
|
+
function childEnvFor(delivery, token) {
|
|
47777
|
+
const env = { ...LAUNCHER_ENV, ...delivery.staticEnv };
|
|
47778
|
+
for (const [envVar, field] of Object.entries(delivery.envMapping)) {
|
|
47779
|
+
const value = token[field];
|
|
47780
|
+
if (typeof value === "string" && value) env[envVar] = value;
|
|
47781
|
+
}
|
|
47782
|
+
return env;
|
|
47783
|
+
}
|
|
47557
47784
|
async function mcpRun(args2) {
|
|
47558
47785
|
const id = args2[0] ?? process.env.CODEAM_MCP_INTEGRATION_ID;
|
|
47559
47786
|
const sessionId = process.env.CODEAM_MCP_SESSION_ID;
|
|
@@ -47603,12 +47830,7 @@ async function mcpRun(args2) {
|
|
|
47603
47830
|
const proxy = new RestartableStdioProxy({
|
|
47604
47831
|
spawnSpec: async () => {
|
|
47605
47832
|
current2 = await client3.getToken(id);
|
|
47606
|
-
|
|
47607
|
-
for (const [envVar, field] of Object.entries(delivery.envMapping)) {
|
|
47608
|
-
const value = current2[field];
|
|
47609
|
-
if (typeof value === "string" && value) env[envVar] = value;
|
|
47610
|
-
}
|
|
47611
|
-
return { command: launcher, args: delivery.args, env };
|
|
47833
|
+
return { command: launcher, args: delivery.args, env: childEnvFor(delivery, current2) };
|
|
47612
47834
|
},
|
|
47613
47835
|
shouldRestartNow: () => current2 !== null && new Date(current2.expiresAt).getTime() - Date.now() < RESTART_AHEAD_MS
|
|
47614
47836
|
});
|
|
@@ -47655,37 +47877,72 @@ function warmCommand(target) {
|
|
|
47655
47877
|
}
|
|
47656
47878
|
return null;
|
|
47657
47879
|
}
|
|
47658
|
-
|
|
47659
|
-
|
|
47880
|
+
var WARM_PACKAGE_TIMEOUT_MS = 12e4;
|
|
47881
|
+
var WARM_TOTAL_BUDGET_MS = 10 * 6e4;
|
|
47882
|
+
var WARM_CONCURRENCY = 4;
|
|
47883
|
+
function runCapturing(command2, args2, timeoutMs) {
|
|
47884
|
+
return new Promise((resolve11) => {
|
|
47885
|
+
const child = (0, import_node_child_process35.spawn)(command2, args2, {
|
|
47886
|
+
env: { ...process.env, ...LAUNCHER_ENV },
|
|
47887
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
47888
|
+
});
|
|
47889
|
+
let stderr = "";
|
|
47890
|
+
child.stderr?.setEncoding("utf8");
|
|
47891
|
+
child.stderr?.on("data", (c2) => stderr += c2);
|
|
47892
|
+
const timer = setTimeout(() => {
|
|
47893
|
+
child.kill("SIGKILL");
|
|
47894
|
+
resolve11({ status: null, stderr, error: new Error(`spawn ${command2} ETIMEDOUT`) });
|
|
47895
|
+
}, timeoutMs);
|
|
47896
|
+
child.on("error", (error) => {
|
|
47897
|
+
clearTimeout(timer);
|
|
47898
|
+
resolve11({ status: null, stderr, error });
|
|
47899
|
+
});
|
|
47900
|
+
child.on("close", (status2) => {
|
|
47901
|
+
clearTimeout(timer);
|
|
47902
|
+
resolve11({ status: status2, stderr });
|
|
47903
|
+
});
|
|
47904
|
+
});
|
|
47905
|
+
}
|
|
47906
|
+
async function mcpWarm(argv = [], opts = {}) {
|
|
47907
|
+
const packageTimeoutMs = opts.packageTimeoutMs ?? WARM_PACKAGE_TIMEOUT_MS;
|
|
47908
|
+
const totalBudgetMs = opts.totalBudgetMs ?? WARM_TOTAL_BUDGET_MS;
|
|
47909
|
+
const concurrency = opts.concurrency ?? WARM_CONCURRENCY;
|
|
47660
47910
|
const targets = warmTargets();
|
|
47661
47911
|
const only = argv.filter((a) => !a.startsWith("-"));
|
|
47662
47912
|
const selected = only.length > 0 ? targets.filter((t2) => only.includes(t2.id)) : targets;
|
|
47663
47913
|
const result = { warmed: [], skipped: [], failed: [] };
|
|
47664
47914
|
console.log(`[codeam mcp-warm] ${selected.length} MCP package(s) to pre-fetch`);
|
|
47665
|
-
|
|
47666
|
-
|
|
47667
|
-
|
|
47668
|
-
|
|
47669
|
-
|
|
47670
|
-
|
|
47671
|
-
|
|
47672
|
-
|
|
47673
|
-
|
|
47674
|
-
|
|
47675
|
-
|
|
47676
|
-
|
|
47677
|
-
|
|
47678
|
-
|
|
47679
|
-
|
|
47680
|
-
|
|
47681
|
-
|
|
47682
|
-
const
|
|
47683
|
-
|
|
47684
|
-
|
|
47685
|
-
|
|
47686
|
-
|
|
47915
|
+
const deadline = Date.now() + totalBudgetMs;
|
|
47916
|
+
const queue = [...selected];
|
|
47917
|
+
const worker = async () => {
|
|
47918
|
+
for (let target = queue.shift(); target; target = queue.shift()) {
|
|
47919
|
+
const cmd = warmCommand(target);
|
|
47920
|
+
if (!cmd) {
|
|
47921
|
+
result.skipped.push(`${target.spec} (no warm recipe for '${target.launcher}')`);
|
|
47922
|
+
continue;
|
|
47923
|
+
}
|
|
47924
|
+
const remaining = deadline - Date.now();
|
|
47925
|
+
if (remaining <= 0) {
|
|
47926
|
+
result.failed.push({ spec: target.spec, reason: "warm budget spent" });
|
|
47927
|
+
console.log(` MISS ${target.spec} (0.0s) \u2014 fetched at runtime instead: warm budget spent`);
|
|
47928
|
+
continue;
|
|
47929
|
+
}
|
|
47930
|
+
const started = Date.now();
|
|
47931
|
+
const run = await runCapturing(cmd.command, cmd.args, Math.min(packageTimeoutMs, remaining));
|
|
47932
|
+
const secs = ((Date.now() - started) / 1e3).toFixed(1);
|
|
47933
|
+
if (run.status === 0) {
|
|
47934
|
+
result.warmed.push(target.spec);
|
|
47935
|
+
console.log(` OK ${target.spec} (${secs}s)`);
|
|
47936
|
+
} else {
|
|
47937
|
+
const noise = /^npm (warn|notice)\b/;
|
|
47938
|
+
const lines = run.stderr.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !noise.test(l));
|
|
47939
|
+
const reason = run.error?.message ?? lines[lines.length - 1] ?? `exit ${String(run.status)} (no stderr)`;
|
|
47940
|
+
result.failed.push({ spec: target.spec, reason });
|
|
47941
|
+
console.log(` MISS ${target.spec} (${secs}s) \u2014 fetched at runtime instead: ${reason}`);
|
|
47942
|
+
}
|
|
47687
47943
|
}
|
|
47688
|
-
}
|
|
47944
|
+
};
|
|
47945
|
+
await Promise.all(Array.from({ length: Math.max(1, concurrency) }, worker));
|
|
47689
47946
|
console.log(
|
|
47690
47947
|
`[codeam mcp-warm] warmed=${result.warmed.length} failed=${result.failed.length} skipped=${result.skipped.length}`
|
|
47691
47948
|
);
|
|
@@ -47693,12 +47950,13 @@ async function mcpWarm(argv = []) {
|
|
|
47693
47950
|
"mcpWarm",
|
|
47694
47951
|
`warmed=${result.warmed.length} failed=${result.failed.length} skipped=${result.skipped.length}`
|
|
47695
47952
|
);
|
|
47953
|
+
return result;
|
|
47696
47954
|
}
|
|
47697
47955
|
|
|
47698
47956
|
// src/commands/version.ts
|
|
47699
47957
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
47700
47958
|
function version2() {
|
|
47701
|
-
const v = true ? "2.
|
|
47959
|
+
const v = true ? "2.74.1" : "unknown";
|
|
47702
47960
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
47703
47961
|
}
|
|
47704
47962
|
|
|
@@ -47900,7 +48158,11 @@ async function main() {
|
|
|
47900
48158
|
return mcpRun(args);
|
|
47901
48159
|
}
|
|
47902
48160
|
if (command === "mcp-warm") {
|
|
47903
|
-
|
|
48161
|
+
await mcpWarm(args);
|
|
48162
|
+
return;
|
|
48163
|
+
}
|
|
48164
|
+
if (command === "mcp-router") {
|
|
48165
|
+
return mcpRouter();
|
|
47904
48166
|
}
|
|
47905
48167
|
if (command === "deploy") {
|
|
47906
48168
|
if (args[0] === "ls" || args[0] === "list") return deployList();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.74.1",
|
|
4
4
|
"description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|