@haven_ai/connect 0.1.0-alpha → 0.1.2-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 CHANGED
@@ -8,7 +8,7 @@ public signing address, proof signature, and API-key hash. Haven never receives
8
8
  the private key or plaintext API key.
9
9
 
10
10
  ```sh
11
- npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code
11
+ npx -y @haven_ai/connect@0.1.2-alpha --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code
12
12
  ```
13
13
 
14
14
  The connector writes owner-only credential files outside the project by default:
@@ -21,3 +21,7 @@ the locally held signer key and the user-approved on-chain Haven wallet rules.
21
21
 
22
22
  Use `--credentials-dir <path>` to choose a different local credential directory.
23
23
  Do not point it at a project repository, shared folder, or cloud-synced folder.
24
+
25
+ Use `--ack-local-tools` with Haven-generated setup prompts. It prepares the
26
+ local Haven tools acknowledgement during setup so Codex and Claude Code can load
27
+ Haven after a normal restart.
package/dist/cli.cjs CHANGED
@@ -8,6 +8,8 @@ var os = require('os');
8
8
  var path = require('path');
9
9
  var child_process = require('child_process');
10
10
  var util = require('util');
11
+ var mcp = require('@haven_ai/mcp');
12
+ var signer = require('@haven_ai/signer');
11
13
 
12
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
15
 
