@haven_ai/connect 0.1.30-alpha.0 → 0.1.32-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.ts CHANGED
@@ -190,6 +190,37 @@ interface PreflightCredentialStorageInput {
190
190
  declare function preflightCredentialStorage(input?: PreflightCredentialStorageInput): Promise<string>;
191
191
  declare function writeCredentialFiles(input: WriteCredentialInput): Promise<StoredCredentialPaths>;
192
192
  declare function defaultAgentDirectory(agentId: string, baseDir?: string): string;
193
+ /**
194
+ * Where the terminal `ConnectOutcome` is parked so a caller that lost the
195
+ * stream can still read the verdict.
196
+ *
197
+ * The field failure this exists for (2026-08-28, connector 0.1.31-alpha.0):
198
+ * setup SUCCEEDED, but the agent driving it stopped watching during the cold
199
+ * signer install, so the connector's final `--json` object — the only place
200
+ * the restart instruction and the read-only verification sequence live — was
201
+ * emitted into a stream nobody was reading. The agent reverse-engineered
202
+ * completion from `codex mcp list` instead.
203
+ */
204
+ declare const CONNECT_OUTCOME_FILENAME = "last-connect-outcome.json";
205
+ /**
206
+ * Persist the terminal outcome next to the credentials it describes.
207
+ *
208
+ * Non-secret by construction: the content is exactly the object already
209
+ * emitted on stdout, whose secret-freedom is pinned by its own tests. This
210
+ * function never reads or embeds credential-file contents — it serializes what
211
+ * it is handed and nothing else. Written 0o600 anyway, because it lives inside
212
+ * a 0o700 credential directory and there is no reason to widen it.
213
+ *
214
+ * Overwrites, unlike every other write in this file: "the last outcome" is a
215
+ * single slot, not a credential, so `assertDoesNotExist` would be wrong here —
216
+ * a stale verdict left in place is exactly the failure the file exists to
217
+ * prevent.
218
+ *
219
+ * Throws on failure. Best-effort is the CALLER's contract (`runConnect` wraps
220
+ * both call sites), so that a test can inject a writer that fails and still
221
+ * assert the run completes.
222
+ */
223
+ declare function writeConnectOutcomeRecord(directory: string, outcome: unknown, warn?: (message: string) => void): Promise<string>;
193
224
 
194
225
  type RuntimeId = 'claude-code' | 'codex-desktop' | 'codex-cli' | 'cursor' | 'vscode' | 'vscode-insiders' | 'claude-desktop' | 'hermes' | 'other';
195
226
  type RestartMode = 'restart-session' | 'restart-app' | 'hot-reload' | 'manual';
@@ -203,8 +234,8 @@ interface RuntimeProfile {
203
234
  }
204
235
  declare function runtimeProfile(runtime: string | undefined, env?: NodeJS.ProcessEnv): RuntimeProfile;
205
236
  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";
237
+ /** The same values as refusal-message prose. */
238
+ declare const RUNTIME_FLAG_VALUES: string;
208
239
  interface RuntimeSelection {
209
240
  runtime: RuntimeId | null;
210
241
  source: 'force' | 'detected' | 'explicit' | 'prompted' | 'none';
@@ -447,7 +478,102 @@ declare function runtimeInstallCapabilities(runtime: string | undefined, env?: N
447
478
  restartRequired: boolean;
448
479
  };
449
480
 
450
- declare const CONNECTOR_VERSION = "0.1.30-alpha.0";
481
+ /**
482
+ * Installed-client scan + interactive pick (#1719).
483
+ *
484
+ * The rung that answers "which runtime am I configuring?" for a HUMAN in a
485
+ * plain terminal, where there is no agent shell to detect. Two properties hold
486
+ * it up, and both are load-bearing:
487
+ *
488
+ * 1. **Only clients the connector can actually write.** A row that cannot be
489
+ * configured is not a choice, it is a dead end wearing a choice's clothes.
490
+ * 2. **The scan populates the choices; it never selects.** Finding exactly one
491
+ * installed app tells you what EXISTS, not where the user wants their agent
492
+ * to run — and the cost of being wrong is an API key and a delegate key
493
+ * written into an app the user does not use. `scanInstalledClients` returns
494
+ * candidates and nothing else; only an answer typed at the prompt resolves
495
+ * a runtime.
496
+ */
497
+ interface InstalledClientCandidate {
498
+ runtime: RuntimeId;
499
+ label: string;
500
+ /** What made this a candidate — shown at the prompt so the pick is informed. */
501
+ detail: string;
502
+ /**
503
+ * The config file Haven would write for this client, when it owns one.
504
+ * `null` for Claude Code, which is configured through its own CLI. Set
505
+ * regardless of which evidence found the client — `evidence` is what says
506
+ * whether that file exists today.
507
+ */
508
+ configPath: string | null;
509
+ evidence: 'config-file' | 'client-directory';
510
+ }
511
+ interface ScanInstalledClientsOptions {
512
+ homeDir?: string;
513
+ /** Workspace root, for the project-local `.vscode/` marker. */
514
+ cwd?: string;
515
+ env?: NodeJS.ProcessEnv;
516
+ /** Injectable so the scan is testable without a populated home directory. */
517
+ exists?: (path: string) => Promise<boolean>;
518
+ }
519
+ declare function scanInstalledClients(options?: ScanInstalledClientsOptions): Promise<InstalledClientCandidate[]>;
520
+ /**
521
+ * The scan's findings as DATA, for the `--json` refusal (#2174).
522
+ *
523
+ * The interactive prompt is deliberately omitted under `--json`, which threw
524
+ * this signal away exactly where it was most useful: an agent retrying a
525
+ * `runtime_undetermined` refusal picked from a nine-value menu on
526
+ * self-knowledge alone, while the connector already knew which client configs
527
+ * exist on the machine.
528
+ *
529
+ * Property 2 above is preserved verbatim and is the reason this returns a
530
+ * HINT rather than a runtime: the caller still has to refuse. Finding exactly
531
+ * one installed app tells you what exists, not where the user wants their
532
+ * agent to run, and the cost of being wrong is an API key and a delegate key
533
+ * written into an app they do not use.
534
+ */
535
+ interface InstalledClientHint {
536
+ /** Runtime ids the scan found, likeliest first. */
537
+ installedClients: readonly RuntimeId[];
538
+ /**
539
+ * The top hit, and only when it is unambiguously top — see
540
+ * `installedClientHint`. A value an agent may echo back as `--runtime`;
541
+ * never a selection the connector makes for it.
542
+ */
543
+ suggestedRuntime?: RuntimeId;
544
+ }
545
+ /**
546
+ * A suggestion is offered only when one candidate is CLEARLY first: a lone
547
+ * candidate, or a single live MCP config file among bare client directories —
548
+ * the one tier difference the scan treats as evidence. Candidates within a
549
+ * tier are separated only by `SCAN_ORDER`, a fixed preference rather than a
550
+ * fact about this machine, so suggesting the winner of that tiebreak would
551
+ * dress an arbitrary choice as a finding.
552
+ *
553
+ * The suggestion is derived from the WHOLE array rather than from the first
554
+ * two entries, so it does not depend on the caller having sorted anything.
555
+ * This is exported, and an unsorted list reaching a positional rule would
556
+ * yield a quietly wrong suggestion — never a selection, since only the caller
557
+ * can act on it, but wrong is still wrong. `installedClients` preserves the
558
+ * order it was given, which for `scanInstalledClients` is likeliest-first.
559
+ */
560
+ declare function installedClientHint(candidates: readonly InstalledClientCandidate[]): InstalledClientHint;
561
+ interface PromptIo {
562
+ write: (text: string) => void;
563
+ /** Resolves the typed line, or `null` on EOF / Ctrl-C. */
564
+ question: (query: string) => Promise<string | null>;
565
+ }
566
+ declare function promptForInstalledClient(candidates: readonly InstalledClientCandidate[], io?: PromptIo): Promise<RuntimeId>;
567
+ /**
568
+ * The whole rung as one thunk: scan, refuse if nothing writable is installed,
569
+ * otherwise prompt. This is what `resolveRuntimeSelection` calls, which is why
570
+ * the registry needs no knowledge of the filesystem or of readline.
571
+ */
572
+ declare function resolveRuntimeByInstalledClientPrompt(options?: ScanInstalledClientsOptions & {
573
+ io?: PromptIo;
574
+ }): Promise<RuntimeId>;
575
+
576
+ declare const CONNECTOR_VERSION = "0.1.32-alpha.0";
451
577
  interface ConnectOptions {
452
578
  setupToken: string;
453
579
  apiBaseUrl: string;
@@ -515,9 +641,57 @@ interface ConnectOutcome {
515
641
  };
516
642
  delegate_address?: string;
517
643
  setup_challenge_expires_at?: string;
644
+ /**
645
+ * #2173, additive within schema_version 1. Both are present on a completed
646
+ * run and absent on `failedConnectOutcome`, which by construction has
647
+ * neither a registration nor a credential scan behind it.
648
+ *
649
+ * `hosted_mcp_url` is the endpoint Connect wired this run up to — written
650
+ * into the runtime's MCP config, or, on a manual runtime that Connect cannot
651
+ * configure, printed as the endpoint to enter by hand. It is deliberately
652
+ * NOT the `--api` backend URL: the hosted MCP
653
+ * server is a separate deployment, and an automation caller comparing the
654
+ * two used to read that intentional topology as an environment mismatch. It
655
+ * is non-secret — the same string already sits in the user's own config file
656
+ * — and carries no credential; the API key travels beside it in a header.
657
+ *
658
+ * `superseded_agent_ids` is the #1688 heads-up made structural: previously
659
+ * it was prose on stderr, so a `--json` caller could not see that the run it
660
+ * just completed left older agents alive with their own keys. Empty on a
661
+ * clean first run. An empty list is NOT proof of a clean machine — a scan
662
+ * that cannot read the credential root also yields an empty list, and the
663
+ * connector prefers that to failing a completed setup.
664
+ */
665
+ hosted_mcp_url?: string;
666
+ superseded_agent_ids?: readonly string[];
667
+ /**
668
+ * #2091, additive within schema_version 1: `message` is the redacted human
669
+ * refusal (automation used to get code + next_action and nothing to act
670
+ * on), and `allowed_runtimes` carries the valid `--runtime` retry values on
671
+ * runtime-selection refusals — the list the backend's setup prompt requires
672
+ * a retry to be drawn from, which `--json` previously discarded with the
673
+ * prose.
674
+ */
518
675
  error?: {
519
676
  code: string;
520
677
  next_action: string;
678
+ message?: string;
679
+ allowed_runtimes?: readonly string[];
680
+ /**
681
+ * #2174, additive within schema_version 1. What the installed-client scan
682
+ * found on THIS machine, narrowing a `runtime_undetermined` retry from the
683
+ * nine-value `allowed_runtimes` menu to what is actually here.
684
+ *
685
+ * A hint an agent may echo back as `--runtime`, never a selection: the
686
+ * outcome stays `failed` with `rerun_connect_with_explicit_runtime`, and
687
+ * the connector never proceeds on it (#1719's populates-never-selects
688
+ * invariant). Absent when the scan found nothing OR could not run —
689
+ * both are honestly "no finding"; neither is a claim the machine is bare.
690
+ * `suggested_runtime` appears only when one candidate is unambiguously
691
+ * top, so it is never the winner of an arbitrary tiebreak.
692
+ */
693
+ installed_clients?: readonly string[];
694
+ suggested_runtime?: string;
521
695
  };
522
696
  }
523
697
  interface ConnectDeps {
@@ -538,6 +712,19 @@ interface ConnectDeps {
538
712
  isTty?: boolean;
539
713
  /** Overridable so the #1719 installed-client prompt is testable without readline. */
540
714
  promptRuntime?: () => Promise<RuntimeId>;
715
+ /**
716
+ * Overridable so the #2173 recovery-record write is testable without a real
717
+ * credential directory — and so a FAILING writer can be injected to prove
718
+ * that a broken record write never fails a completed setup.
719
+ */
720
+ writeOutcomeRecord?: typeof writeConnectOutcomeRecord;
721
+ /**
722
+ * Overridable so the #2174 refusal hint is testable without depending on
723
+ * which agent clients happen to be installed on the machine running the
724
+ * suite — and so a THROWING scan can be injected to prove the refusal
725
+ * degrades to its un-hinted shape.
726
+ */
727
+ scanInstalledClients?: typeof scanInstalledClients;
541
728
  }
542
729
  interface ConnectResult {
543
730
  setupId: string;
@@ -547,21 +734,35 @@ interface ConnectResult {
547
734
  /** Additive, secret-free completion contract shared by library callers and --json. */
548
735
  outcome: ConnectOutcome;
549
736
  }
737
+ /**
738
+ * The failure record for an error thrown by `runConnect` — the run's own, when
739
+ * it built one, and a freshly derived record otherwise (a rejection that never
740
+ * entered the run at all, such as an argument-parse refusal, or an injected
741
+ * one in a test).
742
+ */
743
+ declare function failureOutcomeFor(runtimeHint: string | undefined, error: unknown): ConnectOutcome;
550
744
  declare function runConnect(options: ConnectOptions, deps?: ConnectDeps): Promise<ConnectResult>;
551
745
  declare function completionOutcome(input: {
552
746
  runtimeInstall: RuntimeInstallResult;
553
747
  delegateAddress: string;
748
+ hostedMcpUrl?: string;
749
+ supersededAgentIds?: readonly string[];
554
750
  setupChallengeExpiresAt?: string;
555
751
  approvalRequired: boolean;
556
752
  }): ConnectOutcome;
557
753
  /**
558
- * A deliberately terse failure record: error messages can contain server or
559
- * filesystem detail, while this public contract must remain safe to serialize.
754
+ * The failure record for the `--json` contract.
560
755
  *
561
756
  * #1719: a `ConnectError` carries its own code and next action, so it is read
562
757
  * rather than guessed. The regex ladder below survives only for the refusals
563
758
  * that are still plain `Error`s — every new failure mode joins the vocabulary
564
759
  * instead of joining that ladder.
760
+ *
761
+ * #2091: the record now carries the redacted message rather than omitting it.
762
+ * The old stance — terse because "messages can contain server or filesystem
763
+ * detail" — left automation with a code and nothing to act on; the field
764
+ * failure was a Codex agent staring at `connect_failed` with, in its own
765
+ * words, "no additional safe error detail". Sanitize, don't silence.
565
766
  */
566
767
  declare function failedConnectOutcome(runtimeHint: string | undefined, error: unknown): ConnectOutcome;
567
768
  /**
@@ -600,6 +801,17 @@ interface ParsedCli {
600
801
  reason?: string;
601
802
  replacedBy?: string;
602
803
  };
804
+ /**
805
+ * #2169: unwire one agent — tombstone it, then remove its MCP pair and
806
+ * Hermes dotenv key from every runtime config. No token required; refuses
807
+ * rather than guess when a bare pair is owned by a different agent.
808
+ */
809
+ unwire?: {
810
+ reason?: string;
811
+ replacedBy?: string;
812
+ };
813
+ /** Optional positional value of --unwire <dir> (else --name / --credentials-dir resolve it). */
814
+ unwireDir?: string;
603
815
  /**
604
816
  * #1700: replace an agent's signing key on this machine. Two phases, because
605
817
  * the dashboard sits between them — `start` generates the key and prints its
@@ -634,74 +846,44 @@ declare function shortAddress(address: string): string;
634
846
  * Codes are additive and never renamed: a consumer pinned to an older
635
847
  * connector must keep recognising the ones it already knows.
636
848
  */
849
+ interface ConnectErrorDetails {
850
+ /**
851
+ * The `--runtime` values a retry may use, when the refusal is about runtime
852
+ * selection (#2091). The values lived only in `message` prose, which the
853
+ * `--json` contract discards entirely — while the backend's setup prompt
854
+ * instructs an agent to retry only with "one of the values that refusal
855
+ * lists". Carrying them structurally is what makes that retry reachable
856
+ * from automation.
857
+ */
858
+ allowedRuntimes?: readonly string[];
859
+ /**
860
+ * What the #1719 installed-client scan found on this machine (#2174), when
861
+ * the refusal is `runtime_undetermined`. `installedClients` is ordered
862
+ * likeliest-first; `suggestedRuntime` appears only when one candidate is
863
+ * unambiguously top.
864
+ *
865
+ * A HINT, never a decision. The scan populates choices and never selects —
866
+ * carrying it here does not let the connector proceed on it, and the retry
867
+ * stays the agent's explicit `--runtime <name>`.
868
+ */
869
+ installedClients?: readonly string[];
870
+ suggestedRuntime?: string;
871
+ }
637
872
  declare class ConnectError extends Error {
638
873
  readonly code: string;
639
874
  readonly nextAction: string;
640
- constructor(code: string, message: string, nextAction: string);
875
+ readonly details: ConnectErrorDetails;
876
+ constructor(code: string, message: string, nextAction: string, details?: ConnectErrorDetails);
641
877
  }
642
878
  declare function isConnectError(err: unknown): err is ConnectError;
643
879
 
644
- /**
645
- * Installed-client scan + interactive pick (#1719).
646
- *
647
- * The rung that answers "which runtime am I configuring?" for a HUMAN in a
648
- * plain terminal, where there is no agent shell to detect. Two properties hold
649
- * it up, and both are load-bearing:
650
- *
651
- * 1. **Only clients the connector can actually write.** A row that cannot be
652
- * configured is not a choice, it is a dead end wearing a choice's clothes.
653
- * 2. **The scan populates the choices; it never selects.** Finding exactly one
654
- * installed app tells you what EXISTS, not where the user wants their agent
655
- * to run — and the cost of being wrong is an API key and a delegate key
656
- * written into an app the user does not use. `scanInstalledClients` returns
657
- * candidates and nothing else; only an answer typed at the prompt resolves
658
- * a runtime.
659
- */
660
- interface InstalledClientCandidate {
661
- runtime: RuntimeId;
662
- label: string;
663
- /** What made this a candidate — shown at the prompt so the pick is informed. */
664
- detail: string;
665
- /**
666
- * The config file Haven would write for this client, when it owns one.
667
- * `null` for Claude Code, which is configured through its own CLI. Set
668
- * regardless of which evidence found the client — `evidence` is what says
669
- * whether that file exists today.
670
- */
671
- configPath: string | null;
672
- evidence: 'config-file' | 'client-directory';
673
- }
674
- interface ScanInstalledClientsOptions {
675
- homeDir?: string;
676
- /** Workspace root, for the project-local `.vscode/` marker. */
677
- cwd?: string;
678
- env?: NodeJS.ProcessEnv;
679
- /** Injectable so the scan is testable without a populated home directory. */
680
- exists?: (path: string) => Promise<boolean>;
681
- }
682
- declare function scanInstalledClients(options?: ScanInstalledClientsOptions): Promise<InstalledClientCandidate[]>;
683
- interface PromptIo {
684
- write: (text: string) => void;
685
- /** Resolves the typed line, or `null` on EOF / Ctrl-C. */
686
- question: (query: string) => Promise<string | null>;
687
- }
688
- declare function promptForInstalledClient(candidates: readonly InstalledClientCandidate[], io?: PromptIo): Promise<RuntimeId>;
689
- /**
690
- * The whole rung as one thunk: scan, refuse if nothing writable is installed,
691
- * otherwise prompt. This is what `resolveRuntimeSelection` calls, which is why
692
- * the registry needs no knowledge of the filesystem or of readline.
693
- */
694
- declare function resolveRuntimeByInstalledClientPrompt(options?: ScanInstalledClientsOptions & {
695
- io?: PromptIo;
696
- }): Promise<RuntimeId>;
697
-
698
880
  declare const MCP_RUNTIME_MANIFEST: {
699
881
  readonly mcpPackage: "@haven_ai/mcp";
700
- readonly mcpVersion: "0.1.30-alpha.0";
882
+ readonly mcpVersion: "0.1.32-alpha.0";
701
883
  readonly sdkPackage: "@haven_ai/sdk";
702
- readonly sdkVersion: "0.1.30-alpha.0";
884
+ readonly sdkVersion: "0.1.32-alpha.0";
703
885
  readonly signerPackage: "@haven_ai/signer";
704
- readonly signerVersion: "0.1.30-alpha.0";
886
+ readonly signerVersion: "0.1.32-alpha.0";
705
887
  readonly minimumNodeVersion: "22.0.0";
706
888
  readonly supportedClients: readonly ["codex-cli", "codex-desktop", "claude-code"];
707
889
  readonly requiredTools: readonly string[];
@@ -717,4 +899,4 @@ declare function mcpPackageSpec(): string;
717
899
  declare function sdkPackageSpec(): string;
718
900
  declare function signerPackageSpec(): string;
719
901
 
720
- export { CONNECTOR_VERSION, CONNECT_OUTCOME_SCHEMA_VERSION, type ConnectApiClient, type ConnectDeps, ConnectError, type ConnectOptions, type ConnectOutcome, type ConnectOutcomeStatus, type ConnectResult, type InstalledClientCandidate, type LocalDelegateKey, MCP_RUNTIME_MANIFEST, type ParsedCli, type PrepareSignerRuntimeInput, type PreparedSignerRuntime, type PromptIo, RUNTIME_FLAG_VALUES, type RegisterSetupInput, type RegisterSetupResponse, type ResolveSetupInput, type ResolvedSetup, type RuntimeId, type RuntimeInstallInput, type RuntimeInstallResult, type RuntimeProfile, type RuntimeResolutionOptions, type RuntimeSelection, type ScanInstalledClientsOptions, type StoredCredentialPaths, type UpdateInstallStatusInput, type WriteCredentialInput, completionOutcome, createConnectApiClient, defaultAgentDirectory, delegateKeyFromPrivateKey, failedConnectOutcome, generateDelegateKey, helpText, installRuntime, isConnectError, mcpPackageSpec, normalizeRuntime, parseArgs, prepareSignerRuntime, promptForInstalledClient, redactSecrets, resolveRuntimeByInstalledClientPrompt, resolveRuntimeSelection, runConnect, runtimeInstallCapabilities, runtimeProfile, scanInstalledClients, sdkPackageSpec, shortAddress, signerPackageSpec, writeCredentialFiles };
902
+ export { CONNECTOR_VERSION, CONNECT_OUTCOME_FILENAME, CONNECT_OUTCOME_SCHEMA_VERSION, type ConnectApiClient, type ConnectDeps, ConnectError, type ConnectOptions, type ConnectOutcome, type ConnectOutcomeStatus, type ConnectResult, type InstalledClientCandidate, type InstalledClientHint, type LocalDelegateKey, MCP_RUNTIME_MANIFEST, type ParsedCli, type PrepareSignerRuntimeInput, type PreparedSignerRuntime, type PromptIo, RUNTIME_FLAG_VALUES, type RegisterSetupInput, type RegisterSetupResponse, type ResolveSetupInput, type ResolvedSetup, type RuntimeId, type RuntimeInstallInput, type RuntimeInstallResult, type RuntimeProfile, type RuntimeResolutionOptions, type RuntimeSelection, type ScanInstalledClientsOptions, type StoredCredentialPaths, type UpdateInstallStatusInput, type WriteCredentialInput, completionOutcome, createConnectApiClient, defaultAgentDirectory, delegateKeyFromPrivateKey, failedConnectOutcome, failureOutcomeFor, generateDelegateKey, helpText, installRuntime, installedClientHint, isConnectError, mcpPackageSpec, normalizeRuntime, parseArgs, prepareSignerRuntime, promptForInstalledClient, redactSecrets, resolveRuntimeByInstalledClientPrompt, resolveRuntimeSelection, runConnect, runtimeInstallCapabilities, runtimeProfile, scanInstalledClients, sdkPackageSpec, shortAddress, signerPackageSpec, writeConnectOutcomeRecord, writeCredentialFiles };