@haven_ai/connect 0.1.30-alpha.0 → 0.1.31-alpha.0

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/dist/index.d.cts CHANGED
@@ -203,8 +203,8 @@ interface RuntimeProfile {
203
203
  }
204
204
  declare function runtimeProfile(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeProfile;
205
205
  declare function normalizeRuntime(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeId;
206
- /** The flag values a refusal message should offer. Mirrors RuntimeId. */
207
- declare const RUNTIME_FLAG_VALUES: "claude-code, codex-cli, codex-desktop, cursor, vscode, vscode-insiders, claude-desktop, hermes, other";
206
+ /** The same values as refusal-message prose. */
207
+ declare const RUNTIME_FLAG_VALUES: string;
208
208
  interface RuntimeSelection {
209
209
  runtime: RuntimeId | null;
210
210
  source: 'force' | 'detected' | 'explicit' | 'prompted' | 'none';
@@ -447,7 +447,7 @@ declare function runtimeInstallCapabilities(runtime: string | undefined, env?: N
447
447
  restartRequired: boolean;
448
448
  };
449
449
 
450
- declare const CONNECTOR_VERSION = "0.1.30-alpha.0";
450
+ declare const CONNECTOR_VERSION = "0.1.31-alpha.0";
451
451
  interface ConnectOptions {
452
452
  setupToken: string;
453
453
  apiBaseUrl: string;
@@ -515,9 +515,19 @@ interface ConnectOutcome {
515
515
  };
516
516
  delegate_address?: string;
517
517
  setup_challenge_expires_at?: string;
518
+ /**
519
+ * #2091, additive within schema_version 1: `message` is the redacted human
520
+ * refusal (automation used to get code + next_action and nothing to act
521
+ * on), and `allowed_runtimes` carries the valid `--runtime` retry values on
522
+ * runtime-selection refusals — the list the backend's setup prompt requires
523
+ * a retry to be drawn from, which `--json` previously discarded with the
524
+ * prose.
525
+ */
518
526
  error?: {
519
527
  code: string;
520
528
  next_action: string;
529
+ message?: string;
530
+ allowed_runtimes?: readonly string[];
521
531
  };
522
532
  }
523
533
  interface ConnectDeps {
@@ -555,13 +565,18 @@ declare function completionOutcome(input: {
555
565
  approvalRequired: boolean;
556
566
  }): ConnectOutcome;
557
567
  /**
558
- * A deliberately terse failure record: error messages can contain server or
559
- * filesystem detail, while this public contract must remain safe to serialize.
568
+ * The failure record for the `--json` contract.
560
569
  *
561
570
  * #1719: a `ConnectError` carries its own code and next action, so it is read
562
571
  * rather than guessed. The regex ladder below survives only for the refusals
563
572
  * that are still plain `Error`s — every new failure mode joins the vocabulary
564
573
  * instead of joining that ladder.
574
+ *
575
+ * #2091: the record now carries the redacted message rather than omitting it.
576
+ * The old stance — terse because "messages can contain server or filesystem
577
+ * detail" — left automation with a code and nothing to act on; the field
578
+ * failure was a Codex agent staring at `connect_failed` with, in its own
579
+ * words, "no additional safe error detail". Sanitize, don't silence.
565
580
  */
566
581
  declare function failedConnectOutcome(runtimeHint: string | undefined, error: unknown): ConnectOutcome;
567
582
  /**
@@ -634,10 +649,22 @@ declare function shortAddress(address: string): string;
634
649
  * Codes are additive and never renamed: a consumer pinned to an older
635
650
  * connector must keep recognising the ones it already knows.
636
651
  */
652
+ interface ConnectErrorDetails {
653
+ /**
654
+ * The `--runtime` values a retry may use, when the refusal is about runtime
655
+ * selection (#2091). The values lived only in `message` prose, which the
656
+ * `--json` contract discards entirely — while the backend's setup prompt
657
+ * instructs an agent to retry only with "one of the values that refusal
658
+ * lists". Carrying them structurally is what makes that retry reachable
659
+ * from automation.
660
+ */
661
+ allowedRuntimes?: readonly string[];
662
+ }
637
663
  declare class ConnectError extends Error {
638
664
  readonly code: string;
639
665
  readonly nextAction: string;
640
- constructor(code: string, message: string, nextAction: string);
666
+ readonly details: ConnectErrorDetails;
667
+ constructor(code: string, message: string, nextAction: string, details?: ConnectErrorDetails);
641
668
  }
642
669
  declare function isConnectError(err: unknown): err is ConnectError;
643
670
 
@@ -697,11 +724,11 @@ declare function resolveRuntimeByInstalledClientPrompt(options?: ScanInstalledCl
697
724
 
698
725
  declare const MCP_RUNTIME_MANIFEST: {
699
726
  readonly mcpPackage: "@haven_ai/mcp";
700
- readonly mcpVersion: "0.1.30-alpha.0";
727
+ readonly mcpVersion: "0.1.31-alpha.0";
701
728
  readonly sdkPackage: "@haven_ai/sdk";
702
- readonly sdkVersion: "0.1.30-alpha.0";
729
+ readonly sdkVersion: "0.1.31-alpha.0";
703
730
  readonly signerPackage: "@haven_ai/signer";
704
- readonly signerVersion: "0.1.30-alpha.0";
731
+ readonly signerVersion: "0.1.31-alpha.0";
705
732
  readonly minimumNodeVersion: "22.0.0";
706
733
  readonly supportedClients: readonly ["codex-cli", "codex-desktop", "claude-code"];
707
734
  readonly requiredTools: readonly string[];
package/dist/index.d.ts CHANGED
@@ -203,8 +203,8 @@ interface RuntimeProfile {
203
203
  }
204
204
  declare function runtimeProfile(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeProfile;
205
205
  declare function normalizeRuntime(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeId;
206
- /** The flag values a refusal message should offer. Mirrors RuntimeId. */
207
- declare const RUNTIME_FLAG_VALUES: "claude-code, codex-cli, codex-desktop, cursor, vscode, vscode-insiders, claude-desktop, hermes, other";
206
+ /** The same values as refusal-message prose. */
207
+ declare const RUNTIME_FLAG_VALUES: string;
208
208
  interface RuntimeSelection {
209
209
  runtime: RuntimeId | null;
210
210
  source: 'force' | 'detected' | 'explicit' | 'prompted' | 'none';
@@ -447,7 +447,7 @@ declare function runtimeInstallCapabilities(runtime: string | undefined, env?: N
447
447
  restartRequired: boolean;
448
448
  };
449
449
 
450
- declare const CONNECTOR_VERSION = "0.1.30-alpha.0";
450
+ declare const CONNECTOR_VERSION = "0.1.31-alpha.0";
451
451
  interface ConnectOptions {
452
452
  setupToken: string;
453
453
  apiBaseUrl: string;
@@ -515,9 +515,19 @@ interface ConnectOutcome {
515
515
  };
516
516
  delegate_address?: string;
517
517
  setup_challenge_expires_at?: string;
518
+ /**
519
+ * #2091, additive within schema_version 1: `message` is the redacted human
520
+ * refusal (automation used to get code + next_action and nothing to act
521
+ * on), and `allowed_runtimes` carries the valid `--runtime` retry values on
522
+ * runtime-selection refusals — the list the backend's setup prompt requires
523
+ * a retry to be drawn from, which `--json` previously discarded with the
524
+ * prose.
525
+ */
518
526
  error?: {
519
527
  code: string;
520
528
  next_action: string;
529
+ message?: string;
530
+ allowed_runtimes?: readonly string[];
521
531
  };
522
532
  }
523
533
  interface ConnectDeps {
@@ -555,13 +565,18 @@ declare function completionOutcome(input: {
555
565
  approvalRequired: boolean;
556
566
  }): ConnectOutcome;
557
567
  /**
558
- * A deliberately terse failure record: error messages can contain server or
559
- * filesystem detail, while this public contract must remain safe to serialize.
568
+ * The failure record for the `--json` contract.
560
569
  *
561
570
  * #1719: a `ConnectError` carries its own code and next action, so it is read
562
571
  * rather than guessed. The regex ladder below survives only for the refusals
563
572
  * that are still plain `Error`s — every new failure mode joins the vocabulary
564
573
  * instead of joining that ladder.
574
+ *
575
+ * #2091: the record now carries the redacted message rather than omitting it.
576
+ * The old stance — terse because "messages can contain server or filesystem
577
+ * detail" — left automation with a code and nothing to act on; the field
578
+ * failure was a Codex agent staring at `connect_failed` with, in its own
579
+ * words, "no additional safe error detail". Sanitize, don't silence.
565
580
  */
566
581
  declare function failedConnectOutcome(runtimeHint: string | undefined, error: unknown): ConnectOutcome;
567
582
  /**
@@ -634,10 +649,22 @@ declare function shortAddress(address: string): string;
634
649
  * Codes are additive and never renamed: a consumer pinned to an older
635
650
  * connector must keep recognising the ones it already knows.
636
651
  */
652
+ interface ConnectErrorDetails {
653
+ /**
654
+ * The `--runtime` values a retry may use, when the refusal is about runtime
655
+ * selection (#2091). The values lived only in `message` prose, which the
656
+ * `--json` contract discards entirely — while the backend's setup prompt
657
+ * instructs an agent to retry only with "one of the values that refusal
658
+ * lists". Carrying them structurally is what makes that retry reachable
659
+ * from automation.
660
+ */
661
+ allowedRuntimes?: readonly string[];
662
+ }
637
663
  declare class ConnectError extends Error {
638
664
  readonly code: string;
639
665
  readonly nextAction: string;
640
- constructor(code: string, message: string, nextAction: string);
666
+ readonly details: ConnectErrorDetails;
667
+ constructor(code: string, message: string, nextAction: string, details?: ConnectErrorDetails);
641
668
  }
642
669
  declare function isConnectError(err: unknown): err is ConnectError;
643
670
 
@@ -697,11 +724,11 @@ declare function resolveRuntimeByInstalledClientPrompt(options?: ScanInstalledCl
697
724
 
698
725
  declare const MCP_RUNTIME_MANIFEST: {
699
726
  readonly mcpPackage: "@haven_ai/mcp";
700
- readonly mcpVersion: "0.1.30-alpha.0";
727
+ readonly mcpVersion: "0.1.31-alpha.0";
701
728
  readonly sdkPackage: "@haven_ai/sdk";
702
- readonly sdkVersion: "0.1.30-alpha.0";
729
+ readonly sdkVersion: "0.1.31-alpha.0";
703
730
  readonly signerPackage: "@haven_ai/signer";
704
- readonly signerVersion: "0.1.30-alpha.0";
731
+ readonly signerVersion: "0.1.31-alpha.0";
705
732
  readonly minimumNodeVersion: "22.0.0";
706
733
  readonly supportedClients: readonly ["codex-cli", "codex-desktop", "claude-code"];
707
734
  readonly requiredTools: readonly string[];
package/dist/index.js CHANGED
@@ -146,6 +146,9 @@ var init_key = __esm({
146
146
  function redactSecrets(value) {
147
147
  return value.replace(API_KEY_RE, "sk_agent_[redacted]").replace(PRIVATE_KEY_RE, "0x[redacted-private-key]");
148
148
  }
149
+ function redactForAutomation(value) {
150
+ return redactSecrets(value).replace(/(?:~|\/)[^\s`"']*\/(?:identity|signer|agent)\.json\b/g, "[credential-file-redacted]").replace(/(?:~|\/)[^\s`"']*\/\.env\b/g, "[credential-env-redacted]");
151
+ }
149
152
  function shortAddress(address) {
150
153
  if (!/^0x[0-9a-fA-F]{40}$/.test(address)) return address;
151
154
  return `${address.slice(0, 6)}...${address.slice(-4)}`;
@@ -516,9 +519,9 @@ var init_runtime_manifest = __esm({
516
519
  mcpPackage: "@haven_ai/mcp",
517
520
  mcpVersion: MCP_VERSION,
518
521
  sdkPackage: "@haven_ai/sdk",
519
- sdkVersion: "0.1.30-alpha.0",
522
+ sdkVersion: "0.1.31-alpha.0",
520
523
  signerPackage: "@haven_ai/signer",
521
- signerVersion: "0.1.30-alpha.0",
524
+ signerVersion: "0.1.31-alpha.0",
522
525
  // Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
523
526
  // while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
524
527
  // so the guard that was supposed to enforce the floor waved Node v23 through
@@ -1941,11 +1944,13 @@ var init_connect_error = __esm({
1941
1944
  ConnectError = class extends Error {
1942
1945
  code;
1943
1946
  nextAction;
1944
- constructor(code, message, nextAction2) {
1947
+ details;
1948
+ constructor(code, message, nextAction2, details = {}) {
1945
1949
  super(message);
1946
1950
  this.name = "ConnectError";
1947
1951
  this.code = code;
1948
1952
  this.nextAction = nextAction2;
1953
+ this.details = details;
1949
1954
  }
1950
1955
  };
1951
1956
  }
@@ -1968,7 +1973,8 @@ async function resolveRuntimeSelection(explicit, force, options = {}) {
1968
1973
  throw new ConnectError(
1969
1974
  "runtime_force_unrecognized",
1970
1975
  `Unknown --runtime-force value "${force}". Valid values: ${RUNTIME_FLAG_VALUES}.`,
1971
- "rerun_connect_with_a_valid_runtime_name"
1976
+ "rerun_connect_with_a_valid_runtime_name",
1977
+ { allowedRuntimes: RUNTIME_FLAG_VALUE_LIST }
1972
1978
  );
1973
1979
  }
1974
1980
  return { runtime: forced, source: "force" };
@@ -1981,7 +1987,8 @@ async function resolveRuntimeSelection(explicit, force, options = {}) {
1981
1987
  throw new ConnectError(
1982
1988
  "runtime_unrecognized",
1983
1989
  `"${supplied}" is not an agent runtime Haven knows. Valid values: ${RUNTIME_FLAG_VALUES} (the aliases cowork, codex and openclaw are accepted too). Re-run with one of those, or --runtime other to store credentials and finish the MCP setup by hand. Nothing was written and the Haven setup token is still unused.`,
1984
- "rerun_connect_with_a_valid_runtime_name"
1990
+ "rerun_connect_with_a_valid_runtime_name",
1991
+ { allowedRuntimes: RUNTIME_FLAG_VALUE_LIST }
1985
1992
  );
1986
1993
  }
1987
1994
  return { runtime: detected, source: "detected", discardedHint: supplied };
@@ -2016,7 +2023,7 @@ function detectRuntime(env) {
2016
2023
  if (env.HERMES_HOME || env.HERMES_AGENT) return "hermes";
2017
2024
  return null;
2018
2025
  }
2019
- var RUNTIME_PROFILES, RUNTIME_ALIASES, RUNTIME_FLAG_VALUES;
2026
+ var RUNTIME_PROFILES, RUNTIME_ALIASES, RUNTIME_FLAG_VALUE_LIST, RUNTIME_FLAG_VALUES;
2020
2027
  var init_runtime_registry = __esm({
2021
2028
  "src/runtime-registry.ts"() {
2022
2029
  init_connect_error();
@@ -2135,7 +2142,18 @@ var init_runtime_registry = __esm({
2135
2142
  other: "other",
2136
2143
  manual: "other"
2137
2144
  };
2138
- RUNTIME_FLAG_VALUES = "claude-code, codex-cli, codex-desktop, cursor, vscode, vscode-insiders, claude-desktop, hermes, other";
2145
+ RUNTIME_FLAG_VALUE_LIST = [
2146
+ "claude-code",
2147
+ "codex-cli",
2148
+ "codex-desktop",
2149
+ "cursor",
2150
+ "vscode",
2151
+ "vscode-insiders",
2152
+ "claude-desktop",
2153
+ "hermes",
2154
+ "other"
2155
+ ];
2156
+ RUNTIME_FLAG_VALUES = RUNTIME_FLAG_VALUE_LIST.join(", ");
2139
2157
  }
2140
2158
  });
2141
2159
  async function acknowledgeLocalSignerConsent(signerPath, log) {
@@ -3698,7 +3716,7 @@ function defaultPromptIo() {
3698
3716
  // src/runtime.ts
3699
3717
  init_local_mcp_runtime();
3700
3718
  init_runtime_manifest();
3701
- var CONNECTOR_VERSION = "0.1.30-alpha.0";
3719
+ var CONNECTOR_VERSION = "0.1.31-alpha.0";
3702
3720
  var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
3703
3721
  async function runConnect(options, deps = {}) {
3704
3722
  assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
@@ -3723,7 +3741,14 @@ async function runConnect(options, deps = {}) {
3723
3741
  throw new ConnectError(
3724
3742
  "runtime_undetermined",
3725
3743
  `Could not determine the agent runtime: nothing was detected in this environment and no --runtime was given. If you are an AI agent running this command: re-run it once, unchanged except for adding --runtime <name>, naming the harness you are running in \u2014 one of: ${RUNTIME_FLAG_VALUES} (the aliases cowork, codex and openclaw are accepted too). Do not guess: if your harness is not one of those, use --runtime other, which stores the credentials and prints the manual MCP steps. Nothing was written and the Haven setup token is still unused.`,
3726
- "rerun_connect_with_explicit_runtime"
3744
+ "rerun_connect_with_explicit_runtime",
3745
+ // #2091: the values must ride structurally too. The backend's setup
3746
+ // prompt permits a retry only with "one of the values that refusal
3747
+ // lists" — and --json discards prose, so a prose-only list deadlocked
3748
+ // every automation run in an undetected runtime (Codex in the field:
3749
+ // npx needs network, Codex runs network commands unsandboxed, and the
3750
+ // unsandboxed path carries none of the CODEX_* detection vars).
3751
+ { allowedRuntimes: RUNTIME_FLAG_VALUE_LIST }
3727
3752
  );
3728
3753
  }
3729
3754
  const runtime = selection.runtime;
@@ -3745,11 +3770,16 @@ async function runConnect(options, deps = {}) {
3745
3770
  log(`runtime: ${runtime} (chosen at the prompt \u2014 nothing was detected in this environment)`);
3746
3771
  }
3747
3772
  log("Warming up your connection to Haven\u2026");
3748
- const setup = await api.resolveSetup({
3749
- setupToken: options.setupToken,
3750
- connectorVersion,
3751
- runtime
3752
- });
3773
+ let setup;
3774
+ try {
3775
+ setup = await api.resolveSetup({
3776
+ setupToken: options.setupToken,
3777
+ connectorVersion,
3778
+ runtime
3779
+ });
3780
+ } catch (err) {
3781
+ throw deadSetupTokenError(err) ?? err;
3782
+ }
3753
3783
  assertSetupChallengeIsUsable(setup.challenge.expires_at);
3754
3784
  printSetupSummary(setup, log);
3755
3785
  await preflightStorage({ baseDir: options.credentialsDir, warn: log });
@@ -3786,6 +3816,8 @@ async function runConnect(options, deps = {}) {
3786
3816
  installCapabilities
3787
3817
  });
3788
3818
  } catch (err) {
3819
+ const dead = deadSetupTokenError(err);
3820
+ if (dead) throw dead;
3789
3821
  if (isExpiredSetupChallenge(err)) {
3790
3822
  throw new Error(
3791
3823
  "The Haven setup challenge expired while connecting. Return to Haven, start a fresh connection, and run its new Connect command. Do not reuse or paste credentials."
@@ -3983,7 +4015,17 @@ function failedConnectOutcome(runtimeHint, error) {
3983
4015
  tools: ["haven_get_agent", "haven_get_allowances"],
3984
4016
  instruction: "After a successful setup and activation, verify only with haven_get_agent and haven_get_allowances."
3985
4017
  },
3986
- error: { code, next_action: nextAction2 }
4018
+ error: {
4019
+ code,
4020
+ next_action: nextAction2,
4021
+ // Only a ConnectError's message enters the JSON record: the vocabulary's
4022
+ // prose is connector-authored and safe to serialize, while a plain
4023
+ // Error can carry arbitrary server or filesystem detail (that stance is
4024
+ // pinned by test). Redaction stays on as belt-and-braces; plain-Error
4025
+ // runs still get their redacted message on stderr via the CLI mirror.
4026
+ ...error instanceof ConnectError && message ? { message: redactForAutomation(message) } : {},
4027
+ ...error instanceof ConnectError && error.details.allowedRuntimes ? { allowed_runtimes: error.details.allowedRuntimes } : {}
4028
+ }
3987
4029
  };
3988
4030
  }
3989
4031
  function printSetupSummary(setup, log) {
@@ -4005,16 +4047,20 @@ function assertSetupChallengeIsUsable(expiresAt) {
4005
4047
  "This Haven setup challenge is expired or invalid. Return to Haven, start a fresh connection, and rerun Connect. No local credentials were written."
4006
4048
  );
4007
4049
  }
4050
+ function deadSetupTokenError(err) {
4051
+ if (!(err instanceof ConnectRequestError) || err.status !== 410 && err.status !== 401) return null;
4052
+ return new ConnectError(
4053
+ "setup_challenge_expired_or_invalid",
4054
+ "This Haven setup token is expired or invalid \u2014 tokens are single-use and expire 30 minutes after the dashboard issues them, and a mistyped token reads the same way. Return to Haven, start a fresh connection, and run its new Connect command. No local credentials were written.",
4055
+ "return_to_haven_for_fresh_setup"
4056
+ );
4057
+ }
4008
4058
  function isExpiredSetupChallenge(err) {
4009
4059
  return err instanceof Error && /(?:setup )?challenge.*expir|expir.*(?:setup )?challenge/i.test(err.message);
4010
4060
  }
4011
4061
  function secureLogger(log, redactPaths = false) {
4012
4062
  return (message) => {
4013
- let safe = redactSecrets(message);
4014
- if (redactPaths) {
4015
- safe = safe.replace(/(?:~|\/)[^\s`"']*\/(?:identity|signer|agent)\.json\b/g, "[credential-file-redacted]").replace(/(?:~|\/)[^\s`"']*\/\.env\b/g, "[credential-env-redacted]");
4016
- }
4017
- log(safe);
4063
+ log(redactPaths ? redactForAutomation(message) : redactSecrets(message));
4018
4064
  };
4019
4065
  }
4020
4066
  function printRuntimeInstall(result, log) {
@@ -4358,6 +4404,8 @@ async function runCli(argv, io = {
4358
4404
  parsed = parseArgs(argv);
4359
4405
  } catch (err) {
4360
4406
  if (wantsJson) {
4407
+ io.stderr(`${redactForAutomation(err instanceof Error ? err.message : String(err))}
4408
+ `);
4361
4409
  io.stdout(`${JSON.stringify(failedConnectOutcome(void 0, err))}
4362
4410
  `);
4363
4411
  } else {
@@ -4538,6 +4586,8 @@ async function runCli(argv, io = {
4538
4586
  return 0;
4539
4587
  } catch (err) {
4540
4588
  if (parsed.json) {
4589
+ io.stderr(`${redactForAutomation(err instanceof Error ? err.message : String(err))}
4590
+ `);
4541
4591
  io.stdout(`${JSON.stringify(failedConnectOutcome(parsed.options.runtime, err))}
4542
4592
  `);
4543
4593
  } else {