@@ -50,9 +52,14 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
50
52
  body: JSON.stringify({
51
53
  runtime: input.runtime,
52
54
  connector_version: input.connectorVersion,
55
+ runtime_mcp_mode: input.runtimeMcpMode,
53
56
  hosted_mcp_configured: input.hostedMcpConfigured,
54
57
  local_signer_configured: input.localSignerConfigured,
58
+ local_mcp_configured: input.localMcpConfigured,
55
59
  credential_files_written: input.credentialFilesWritten,
60
+ signer_acknowledged: input.signerAcknowledged,
61
+ local_mcp_acknowledged: input.localMcpAcknowledged,
62
+ activation_command_available: input.activationCommandAvailable,
56
63
  probe_result: input.probeResult,
57
64
  restart_required: input.restartRequired,
58
65
  next_user_action: input.nextUserAction,
@@ -134,6 +141,7 @@ async function writeCredentialFiles(input) {
134
141
  signerPath,
135
142
  {
136
143
  delegate_key: input.delegateKey,
144
+ delegate_address: input.delegateAddress,
137
145
  agent_id: input.agentId,
138
146
  safe_address: input.safeAddress,
139
147
  chain_id: input.chainId,
@@ -153,6 +161,7 @@ async function writeCredentialFiles(input) {
153
161
  network: input.network,
154
162
  api_url: input.apiUrl,
155
163
  hosted_mcp_url: input.hostedMcpUrl,
164
+ agent_budget: input.agentBudget,
156
165
  note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
157
166
  },
158
167
  input.warn
@@ -213,6 +222,8 @@ async function writeRuntimeConfig(input) {
213
222
  return {
214
223
  hostedConfigured: false,
215
224
  signerConfigured: false,
225
+ localMcpConfigured: false,
226
+ runtimeMcpMode: "manual",
216
227
  target: "manual runtime setup",
217
228
  changed: false,
218
229
  restartRequired: true,
@@ -237,7 +248,7 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
237
248
  function buildSignerServer(signerPath, runtime) {
238
249
  const server = {
239
250
  command: "npx",
240
- args: ["-y", "@haven_ai/signer", "--credentials", signerPath]
251
+ args: ["-y", signerPackageName(), "--credentials", signerPath]
241
252
  };
242
253
  if (runtime === "vscode") return { type: "stdio", ...server };
243
254
  return server;
@@ -254,17 +265,13 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
254
265
  return `${JSON.stringify(config, null, 2)}
255
266
  `;
256
267
  }
257
- function mergeCodexToml(existingToml, hostedMcpUrl, signerPath) {
268
+ function mergeCodexToml(existingToml, identityPath, signerPath) {
258
269
  let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
259
270
  next = next.trimEnd();
260
271
  const block = [
261
272
  "[mcp_servers.haven]",
262
- `url = ${tomlString(hostedMcpUrl)}`,
263
- 'bearer_token_env_var = "HAVEN_TOKEN"',
264
- "",
265
- "[mcp_servers.haven_signer]",
266
273
  'command = "npx"',
267
- `args = ["-y", "@haven_ai/signer", "--credentials", ${tomlString(signerPath)}]`
274
+ `args = ["-y", ${tomlString(localMcpPackageName())}, "--identity", ${tomlString(identityPath)}, "--signer", ${tomlString(signerPath)}]`
268
275
  ].join("\n");
269
276
  return `${next ? `${next}
270
277
 
@@ -284,6 +291,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
284
291
  return {
285
292
  hostedConfigured: true,
286
293
  signerConfigured: true,
294
+ localMcpConfigured: false,
295
+ runtimeMcpMode: "hosted_plus_signer",
287
296
  target: configTargetLabel(input.runtime),
288
297
  changed: existing !== merged,
289
298
  restartRequired: input.runtime === "claude-desktop",
@@ -293,6 +302,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
293
302
  return {
294
303
  hostedConfigured: false,
295
304
  signerConfigured: false,
305
+ localMcpConfigured: false,
306
+ runtimeMcpMode: "hosted_plus_signer",
296
307
  target: configTargetLabel(input.runtime),
297
308
  changed: false,
298
309
  restartRequired: true,
@@ -303,29 +314,29 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
303
314
  }
304
315
  async function writeCodexConfig(input) {
305
316
  const target = codexConfigPath(input.homeDir);
306
- const envTarget = path.join(input.credentialDirectory, "identity.env");
307
317
  try {
308
318
  const existing = await readOptional(target);
309
- const merged = mergeCodexToml(existing ?? "", input.hostedMcpUrl, input.signerPath);
319
+ const merged = mergeCodexToml(existing ?? "", input.identityPath, input.signerPath);
310
320
  await writeOwnerOnlyText(target, merged);
311
- await writeOwnerOnlyText(envTarget, `HAVEN_TOKEN=${shellToken(input.apiKey)}
312
- `);
313
321
  return {
314
322
  hostedConfigured: false,
315
323
  signerConfigured: true,
324
+ localMcpConfigured: true,
325
+ runtimeMcpMode: "local_stdio",
316
326
  target: "Codex CLI config",
317
327
  changed: existing !== merged,
318
328
  restartRequired: true,
319
329
  messages: [
320
- "Updated Haven MCP entries in Codex CLI config.",
321
- "Wrote the hosted MCP token to a private env file. Launch Codex with that env file before using Haven tools."
322
- ],
323
- errorCode: "codex_env_activation_required"
330
+ "Updated local Haven MCP entry in Codex CLI config.",
331
+ "After Haven approval, restart Codex normally so it can load Haven tools."
332
+ ]
324
333
  };
325
334
  } catch (err) {
326
335
  return {
327
336
  hostedConfigured: false,
328
337
  signerConfigured: false,
338
+ localMcpConfigured: false,
339
+ runtimeMcpMode: "local_stdio",
329
340
  target: "Codex CLI config",
330
341
  changed: false,
331
342
  restartRequired: true,
@@ -375,9 +386,6 @@ function removeTomlTable(toml, table) {
375
386
  function tomlString(value) {
376
387
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
377
388
  }
378
- function shellToken(value) {
379
- return `'${value.replace(/'/g, `'\\''`)}'`;
380
- }
381
389
  function cursorConfigPath(homeDir = os.homedir()) {
382
390
  return path.resolve(homeDir, ".cursor", "mcp.json");
383
391
  }
@@ -412,6 +420,88 @@ function configTargetLabel(runtime) {
412
420
  return "runtime MCP config";
413
421
  }
414
422
  }
423
+ function signerPackageName() {
424
+ return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
425
+ }
426
+ function localMcpPackageName() {
427
+ return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
428
+ }
429
+ async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
430
+ try {
431
+ const input = await buildLocalMcpConsentInput(identityPath, signerPath);
432
+ const decision = await mcp.ensureConsent(input, {
433
+ credentialsPath: identityPath,
434
+ writeAck: true,
435
+ out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
436
+ });
437
+ return {
438
+ acknowledged: decision.ok,
439
+ hash: decision.hash,
440
+ reason: decision.reason
441
+ };
442
+ } catch (err) {
443
+ return {
444
+ acknowledged: false,
445
+ error: err instanceof Error ? err.message : String(err)
446
+ };
447
+ }
448
+ }
449
+ async function getLocalMcpConsentStatus(identityPath, signerPath) {
450
+ try {
451
+ const input = await buildLocalMcpConsentInput(identityPath, signerPath);
452
+ const hash = mcp.computeConsentHash(input);
453
+ const stored = await readLocalMcpAckFile(localMcpAckPath(identityPath));
454
+ if (stored === hash) {
455
+ return { acknowledged: true, hash, reason: "ack_file_match" };
456
+ }
457
+ return {
458
+ acknowledged: false,
459
+ hash,
460
+ reason: stored ? "ack_file_mismatch" : "ack_file_missing"
461
+ };
462
+ } catch (err) {
463
+ return {
464
+ acknowledged: false,
465
+ error: err instanceof Error ? err.message : String(err)
466
+ };
467
+ }
468
+ }
469
+ function localMcpAckPath(identityPath) {
470
+ return path.resolve(`${identityPath}.ack.json`);
471
+ }
472
+ async function buildLocalMcpConsentInput(identityPath, signerPath) {
473
+ const credentials = await mcp.loadCredentials({ identityPath, signerPath });
474
+ const unavailableDuringSetup = {
475
+ getAllowances: async () => {
476
+ throw new Error("Haven approval is not complete yet.");
477
+ }
478
+ };
479
+ return mcp.consentInputFromClient(
480
+ unavailableDuringSetup,
481
+ {
482
+ apiKey: credentials.apiKey,
483
+ apiUrl: credentials.apiUrl,
484
+ agentId: credentials.agentId,
485
+ safeAddress: credentials.safeAddress,
486
+ delegateAddress: credentials.delegateAddress,
487
+ chainId: credentials.chainId,
488
+ allowanceSummary: credentials.allowanceSummary
489
+ },
490
+ mcp.registeredToolNames()
491
+ );
492
+ }
493
+ async function readLocalMcpAckFile(path) {
494
+ try {
495
+ const parsed = JSON.parse(await promises.readFile(path, "utf8"));
496
+ return typeof parsed.ack === "string" ? parsed.ack : null;
497
+ } catch {
498
+ return null;
499
+ }
500
+ }
501
+ function writeLogChunk(log, chunk) {
502
+ const message = String(chunk).trimEnd();
503
+ if (message) log(message);
504
+ }
415
505
  async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
416
506
  let response;
417
507
  try {
@@ -560,51 +650,140 @@ function detectRuntime(env) {
560
650
  if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
561
651
  return null;
562
652
  }
653
+ async function acknowledgeLocalSignerConsent(signerPath, log) {
654
+ try {
655
+ const input = await buildSignerConsentInput(signerPath);
656
+ const decision = await signer.ensureSignerConsent(input, {
657
+ credentialsPath: signerPath,
658
+ writeAck: true,
659
+ out: log ? { write: (chunk) => writeLogChunk2(log, chunk) } : void 0
660
+ });
661
+ return {
662
+ acknowledged: decision.ok,
663
+ hash: decision.hash,
664
+ reason: decision.reason
665
+ };
666
+ } catch (err) {
667
+ return {
668
+ acknowledged: false,
669
+ error: err instanceof Error ? err.message : String(err)
670
+ };
671
+ }
672
+ }
673
+ async function getLocalSignerConsentStatus(signerPath) {
674
+ try {
675
+ const input = await buildSignerConsentInput(signerPath);
676
+ const hash = signer.computeSignerConsentHash(input);
677
+ const stored = await readSignerAckFile(signerAckPath(signerPath));
678
+ if (stored === hash) {
679
+ return { acknowledged: true, hash, reason: "ack_file_match" };
680
+ }
681
+ return {
682
+ acknowledged: false,
683
+ hash,
684
+ reason: stored ? "ack_file_mismatch" : "ack_file_missing"
685
+ };
686
+ } catch (err) {
687
+ return {
688
+ acknowledged: false,
689
+ error: err instanceof Error ? err.message : String(err)
690
+ };
691
+ }
692
+ }
693
+ function signerAckPath(signerPath) {
694
+ return path.resolve(`${signerPath}.signer-ack.json`);
695
+ }
696
+ async function buildSignerConsentInput(signerPath) {
697
+ const credentials = await signer.loadSignerCredentials(signerPath);
698
+ const signer$1 = signer.createEdgeSigner(credentials.delegateKey, {
699
+ x402BindingSigner: credentials.x402BindingSigner
700
+ });
701
+ return {
702
+ delegateAddress: signer$1.delegateAddress,
703
+ safeAddress: credentials.safeAddress,
704
+ agentId: credentials.agentId,
705
+ chainId: credentials.chainId,
706
+ network: credentials.network,
707
+ toolNames: Object.keys(signer.toolSchemas)
708
+ };
709
+ }
710
+ async function readSignerAckFile(path) {
711
+ try {
712
+ const parsed = JSON.parse(await promises.readFile(path, "utf8"));
713
+ return typeof parsed.ack === "string" ? parsed.ack : null;
714
+ } catch {
715
+ return null;
716
+ }
717
+ }
718
+ function writeLogChunk2(log, chunk) {
719
+ const message = String(chunk).trimEnd();
720
+ if (message) log(message);
721
+ }
563
722
 
564
723
  // src/runtime-install.ts
565
724
  var execFileAsync = util.promisify(child_process.execFile);
566
725
  async function installRuntime(input, deps = {}) {
567
726
  const runtime = normalizeRuntime(input.runtime, deps.env);
568
727
  const profile = runtimeProfile(runtime, deps.env);
728
+ const localRuntime = usesLocalMcp(runtime);
729
+ const consentMessages = [];
730
+ const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
731
+ const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
569
732
  if (runtime === "other") {
570
- const signerReady2 = await probeLocalSignerCredential(input.signerPath);
733
+ const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
734
+ const signerReady = signerCredentialReady2 && signerConsent?.acknowledged;
571
735
  return {
572
736
  runtime,
737
+ runtimeMcpMode: "manual",
573
738
  hostedMcpConfigured: false,
574
739
  localSignerConfigured: false,
575
- probeResult: signerReady2 ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
740
+ localMcpConfigured: false,
741
+ probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
576
742
  restartRequired: true,
577
743
  nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
578
744
  errorCode: "manual_runtime_setup_required",
579
745
  configTarget: "manual runtime setup",
580
- messages: ["Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."]
746
+ signerAcknowledged: signerConsent?.acknowledged,
747
+ localMcpAcknowledged: false,
748
+ messages: [
749
+ ...consentMessages,
750
+ "Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
751
+ ]
581
752
  };
582
753
  }
583
754
  const configResult = runtime === "claude-code" ? await configureClaudeCode(input, deps) : await writeRuntimeConfig({
584
755
  runtime,
585
756
  hostedMcpUrl: input.hostedMcpUrl,
586
757
  apiKey: input.apiKey,
758
+ identityPath: input.identityPath,
587
759
  signerPath: input.signerPath,
588
760
  credentialDirectory: input.credentialDirectory,
589
761
  homeDir: deps.homeDir
590
762
  });
591
- const [hostedProbe, signerReady] = await Promise.all([
763
+ const [hostedProbe, signerCredentialReady] = await Promise.all([
592
764
  configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
593
765
  probeLocalSignerCredential(input.signerPath)
594
766
  ]);
595
767
  const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
596
- const signerOk = configResult.signerConfigured && signerReady;
768
+ const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged);
769
+ const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
597
770
  const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
771
+ const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) : signerConsentErrorCode(signerCredentialReady, signerConsent));
598
772
  return {
599
773
  runtime,
774
+ runtimeMcpMode: configResult.runtimeMcpMode,
600
775
  hostedMcpConfigured: hostedOk,
601
776
  localSignerConfigured: signerOk,
602
- probeResult: buildProbeResult(configResult.hostedConfigured, hostedProbe.status, signerOk),
777
+ localMcpConfigured: localMcpOk,
778
+ probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
603
779
  restartRequired,
604
- nextUserAction: nextAction(profile.restartMode, configResult.errorCode),
605
- errorCode: configResult.errorCode,
780
+ nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
781
+ errorCode,
606
782
  configTarget: configResult.target,
607
- messages: configResult.messages
783
+ signerAcknowledged: signerConsent?.acknowledged,
784
+ localMcpAcknowledged: localMcpConsent?.acknowledged,
785
+ activationCommand: configResult.activationCommand,
786
+ messages: [...consentMessages, ...configResult.messages]
608
787
  };
609
788
  }
610
789
  function runtimeInstallCapabilities(runtime, env = process.env) {
@@ -620,39 +799,43 @@ async function configureClaudeCode(input, deps) {
620
799
  await runCommand("claude", [
621
800
  "mcp",
622
801
  "add",
623
- "--transport",
624
- "http",
625
802
  "haven",
626
- input.hostedMcpUrl,
627
- "--header",
628
- `Authorization: Bearer ${input.apiKey}`
629
- ]);
630
- await runCommand("claude", [
631
- "mcp",
632
- "add",
633
- "haven-signer",
803
+ "--",
634
804
  "npx",
635
805
  "-y",
636
- "@haven_ai/signer",
637
- "--credentials",
806
+ localMcpPackageName2(),
807
+ "--identity",
808
+ input.identityPath,
809
+ "--signer",
638
810
  input.signerPath
639
811
  ]);
812
+ await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
640
813
  return {
641
- hostedConfigured: true,
814
+ hostedConfigured: false,
642
815
  signerConfigured: true,
816
+ localMcpConfigured: true,
817
+ runtimeMcpMode: "local_stdio",
643
818
  target: "Claude Code MCP config",
644
819
  changed: true,
645
820
  restartRequired: true,
646
- messages: ["Updated Haven MCP entries with Claude Code."]
821
+ messages: [
822
+ "Updated local Haven MCP entry with Claude Code.",
823
+ "After Haven approval, restart Claude Code normally so it can load Haven tools."
824
+ ]
647
825
  };
648
826
  } catch (err) {
649
827
  return {
650
828
  hostedConfigured: false,
651
829
  signerConfigured: false,
830
+ localMcpConfigured: false,
831
+ runtimeMcpMode: "local_stdio",
652
832
  target: "Claude Code MCP config",
653
833
  changed: false,
654
834
  restartRequired: true,
655
- messages: [`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`],
835
+ messages: [
836
+ `Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
837
+ "Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
838
+ ],
656
839
  errorCode: "claude_code_config_failed"
657
840
  };
658
841
  }
@@ -660,21 +843,66 @@ async function configureClaudeCode(input, deps) {
660
843
  async function defaultRunCommand(command, args) {
661
844
  await execFileAsync(command, args, { timeout: 1e4 });
662
845
  }
663
- function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
846
+ function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady) {
847
+ if (mode === "local_stdio") {
848
+ return localMcpReady ? "local_stdio_mcp_ready" : "local_stdio_mcp_unavailable";
849
+ }
664
850
  const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
665
851
  const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
666
852
  return `${hostedPart}_${signerPart}`.slice(0, 120);
667
853
  }
668
- function nextAction(restartMode, errorCode) {
854
+ async function resolveLocalMcpConsent(input, messages) {
855
+ if (input.ackLocalTools || input.ackSigner) {
856
+ const status = await acknowledgeLocalMcpConsent(input.identityPath, input.signerPath, (message) => messages.push(message));
857
+ if (status.acknowledged) {
858
+ messages.push("Prepared the local Haven tools acknowledgement.");
859
+ } else {
860
+ messages.push("Local Haven tools acknowledgement still needs attention.");
861
+ }
862
+ return status;
863
+ }
864
+ return getLocalMcpConsentStatus(input.identityPath, input.signerPath);
865
+ }
866
+ async function resolveSignerConsent(input, messages) {
867
+ if (input.ackSigner || input.ackLocalTools) {
868
+ const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
869
+ if (status.acknowledged) {
870
+ messages.push("Prepared the local Haven signer acknowledgement.");
871
+ } else {
872
+ messages.push("Local Haven signer acknowledgement still needs attention.");
873
+ }
874
+ return status;
875
+ }
876
+ return getLocalSignerConsentStatus(input.signerPath);
877
+ }
878
+ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
879
+ if (!signerCredentialReady) return "local_signer_credential_unavailable";
880
+ if (!signerConsent?.acknowledged) return "local_signer_ack_required";
881
+ return void 0;
882
+ }
883
+ function localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) {
884
+ if (!signerCredentialReady) return "local_signer_credential_unavailable";
885
+ if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
886
+ return void 0;
887
+ }
888
+ function nextAction(runtime, restartMode, errorCode) {
669
889
  if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
670
890
  if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
891
+ if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
892
+ if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
671
893
  if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
672
894
  if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
673
895
  return "return_to_haven_for_wallet_approval_then_configure_runtime";
674
896
  }
897
+ function localMcpPackageName2() {
898
+ return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
899
+ }
900
+ function usesLocalMcp(runtime) {
901
+ return runtime === "codex-cli" || runtime === "claude-code";
902
+ }
675
903
 
676
904
  // src/runtime.ts
677
- var CONNECTOR_VERSION = "0.1.0";
905
+ var CONNECTOR_VERSION = "0.1.2";
678
906
  async function runConnect(options, deps = {}) {
679
907
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
680
908
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -720,9 +948,15 @@ async function runConnect(options, deps = {}) {
720
948
  agentId: registration.agent_id,
721
949
  apiKey: localApiKey,
722
950
  delegateKey: localKey.privateKey,
951
+ delegateAddress: localKey.address,
723
952
  safeAddress: setup.haven_wallet.address,
724
953
  chainId: setup.haven_wallet.chain_id,
725
954
  network: setup.haven_wallet.network,
955
+ agentBudget: setup.agent_budget.map((budget) => ({
956
+ token_symbol: budget.token_symbol,
957
+ allowance_amount: budget.allowance_amount,
958
+ reset_period_min: budget.reset_period_min
959
+ })),
726
960
  apiUrl: options.apiBaseUrl,
727
961
  hostedMcpUrl: registration.hosted_mcp_url,
728
962
  warn: log
@@ -736,16 +970,23 @@ async function runConnect(options, deps = {}) {
736
970
  signerPath: credentialPaths.signerPath,
737
971
  identityPath: credentialPaths.identityPath,
738
972
  credentialDirectory: credentialPaths.directory,
739
- environmentLabel: options.environmentLabel ?? "Local workspace"
973
+ environmentLabel: options.environmentLabel ?? "Local workspace",
974
+ ackSigner: options.ackSigner,
975
+ ackLocalTools: options.ackLocalTools
740
976
  });
741
977
  printRuntimeInstall(runtimeInstall, log);
742
978
  try {
743
979
  await api.updateInstallStatus(registration.setup_id, localApiKey, {
744
980
  runtime: runtimeInstall.runtime,
745
981
  connectorVersion,
982
+ runtimeMcpMode: runtimeInstall.runtimeMcpMode,
746
983
  hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
747
984
  localSignerConfigured: runtimeInstall.localSignerConfigured,
985
+ localMcpConfigured: runtimeInstall.localMcpConfigured,
748
986
  credentialFilesWritten: true,
987
+ signerAcknowledged: runtimeInstall.signerAcknowledged,
988
+ localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
989
+ activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
749
990
  probeResult: runtimeInstall.probeResult,
750
991
  restartRequired: runtimeInstall.restartRequired,
751
992
  nextUserAction: runtimeInstall.nextUserAction,
@@ -757,7 +998,7 @@ async function runConnect(options, deps = {}) {
757
998
  }
758
999
  log("Return to Haven to approve the agent rules.");
759
1000
  if (runtimeInstall.restartRequired) {
760
- log("Restart this agent session after approval if your runtime loads MCP tools at startup.");
1001
+ log("After approval, restart this agent normally so it can load Haven tools.");
761
1002
  }
762
1003
  return {
763
1004
  setupId: registration.setup_id,
@@ -783,10 +1024,12 @@ function secureLogger(log) {
783
1024
  }
784
1025
  function printRuntimeInstall(result, log) {
785
1026
  for (const message of result.messages) log(message);
786
- if (result.hostedMcpConfigured) {
1027
+ if (result.localMcpConfigured) {
1028
+ log("Configured local Haven MCP tools.");
1029
+ } else if (result.hostedMcpConfigured) {
787
1030
  log("Configured hosted Haven MCP identity.");
788
1031
  } else {
789
- log("Hosted Haven MCP identity still needs runtime setup.");
1032
+ log("Haven MCP tools still need runtime setup.");
790
1033
  }
791
1034
  if (result.localSignerConfigured) {
792
1035
  log("Configured local Haven signer.");
@@ -816,6 +1059,11 @@ function parseArgs(argv, env = process.env) {
816
1059
  options.credentialsDir = requireValue(argv, ++i, arg);
817
1060
  } else if (arg === "--environment-label") {
818
1061
  options.environmentLabel = requireValue(argv, ++i, arg);
1062
+ } else if (arg === "--ack-local-tools") {
1063
+ options.ackLocalTools = true;
1064
+ } else if (arg === "--ack-signer") {
1065
+ options.ackSigner = true;
1066
+ options.ackLocalTools = true;
819
1067
  } else if (arg === "--version") {
820
1068
  process.stdout.write(`${CONNECTOR_VERSION}
821
1069
  `);
@@ -844,7 +1092,7 @@ function helpText() {
844
1092
  "sends Haven only the public signing address plus a proof signature.",
845
1093
  "",
846
1094
  "Usage:",
847
- " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code",
1095
+ " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code",
848
1096
  "",
849
1097
  "Options:",
850
1098
  " --setup <token> Short-lived setup token from Haven.",
@@ -852,6 +1100,8 @@ function helpText() {
852
1100
  " --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
853
1101
  " --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
854
1102
  " --environment-label <text> Non-sensitive label shown in Haven setup review.",
1103
+ " --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
1104
+ " --ack-signer Backward-compatible alias for --ack-local-tools.",
855
1105
  " --help Show this help.",
856
1106
  "",
857
1107
  "The connector never prints the private key and never sends it to Haven."