@haven_ai/connect 0.1.2-alpha → 0.1.3-alpha
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 +1 -1
- package/dist/cli.cjs +560 -55
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +563 -58
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +560 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +562 -57
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -3,10 +3,10 @@ import { Wallet } from 'ethers';
|
|
|
3
3
|
import { mkdir, rm, writeFile, access, chmod, readFile } from 'fs/promises';
|
|
4
4
|
import { homedir, platform } from 'os';
|
|
5
5
|
import { join, resolve, dirname } from 'path';
|
|
6
|
-
import { execFile } from 'child_process';
|
|
6
|
+
import { execFile, spawn } from 'child_process';
|
|
7
7
|
import { promisify } from 'util';
|
|
8
8
|
import { MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient, registeredToolNames } from '@haven_ai/mcp';
|
|
9
|
-
import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas
|
|
9
|
+
import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
|
|
10
10
|
|
|
11
11
|
// src/api.ts
|
|
12
12
|
function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
@@ -201,9 +201,50 @@ async function restrictPermissions(path, mode, warn) {
|
|
|
201
201
|
);
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
+
var MCP_RUNTIME_MANIFEST = {
|
|
205
|
+
mcpPackage: "@haven_ai/mcp",
|
|
206
|
+
mcpVersion: MCP_VERSION,
|
|
207
|
+
sdkPackage: "@haven_ai/sdk",
|
|
208
|
+
sdkVersion: "0.1.6",
|
|
209
|
+
signerPackage: "@haven_ai/signer",
|
|
210
|
+
signerVersion: "0.1.0-alpha",
|
|
211
|
+
minimumNodeVersion: "20.0.0",
|
|
212
|
+
supportedClients: ["codex-cli", "codex-desktop", "claude-code"],
|
|
213
|
+
requiredTools: [
|
|
214
|
+
"haven_quote_x402",
|
|
215
|
+
"haven_pay_x402_quote",
|
|
216
|
+
"haven_resume_x402_payment",
|
|
217
|
+
"haven_quote_mpp",
|
|
218
|
+
"haven_pay_mpp_challenge",
|
|
219
|
+
"haven_resume_mpp_payment",
|
|
220
|
+
"haven_get_payment_status",
|
|
221
|
+
"haven_get_resume_state",
|
|
222
|
+
"haven_get_agent",
|
|
223
|
+
"haven_get_allowances",
|
|
224
|
+
"haven_list_receipts"
|
|
225
|
+
]
|
|
226
|
+
};
|
|
227
|
+
function mcpPackageSpec() {
|
|
228
|
+
return `${MCP_RUNTIME_MANIFEST.mcpPackage}@${MCP_RUNTIME_MANIFEST.mcpVersion}`;
|
|
229
|
+
}
|
|
230
|
+
function sdkPackageSpec() {
|
|
231
|
+
return `${MCP_RUNTIME_MANIFEST.sdkPackage}@${MCP_RUNTIME_MANIFEST.sdkVersion}`;
|
|
232
|
+
}
|
|
233
|
+
function signerPackageSpec() {
|
|
234
|
+
return `${MCP_RUNTIME_MANIFEST.signerPackage}@${MCP_RUNTIME_MANIFEST.signerVersion}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/config-writers.ts
|
|
238
|
+
var InvalidCodexTomlError = class extends Error {
|
|
239
|
+
constructor(message) {
|
|
240
|
+
super(message);
|
|
241
|
+
this.name = "InvalidCodexTomlError";
|
|
242
|
+
}
|
|
243
|
+
};
|
|
204
244
|
async function writeRuntimeConfig(input) {
|
|
205
245
|
switch (input.runtime) {
|
|
206
246
|
case "codex-cli":
|
|
247
|
+
case "codex-desktop":
|
|
207
248
|
return writeCodexConfig(input);
|
|
208
249
|
case "cursor":
|
|
209
250
|
return writeJsonRuntimeConfig(input, cursorConfigPath(input.homeDir), "mcpServers");
|
|
@@ -258,18 +299,21 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
258
299
|
return `${JSON.stringify(config, null, 2)}
|
|
259
300
|
`;
|
|
260
301
|
}
|
|
261
|
-
function mergeCodexToml(existingToml,
|
|
262
|
-
let next =
|
|
302
|
+
function mergeCodexToml(existingToml, localMcpCommand) {
|
|
303
|
+
let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
263
304
|
next = next.trimEnd();
|
|
264
305
|
const block = [
|
|
265
306
|
"[mcp_servers.haven]",
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
`command = ${tomlString(localMcpCommand)}`,
|
|
308
|
+
"args = []",
|
|
309
|
+
"startup_timeout_sec = 120"
|
|
268
310
|
].join("\n");
|
|
269
|
-
|
|
311
|
+
validateCodexToml(block, "Generated Codex Haven config");
|
|
312
|
+
const merged = `${next ? `${next}
|
|
270
313
|
|
|
271
314
|
` : ""}${block}
|
|
272
315
|
`;
|
|
316
|
+
return merged;
|
|
273
317
|
}
|
|
274
318
|
async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
275
319
|
try {
|
|
@@ -309,32 +353,36 @@ async function writeCodexConfig(input) {
|
|
|
309
353
|
const target = codexConfigPath(input.homeDir);
|
|
310
354
|
try {
|
|
311
355
|
const existing = await readOptional(target);
|
|
312
|
-
|
|
356
|
+
if (!input.localMcpCommand) {
|
|
357
|
+
throw new Error("local MCP wrapper command is required");
|
|
358
|
+
}
|
|
359
|
+
const merged = mergeCodexToml(existing ?? "", input.localMcpCommand);
|
|
313
360
|
await writeOwnerOnlyText(target, merged);
|
|
314
361
|
return {
|
|
315
362
|
hostedConfigured: false,
|
|
316
363
|
signerConfigured: true,
|
|
317
364
|
localMcpConfigured: true,
|
|
318
365
|
runtimeMcpMode: "local_stdio",
|
|
319
|
-
target:
|
|
366
|
+
target: configTargetLabel(input.runtime),
|
|
320
367
|
changed: existing !== merged,
|
|
321
368
|
restartRequired: true,
|
|
322
369
|
messages: [
|
|
323
|
-
|
|
370
|
+
`Updated local Haven MCP entry in ${configTargetLabel(input.runtime)}.`,
|
|
324
371
|
"After Haven approval, restart Codex normally so it can load Haven tools."
|
|
325
372
|
]
|
|
326
373
|
};
|
|
327
374
|
} catch (err) {
|
|
375
|
+
const invalidToml = err instanceof InvalidCodexTomlError;
|
|
328
376
|
return {
|
|
329
377
|
hostedConfigured: false,
|
|
330
378
|
signerConfigured: false,
|
|
331
379
|
localMcpConfigured: false,
|
|
332
380
|
runtimeMcpMode: "local_stdio",
|
|
333
|
-
target:
|
|
381
|
+
target: configTargetLabel(input.runtime),
|
|
334
382
|
changed: false,
|
|
335
383
|
restartRequired: true,
|
|
336
|
-
messages: [`Could not update
|
|
337
|
-
errorCode: "runtime_config_write_failed"
|
|
384
|
+
messages: [`Could not update ${configTargetLabel(input.runtime)}: ${err instanceof Error ? err.message : String(err)}`],
|
|
385
|
+
errorCode: invalidToml ? "codex_config_invalid" : "runtime_config_write_failed"
|
|
338
386
|
};
|
|
339
387
|
}
|
|
340
388
|
}
|
|
@@ -358,24 +406,191 @@ function parseJsonObject(value) {
|
|
|
358
406
|
}
|
|
359
407
|
return parsed;
|
|
360
408
|
}
|
|
361
|
-
function
|
|
409
|
+
function removeTomlTableTree(toml, table) {
|
|
362
410
|
const lines = toml.split(/\r?\n/);
|
|
363
|
-
const start = `[${table}]`;
|
|
364
411
|
const kept = [];
|
|
365
412
|
let skipping = false;
|
|
366
413
|
for (const line of lines) {
|
|
367
414
|
const trimmed = line.trim();
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (skipping && trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
373
|
-
skipping = false;
|
|
415
|
+
const tableName = tomlTableName(trimmed);
|
|
416
|
+
if (tableName) {
|
|
417
|
+
skipping = tableName === table || tableName.startsWith(`${table}.`);
|
|
418
|
+
if (skipping) continue;
|
|
374
419
|
}
|
|
375
420
|
if (!skipping) kept.push(line);
|
|
376
421
|
}
|
|
377
422
|
return kept.join("\n");
|
|
378
423
|
}
|
|
424
|
+
function tomlTableName(line) {
|
|
425
|
+
if (line.startsWith("[[") && line.endsWith("]]")) return line.slice(2, -2).trim();
|
|
426
|
+
if (line.startsWith("[") && line.endsWith("]")) return line.slice(1, -1).trim();
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
function validateCodexToml(toml, label = "Codex config") {
|
|
430
|
+
const lines = toml.split(/\r?\n/);
|
|
431
|
+
let pendingValue = null;
|
|
432
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
433
|
+
const raw = lines[index];
|
|
434
|
+
const line = stripTomlComment(raw).trim();
|
|
435
|
+
if (!line) continue;
|
|
436
|
+
if (pendingValue) {
|
|
437
|
+
pendingValue.value = `${pendingValue.value}
|
|
438
|
+
${line}`;
|
|
439
|
+
if (hasBalancedTomlContainers(pendingValue.value)) {
|
|
440
|
+
if (!isTomlValue(pendingValue.value)) {
|
|
441
|
+
throw new InvalidCodexTomlError(`${label} has invalid TOML near line ${pendingValue.line}.`);
|
|
442
|
+
}
|
|
443
|
+
pendingValue = null;
|
|
444
|
+
}
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (isTomlTable(line)) continue;
|
|
448
|
+
const equalsIndex = line.indexOf("=");
|
|
449
|
+
if (equalsIndex <= 0) {
|
|
450
|
+
throw new InvalidCodexTomlError(`${label} has invalid TOML near line ${index + 1}.`);
|
|
451
|
+
}
|
|
452
|
+
const key = line.slice(0, equalsIndex).trim();
|
|
453
|
+
const value = line.slice(equalsIndex + 1).trim();
|
|
454
|
+
if (!isTomlKey(key)) {
|
|
455
|
+
throw new InvalidCodexTomlError(`${label} has invalid TOML near line ${index + 1}.`);
|
|
456
|
+
}
|
|
457
|
+
if (startsTomlContainer(value) && !hasBalancedTomlContainers(value)) {
|
|
458
|
+
pendingValue = { value, line: index + 1 };
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (!isTomlValue(value)) {
|
|
462
|
+
throw new InvalidCodexTomlError(`${label} has invalid TOML near line ${index + 1}.`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (pendingValue) {
|
|
466
|
+
throw new InvalidCodexTomlError(`${label} has invalid TOML near line ${pendingValue.line}.`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function isTomlTable(line) {
|
|
470
|
+
const table = tomlTableName(line);
|
|
471
|
+
return Boolean(table && splitTomlDottedKey(table).every(isTomlKeyPart));
|
|
472
|
+
}
|
|
473
|
+
function isTomlKey(value) {
|
|
474
|
+
return splitTomlDottedKey(value).every(isTomlKeyPart);
|
|
475
|
+
}
|
|
476
|
+
function splitTomlDottedKey(value) {
|
|
477
|
+
const parts = [];
|
|
478
|
+
let current = "";
|
|
479
|
+
let quote = null;
|
|
480
|
+
let escaped = false;
|
|
481
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
482
|
+
const char = value[i];
|
|
483
|
+
if (quote) {
|
|
484
|
+
current += char;
|
|
485
|
+
if (quote === '"' && char === "\\" && !escaped) {
|
|
486
|
+
escaped = true;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (char === quote && !escaped) quote = null;
|
|
490
|
+
escaped = false;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (char === '"' || char === "'") {
|
|
494
|
+
quote = char;
|
|
495
|
+
current += char;
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (char === ".") {
|
|
499
|
+
parts.push(current.trim());
|
|
500
|
+
current = "";
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
current += char;
|
|
504
|
+
}
|
|
505
|
+
parts.push(current.trim());
|
|
506
|
+
return quote ? [] : parts;
|
|
507
|
+
}
|
|
508
|
+
function isTomlKeyPart(value) {
|
|
509
|
+
return isTomlBareKey(value) || isTomlQuotedString(value);
|
|
510
|
+
}
|
|
511
|
+
function isTomlBareKey(value) {
|
|
512
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
513
|
+
}
|
|
514
|
+
function isTomlValue(value) {
|
|
515
|
+
if (!value) return false;
|
|
516
|
+
if (isTomlQuotedString(value)) return true;
|
|
517
|
+
if (/^(true|false)$/i.test(value)) return true;
|
|
518
|
+
if (/^[+-]?(?:inf|nan)$/i.test(value)) return true;
|
|
519
|
+
if (/^[+-]?(?:0|[1-9][0-9_]*)(?:\.[0-9_]+)?(?:[eE][+-]?[0-9_]+)?$/.test(value)) return true;
|
|
520
|
+
if (/^\d{4}-\d{2}-\d{2}(?:[Tt ][0-9:.+-Zz]+)?$/.test(value)) return true;
|
|
521
|
+
if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}")) {
|
|
522
|
+
return hasBalancedTomlContainers(value);
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
function startsTomlContainer(value) {
|
|
527
|
+
return value.startsWith("[") || value.startsWith("{");
|
|
528
|
+
}
|
|
529
|
+
function isTomlQuotedString(value) {
|
|
530
|
+
if (value.startsWith('"""') || value.startsWith("'''")) {
|
|
531
|
+
const marker = value.slice(0, 3);
|
|
532
|
+
return value.length >= 6 && value.endsWith(marker);
|
|
533
|
+
}
|
|
534
|
+
if ((!value.startsWith('"') || !value.endsWith('"')) && (!value.startsWith("'") || !value.endsWith("'"))) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
return hasBalancedTomlContainers(value);
|
|
538
|
+
}
|
|
539
|
+
function stripTomlComment(value) {
|
|
540
|
+
let quote = null;
|
|
541
|
+
let escaped = false;
|
|
542
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
543
|
+
const char = value[i];
|
|
544
|
+
if (quote) {
|
|
545
|
+
if (quote === '"' && char === "\\" && !escaped) {
|
|
546
|
+
escaped = true;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (char === quote && !escaped) quote = null;
|
|
550
|
+
escaped = false;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (char === '"' || char === "'") {
|
|
554
|
+
quote = char;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (char === "#") return value.slice(0, i);
|
|
558
|
+
}
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
function hasBalancedTomlContainers(value) {
|
|
562
|
+
const stack = [];
|
|
563
|
+
let quote = null;
|
|
564
|
+
let escaped = false;
|
|
565
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
566
|
+
const char = value[i];
|
|
567
|
+
if (quote) {
|
|
568
|
+
if (quote === '"' && char === "\\" && !escaped) {
|
|
569
|
+
escaped = true;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (char === quote && !escaped) quote = null;
|
|
573
|
+
escaped = false;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (char === '"' || char === "'") {
|
|
577
|
+
quote = char;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (char === "[" || char === "{") {
|
|
581
|
+
stack.push(char);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (char === "]") {
|
|
585
|
+
if (stack.pop() !== "[") return false;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (char === "}") {
|
|
589
|
+
if (stack.pop() !== "{") return false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return stack.length === 0 && quote === null;
|
|
593
|
+
}
|
|
379
594
|
function tomlString(value) {
|
|
380
595
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
381
596
|
}
|
|
@@ -403,6 +618,10 @@ function claudeDesktopConfigPath(homeDir = homedir()) {
|
|
|
403
618
|
}
|
|
404
619
|
function configTargetLabel(runtime) {
|
|
405
620
|
switch (runtime) {
|
|
621
|
+
case "codex-cli":
|
|
622
|
+
return "Codex CLI config";
|
|
623
|
+
case "codex-desktop":
|
|
624
|
+
return "Codex Desktop config";
|
|
406
625
|
case "cursor":
|
|
407
626
|
return "Cursor MCP config";
|
|
408
627
|
case "vscode":
|
|
@@ -414,10 +633,7 @@ function configTargetLabel(runtime) {
|
|
|
414
633
|
}
|
|
415
634
|
}
|
|
416
635
|
function signerPackageName() {
|
|
417
|
-
return
|
|
418
|
-
}
|
|
419
|
-
function localMcpPackageName() {
|
|
420
|
-
return `@haven_ai/mcp@${MCP_VERSION}`;
|
|
636
|
+
return signerPackageSpec();
|
|
421
637
|
}
|
|
422
638
|
async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
|
|
423
639
|
try {
|
|
@@ -531,6 +747,72 @@ async function probeLocalSignerCredential(signerPath) {
|
|
|
531
747
|
return false;
|
|
532
748
|
}
|
|
533
749
|
}
|
|
750
|
+
async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4) {
|
|
751
|
+
return new Promise((resolve6) => {
|
|
752
|
+
const child = spawn(command, args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
753
|
+
let stdout = "";
|
|
754
|
+
let settled = false;
|
|
755
|
+
let sawInitialize = false;
|
|
756
|
+
const finish = (result) => {
|
|
757
|
+
if (settled) return;
|
|
758
|
+
settled = true;
|
|
759
|
+
clearTimeout(timeout);
|
|
760
|
+
child.kill();
|
|
761
|
+
resolve6(result);
|
|
762
|
+
};
|
|
763
|
+
const timeout = setTimeout(() => finish({ status: "timeout" }), timeoutMs);
|
|
764
|
+
child.on("error", () => finish({ status: "process_error" }));
|
|
765
|
+
child.on("exit", (code) => {
|
|
766
|
+
if (!settled && code !== 0) finish({ status: "process_error" });
|
|
767
|
+
});
|
|
768
|
+
child.stdout.on("data", (chunk) => {
|
|
769
|
+
stdout += chunk.toString("utf8");
|
|
770
|
+
const lines = stdout.split(/\r?\n/);
|
|
771
|
+
stdout = lines.pop() ?? "";
|
|
772
|
+
for (const line of lines) {
|
|
773
|
+
const trimmed = line.trim();
|
|
774
|
+
if (!trimmed) continue;
|
|
775
|
+
let payload;
|
|
776
|
+
try {
|
|
777
|
+
payload = JSON.parse(trimmed);
|
|
778
|
+
} catch {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (payload.error) {
|
|
782
|
+
finish({ status: "bad_response" });
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (payload.id === 1 && !sawInitialize) {
|
|
786
|
+
sawInitialize = true;
|
|
787
|
+
writeJsonRpc(child, { jsonrpc: "2.0", method: "notifications/initialized", params: {} });
|
|
788
|
+
writeJsonRpc(child, { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (payload.id === 2) {
|
|
792
|
+
const tools = payload.result?.tools;
|
|
793
|
+
const toolNames = Array.isArray(tools) ? tools.map((tool) => tool && typeof tool === "object" && "name" in tool ? tool.name : void 0).filter((name) => typeof name === "string") : [];
|
|
794
|
+
const missing = requiredTools.filter((name) => !toolNames.includes(name));
|
|
795
|
+
finish({ status: missing.length === 0 ? "ok" : "missing_tools", toolNames });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
writeJsonRpc(child, {
|
|
801
|
+
jsonrpc: "2.0",
|
|
802
|
+
id: 1,
|
|
803
|
+
method: "initialize",
|
|
804
|
+
params: {
|
|
805
|
+
protocolVersion: "2025-06-18",
|
|
806
|
+
capabilities: {},
|
|
807
|
+
clientInfo: { name: "haven-connect-probe", version: "0.0.0" }
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
function writeJsonRpc(child, payload) {
|
|
813
|
+
child.stdin?.write(`${JSON.stringify(payload)}
|
|
814
|
+
`);
|
|
815
|
+
}
|
|
534
816
|
function parseJsonRpcPayload(raw) {
|
|
535
817
|
const trimmed = raw.trim();
|
|
536
818
|
if (!trimmed) return null;
|
|
@@ -559,6 +841,161 @@ async function fetchWithTimeout(fetchImpl, url, init) {
|
|
|
559
841
|
clearTimeout(timeout);
|
|
560
842
|
}
|
|
561
843
|
}
|
|
844
|
+
var execFileAsync = promisify(execFile);
|
|
845
|
+
var UnsupportedNodeVersionError = class extends Error {
|
|
846
|
+
code = "local_mcp_unsupported_node_version";
|
|
847
|
+
constructor(nodeVersion, minimumNodeVersion) {
|
|
848
|
+
super(`Node.js ${nodeVersion} is not supported. Haven local MCP requires Node.js >=${minimumNodeVersion}.`);
|
|
849
|
+
this.name = "UnsupportedNodeVersionError";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
async function prepareLocalMcpRuntime(input, deps = {}) {
|
|
853
|
+
assertSupportedNodeVersion(input.nodeVersion);
|
|
854
|
+
const homeDir = input.homeDir ?? homedir();
|
|
855
|
+
const runtimeDirectory = resolve(homeDir, ".haven", "mcp-runtime", MCP_RUNTIME_MANIFEST.mcpVersion);
|
|
856
|
+
const npmCacheDirectory = resolve(homeDir, ".haven", "npm-cache");
|
|
857
|
+
const cliPath = join(runtimeDirectory, "node_modules", "@haven_ai", "mcp", "dist", "cli.js");
|
|
858
|
+
const messages = [];
|
|
859
|
+
await mkdir(runtimeDirectory, { recursive: true, mode: 448 });
|
|
860
|
+
await chmod(runtimeDirectory, 448).catch(() => void 0);
|
|
861
|
+
await mkdir(npmCacheDirectory, { recursive: true, mode: 448 });
|
|
862
|
+
await chmod(npmCacheDirectory, 448).catch(() => void 0);
|
|
863
|
+
if (await installedRuntimeMatches(runtimeDirectory, cliPath)) {
|
|
864
|
+
messages.push(`Using existing local Haven MCP runtime ${mcpPackageSpec()}.`);
|
|
865
|
+
} else {
|
|
866
|
+
await installRuntimePackages(runtimeDirectory, npmCacheDirectory, deps.runCommand);
|
|
867
|
+
messages.push(`Installed local Haven MCP runtime ${mcpPackageSpec()}.`);
|
|
868
|
+
}
|
|
869
|
+
await assertFileExists(cliPath, "local Haven MCP CLI");
|
|
870
|
+
const wrapperPath = join(input.credentialDirectory, "bin", "haven-mcp");
|
|
871
|
+
await writeWrapper({
|
|
872
|
+
wrapperPath,
|
|
873
|
+
cliPath,
|
|
874
|
+
identityPath: input.identityPath,
|
|
875
|
+
signerPath: input.signerPath
|
|
876
|
+
});
|
|
877
|
+
await writeRuntimeSidecar({
|
|
878
|
+
path: join(input.credentialDirectory, "mcp-runtime.json"),
|
|
879
|
+
wrapperPath,
|
|
880
|
+
runtimeDirectory,
|
|
881
|
+
npmCacheDirectory,
|
|
882
|
+
cliPath
|
|
883
|
+
});
|
|
884
|
+
messages.push(`Prepared stable local Haven MCP wrapper: ${wrapperPath}`);
|
|
885
|
+
return {
|
|
886
|
+
command: wrapperPath,
|
|
887
|
+
args: [],
|
|
888
|
+
wrapperPath,
|
|
889
|
+
runtimeDirectory,
|
|
890
|
+
npmCacheDirectory,
|
|
891
|
+
cliPath,
|
|
892
|
+
messages
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function assertSupportedNodeVersion(nodeVersion = process.versions.node, minimumNodeVersion = MCP_RUNTIME_MANIFEST.minimumNodeVersion) {
|
|
896
|
+
if (compareNodeVersions(nodeVersion, minimumNodeVersion) < 0) {
|
|
897
|
+
throw new UnsupportedNodeVersionError(nodeVersion, minimumNodeVersion);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function compareNodeVersions(left, right) {
|
|
901
|
+
const leftParts = parseNodeVersion(left);
|
|
902
|
+
const rightParts = parseNodeVersion(right);
|
|
903
|
+
for (let i = 0; i < 3; i += 1) {
|
|
904
|
+
if (leftParts[i] !== rightParts[i]) return leftParts[i] > rightParts[i] ? 1 : -1;
|
|
905
|
+
}
|
|
906
|
+
return 0;
|
|
907
|
+
}
|
|
908
|
+
function parseNodeVersion(value) {
|
|
909
|
+
const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
910
|
+
if (!match) return [0, 0, 0];
|
|
911
|
+
return [
|
|
912
|
+
Number(match[1] ?? 0),
|
|
913
|
+
Number(match[2] ?? 0),
|
|
914
|
+
Number(match[3] ?? 0)
|
|
915
|
+
];
|
|
916
|
+
}
|
|
917
|
+
async function installRuntimePackages(runtimeDirectory, npmCacheDirectory, runCommand) {
|
|
918
|
+
const args = [
|
|
919
|
+
"install",
|
|
920
|
+
"--prefix",
|
|
921
|
+
runtimeDirectory,
|
|
922
|
+
"--cache",
|
|
923
|
+
npmCacheDirectory,
|
|
924
|
+
"--no-audit",
|
|
925
|
+
"--no-fund",
|
|
926
|
+
"--omit=dev",
|
|
927
|
+
mcpPackageSpec(),
|
|
928
|
+
sdkPackageSpec()
|
|
929
|
+
];
|
|
930
|
+
try {
|
|
931
|
+
if (runCommand) await runCommand("npm", args);
|
|
932
|
+
else await execFileAsync("npm", args, { timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
933
|
+
} catch (err) {
|
|
934
|
+
throw new Error(`Could not install local Haven MCP runtime ${mcpPackageSpec()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
async function installedRuntimeMatches(runtimeDirectory, cliPath) {
|
|
938
|
+
try {
|
|
939
|
+
await assertFileExists(cliPath, "local Haven MCP CLI");
|
|
940
|
+
const [mcpPackage, sdkPackage] = await Promise.all([
|
|
941
|
+
readPackageJson(join(runtimeDirectory, "node_modules", "@haven_ai", "mcp", "package.json")),
|
|
942
|
+
readPackageJson(join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
|
|
943
|
+
]);
|
|
944
|
+
return mcpPackage.version === MCP_RUNTIME_MANIFEST.mcpVersion && sdkPackage.version === MCP_RUNTIME_MANIFEST.sdkVersion;
|
|
945
|
+
} catch {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async function readPackageJson(path) {
|
|
950
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
951
|
+
}
|
|
952
|
+
async function writeWrapper(input) {
|
|
953
|
+
await mkdir(dirname(input.wrapperPath), { recursive: true, mode: 448 });
|
|
954
|
+
await chmod(dirname(input.wrapperPath), 448).catch(() => void 0);
|
|
955
|
+
const source = [
|
|
956
|
+
"#!/usr/bin/env node",
|
|
957
|
+
"import { spawn } from 'node:child_process'",
|
|
958
|
+
"",
|
|
959
|
+
`const cliPath = ${JSON.stringify(input.cliPath)}`,
|
|
960
|
+
`const identityPath = ${JSON.stringify(input.identityPath)}`,
|
|
961
|
+
`const signerPath = ${JSON.stringify(input.signerPath)}`,
|
|
962
|
+
"",
|
|
963
|
+
"const child = spawn(process.execPath, [cliPath, '--identity', identityPath, '--signer', signerPath, ...process.argv.slice(2)], {",
|
|
964
|
+
" stdio: 'inherit',",
|
|
965
|
+
"})",
|
|
966
|
+
"",
|
|
967
|
+
"child.on('exit', (code, signal) => {",
|
|
968
|
+
" if (signal) process.kill(process.pid, signal)",
|
|
969
|
+
" else process.exit(code ?? 1)",
|
|
970
|
+
"})",
|
|
971
|
+
""
|
|
972
|
+
].join("\n");
|
|
973
|
+
await writeFile(input.wrapperPath, source, { mode: 448 });
|
|
974
|
+
await chmod(input.wrapperPath, 448).catch(() => void 0);
|
|
975
|
+
}
|
|
976
|
+
async function writeRuntimeSidecar(input) {
|
|
977
|
+
const value = {
|
|
978
|
+
mcp_package: MCP_RUNTIME_MANIFEST.mcpPackage,
|
|
979
|
+
mcp_version: MCP_RUNTIME_MANIFEST.mcpVersion,
|
|
980
|
+
sdk_package: MCP_RUNTIME_MANIFEST.sdkPackage,
|
|
981
|
+
sdk_version: MCP_RUNTIME_MANIFEST.sdkVersion,
|
|
982
|
+
minimum_node_version: MCP_RUNTIME_MANIFEST.minimumNodeVersion,
|
|
983
|
+
wrapper_path: input.wrapperPath,
|
|
984
|
+
runtime_directory: input.runtimeDirectory,
|
|
985
|
+
npm_cache_directory: input.npmCacheDirectory,
|
|
986
|
+
cli_path: input.cliPath
|
|
987
|
+
};
|
|
988
|
+
await writeFile(input.path, `${JSON.stringify(value, null, 2)}
|
|
989
|
+
`, { mode: 384 });
|
|
990
|
+
await chmod(input.path, 384).catch(() => void 0);
|
|
991
|
+
}
|
|
992
|
+
async function assertFileExists(path, label) {
|
|
993
|
+
try {
|
|
994
|
+
await access(path);
|
|
995
|
+
} catch {
|
|
996
|
+
throw new Error(`Missing ${label}: ${path}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
562
999
|
|
|
563
1000
|
// src/runtime-registry.ts
|
|
564
1001
|
var RUNTIME_PROFILES = {
|
|
@@ -574,6 +1011,12 @@ var RUNTIME_PROFILES = {
|
|
|
574
1011
|
restartMode: "restart-session",
|
|
575
1012
|
canWriteRuntimeConfig: true
|
|
576
1013
|
},
|
|
1014
|
+
"codex-desktop": {
|
|
1015
|
+
id: "codex-desktop",
|
|
1016
|
+
label: "Codex Desktop",
|
|
1017
|
+
restartMode: "restart-session",
|
|
1018
|
+
canWriteRuntimeConfig: true
|
|
1019
|
+
},
|
|
577
1020
|
cursor: {
|
|
578
1021
|
id: "cursor",
|
|
579
1022
|
label: "Cursor",
|
|
@@ -608,6 +1051,12 @@ var RUNTIME_ALIASES = {
|
|
|
608
1051
|
"codex-cli": "codex-cli",
|
|
609
1052
|
codexcli: "codex-cli",
|
|
610
1053
|
"codex_cli": "codex-cli",
|
|
1054
|
+
"codex-desktop": "codex-desktop",
|
|
1055
|
+
"codex_desktop": "codex-desktop",
|
|
1056
|
+
codexdesktop: "codex-desktop",
|
|
1057
|
+
"codex-app": "codex-desktop",
|
|
1058
|
+
"codex_app": "codex-desktop",
|
|
1059
|
+
codexapp: "codex-desktop",
|
|
611
1060
|
cursor: "cursor",
|
|
612
1061
|
vscode: "vscode",
|
|
613
1062
|
"vs-code": "vscode",
|
|
@@ -714,7 +1163,7 @@ function writeLogChunk2(log, chunk) {
|
|
|
714
1163
|
}
|
|
715
1164
|
|
|
716
1165
|
// src/runtime-install.ts
|
|
717
|
-
var
|
|
1166
|
+
var execFileAsync2 = promisify(execFile);
|
|
718
1167
|
async function installRuntime(input, deps = {}) {
|
|
719
1168
|
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
720
1169
|
const profile = runtimeProfile(runtime, deps.env);
|
|
@@ -744,31 +1193,66 @@ async function installRuntime(input, deps = {}) {
|
|
|
744
1193
|
]
|
|
745
1194
|
};
|
|
746
1195
|
}
|
|
747
|
-
|
|
1196
|
+
let localRuntimeInstall;
|
|
1197
|
+
let localRuntimeError;
|
|
1198
|
+
if (localRuntime) {
|
|
1199
|
+
try {
|
|
1200
|
+
localRuntimeInstall = await prepareRuntimeForLocalMcp(input, deps);
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
localRuntimeError = err;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
if (localRuntimeError) {
|
|
1206
|
+
const errorCode2 = localRuntimePrepareErrorCode(localRuntimeError);
|
|
1207
|
+
return {
|
|
1208
|
+
runtime,
|
|
1209
|
+
runtimeMcpMode: "local_stdio",
|
|
1210
|
+
hostedMcpConfigured: false,
|
|
1211
|
+
localSignerConfigured: false,
|
|
1212
|
+
localMcpConfigured: false,
|
|
1213
|
+
probeResult: errorCode2 === "local_mcp_unsupported_node_version" ? "local_stdio_mcp_unsupported_node_version" : "local_stdio_mcp_runtime_install_failed",
|
|
1214
|
+
restartRequired: true,
|
|
1215
|
+
nextUserAction: nextAction(runtime, profile.restartMode, errorCode2),
|
|
1216
|
+
errorCode: errorCode2,
|
|
1217
|
+
configTarget: profile.label,
|
|
1218
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
1219
|
+
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
1220
|
+
activationCommand: void 0,
|
|
1221
|
+
messages: [
|
|
1222
|
+
...consentMessages,
|
|
1223
|
+
`Could not prepare local Haven MCP runtime: ${localRuntimeError instanceof Error ? localRuntimeError.message : String(localRuntimeError)}`
|
|
1224
|
+
]
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
const configResult = runtime === "claude-code" ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await writeRuntimeConfig({
|
|
748
1228
|
runtime,
|
|
749
1229
|
hostedMcpUrl: input.hostedMcpUrl,
|
|
750
1230
|
apiKey: input.apiKey,
|
|
751
1231
|
identityPath: input.identityPath,
|
|
752
1232
|
signerPath: input.signerPath,
|
|
753
1233
|
credentialDirectory: input.credentialDirectory,
|
|
1234
|
+
localMcpCommand: localRuntimeInstall?.command,
|
|
754
1235
|
homeDir: deps.homeDir
|
|
755
1236
|
});
|
|
756
|
-
const
|
|
1237
|
+
const localProbePromise = configResult.runtimeMcpMode === "local_stdio" && localRuntimeInstall ? runLocalMcpProbe(localRuntimeInstall, deps) : Promise.resolve(void 0);
|
|
1238
|
+
const [hostedProbe, signerCredentialReady, localMcpProbe] = await Promise.all([
|
|
757
1239
|
configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
|
|
758
|
-
probeLocalSignerCredential(input.signerPath)
|
|
1240
|
+
probeLocalSignerCredential(input.signerPath),
|
|
1241
|
+
localProbePromise
|
|
759
1242
|
]);
|
|
760
1243
|
const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
|
|
761
|
-
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged);
|
|
1244
|
+
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged) && localMcpProbe?.status === "ok";
|
|
762
1245
|
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
763
1246
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
764
|
-
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ?
|
|
1247
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1248
|
+
const localProbeMessages = localMcpProbe && localMcpProbe.status !== "ok" ? [`Local Haven MCP handshake failed: ${localMcpProbe.status}.`] : localMcpProbe?.status === "ok" ? ["Verified local Haven MCP tools with a stdio handshake."] : [];
|
|
765
1249
|
return {
|
|
766
1250
|
runtime,
|
|
767
1251
|
runtimeMcpMode: configResult.runtimeMcpMode,
|
|
768
1252
|
hostedMcpConfigured: hostedOk,
|
|
769
1253
|
localSignerConfigured: signerOk,
|
|
770
1254
|
localMcpConfigured: localMcpOk,
|
|
771
|
-
probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
|
|
1255
|
+
probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk, localMcpProbe?.status),
|
|
772
1256
|
restartRequired,
|
|
773
1257
|
nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
|
|
774
1258
|
errorCode,
|
|
@@ -776,7 +1260,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
776
1260
|
signerAcknowledged: signerConsent?.acknowledged,
|
|
777
1261
|
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
778
1262
|
activationCommand: configResult.activationCommand,
|
|
779
|
-
messages: [...consentMessages, ...configResult.messages]
|
|
1263
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages]
|
|
780
1264
|
};
|
|
781
1265
|
}
|
|
782
1266
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -786,23 +1270,21 @@ function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
|
786
1270
|
restartRequired: restartRequiredForRuntime(runtime, env)
|
|
787
1271
|
};
|
|
788
1272
|
}
|
|
789
|
-
async function configureClaudeCode(
|
|
1273
|
+
async function configureClaudeCode(deps, localMcpCommand) {
|
|
790
1274
|
const runCommand = deps.runCommand ?? defaultRunCommand;
|
|
1275
|
+
const serverJson = JSON.stringify({
|
|
1276
|
+
type: "stdio",
|
|
1277
|
+
command: localMcpCommand,
|
|
1278
|
+
args: [],
|
|
1279
|
+
env: {}
|
|
1280
|
+
});
|
|
791
1281
|
try {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
"add",
|
|
795
|
-
|
|
796
|
-
"--",
|
|
797
|
-
"npx",
|
|
798
|
-
"-y",
|
|
799
|
-
localMcpPackageName2(),
|
|
800
|
-
"--identity",
|
|
801
|
-
input.identityPath,
|
|
802
|
-
"--signer",
|
|
803
|
-
input.signerPath
|
|
804
|
-
]);
|
|
1282
|
+
if (!localMcpCommand) throw new Error("local MCP wrapper command is required");
|
|
1283
|
+
await runCommand("claude", ["mcp", "add-json", "haven", serverJson, "--scope", "user"]).catch(async () => {
|
|
1284
|
+
await runCommand("claude", ["mcp", "add", "haven", "--scope", "user", "--", localMcpCommand]);
|
|
1285
|
+
});
|
|
805
1286
|
await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
|
|
1287
|
+
const verified = await runCommand("claude", ["mcp", "get", "haven"]).then(() => true).catch(() => false);
|
|
806
1288
|
return {
|
|
807
1289
|
hostedConfigured: false,
|
|
808
1290
|
signerConfigured: true,
|
|
@@ -813,6 +1295,7 @@ async function configureClaudeCode(input, deps) {
|
|
|
813
1295
|
restartRequired: true,
|
|
814
1296
|
messages: [
|
|
815
1297
|
"Updated local Haven MCP entry with Claude Code.",
|
|
1298
|
+
...verified ? ["Verified Claude Code MCP entry."] : [],
|
|
816
1299
|
"After Haven approval, restart Claude Code normally so it can load Haven tools."
|
|
817
1300
|
]
|
|
818
1301
|
};
|
|
@@ -834,11 +1317,12 @@ async function configureClaudeCode(input, deps) {
|
|
|
834
1317
|
}
|
|
835
1318
|
}
|
|
836
1319
|
async function defaultRunCommand(command, args) {
|
|
837
|
-
await
|
|
1320
|
+
await execFileAsync2(command, args, { timeout: 1e4 });
|
|
838
1321
|
}
|
|
839
|
-
function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady) {
|
|
1322
|
+
function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady, localMcpProbeStatus) {
|
|
840
1323
|
if (mode === "local_stdio") {
|
|
841
|
-
|
|
1324
|
+
if (localMcpReady) return "local_stdio_mcp_ready";
|
|
1325
|
+
return localMcpProbeStatus ? `local_stdio_mcp_${localMcpProbeStatus}` : "local_stdio_mcp_unavailable";
|
|
842
1326
|
}
|
|
843
1327
|
const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
|
|
844
1328
|
const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
|
|
@@ -873,25 +1357,46 @@ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
|
873
1357
|
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
874
1358
|
return void 0;
|
|
875
1359
|
}
|
|
876
|
-
function
|
|
1360
|
+
function localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbeStatus) {
|
|
877
1361
|
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
878
1362
|
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
1363
|
+
if (localMcpProbeStatus && localMcpProbeStatus !== "ok") return `local_mcp_probe_${localMcpProbeStatus}`;
|
|
879
1364
|
return void 0;
|
|
880
1365
|
}
|
|
881
1366
|
function nextAction(runtime, restartMode, errorCode) {
|
|
882
1367
|
if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
|
|
883
1368
|
if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
|
|
884
|
-
if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
|
|
1369
|
+
if (runtime === "codex-cli" || runtime === "codex-desktop") return "return_to_haven_for_wallet_approval_then_restart_codex";
|
|
885
1370
|
if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
|
|
886
1371
|
if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
|
|
887
1372
|
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
888
1373
|
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
889
1374
|
}
|
|
890
|
-
function localMcpPackageName2() {
|
|
891
|
-
return `@haven_ai/mcp@${MCP_VERSION}`;
|
|
892
|
-
}
|
|
893
1375
|
function usesLocalMcp(runtime) {
|
|
894
|
-
return runtime === "codex-cli" || runtime === "claude-code";
|
|
1376
|
+
return runtime === "codex-cli" || runtime === "codex-desktop" || runtime === "claude-code";
|
|
1377
|
+
}
|
|
1378
|
+
async function prepareRuntimeForLocalMcp(input, deps) {
|
|
1379
|
+
const prepare = deps.prepareLocalMcpRuntime ?? ((runtimeInput) => prepareLocalMcpRuntime(runtimeInput, { runCommand: deps.runCommand }));
|
|
1380
|
+
return prepare({
|
|
1381
|
+
credentialDirectory: input.credentialDirectory,
|
|
1382
|
+
identityPath: input.identityPath,
|
|
1383
|
+
signerPath: input.signerPath,
|
|
1384
|
+
homeDir: deps.homeDir
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
async function runLocalMcpProbe(runtimeInstall, deps) {
|
|
1388
|
+
const probe = deps.probeLocalMcpTools ?? probeLocalMcpTools;
|
|
1389
|
+
try {
|
|
1390
|
+
return await probe(runtimeInstall.command, runtimeInstall.args, MCP_RUNTIME_MANIFEST.requiredTools);
|
|
1391
|
+
} catch {
|
|
1392
|
+
return { status: "process_error" };
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function localRuntimePrepareErrorCode(err) {
|
|
1396
|
+
if (err && typeof err === "object" && "code" in err && err.code === "local_mcp_unsupported_node_version") {
|
|
1397
|
+
return "local_mcp_unsupported_node_version";
|
|
1398
|
+
}
|
|
1399
|
+
return "local_mcp_runtime_install_failed";
|
|
895
1400
|
}
|
|
896
1401
|
|
|
897
1402
|
// src/runtime.ts
|
|
@@ -1090,7 +1595,7 @@ function helpText() {
|
|
|
1090
1595
|
"Options:",
|
|
1091
1596
|
" --setup <token> Short-lived setup token from Haven.",
|
|
1092
1597
|
" --api <url> Haven backend API URL. Defaults to HAVEN_API_URL or http://localhost:3001.",
|
|
1093
|
-
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
|
|
1598
|
+
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode, or claude-desktop.",
|
|
1094
1599
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
1095
1600
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1096
1601
|
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|