@haven_ai/connect 0.1.34-alpha.0 → 0.1.35-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
@@ -50,6 +50,14 @@ interface RegisterSetupInput extends ResolveSetupInput {
50
50
  * of those may render as unknown.
51
51
  */
52
52
  mcpServerName?: string;
53
+ /**
54
+ * #2528: how this run was invoked — `'json'` when `--json` was passed,
55
+ * `'prose'` otherwise. The connector is the only party that can report it:
56
+ * the register call is byte-identical over the wire either way, so there is
57
+ * no header, user agent or timing signal the backend could recover it from.
58
+ * Optional on the wire so an older backend simply ignores it.
59
+ */
60
+ runMode?: 'json' | 'prose';
53
61
  connectorContext?: ConnectorContext;
54
62
  installCapabilities?: {
55
63
  canWriteRuntimeConfig?: boolean;
@@ -77,6 +85,25 @@ interface UpdateInstallStatusInput {
77
85
  nextUserAction: string;
78
86
  errorCode?: string | null;
79
87
  environmentLabel?: string;
88
+ /**
89
+ * The other agent directories this machine holds, so the DASHBOARD can offer
90
+ * the owner a one-click revoke of what this setup superseded (#2561).
91
+ *
92
+ * A tri-state, and the middle case is why it is not a plain array:
93
+ *
94
+ * - a list — the scan ran and found these;
95
+ * - `[]` — the scan ran and found none;
96
+ * - `null` or absent — the scan could not run.
97
+ *
98
+ * The connector cannot revoke and must not: `POST /agents/:id/revoke` is
99
+ * owner-authenticated, and an agent credential retiring a sibling agent is
100
+ * the "agent editing its own authority" the re-key routes already refuse.
101
+ * So this reports, and a human clicks.
102
+ *
103
+ * `null` matters because the alternative is a dashboard telling somebody
104
+ * "nothing to revoke" about a machine nobody managed to read.
105
+ */
106
+ supersededAgentIds?: readonly string[] | null;
80
107
  }
81
108
  interface ConnectorContext {
82
109
  environment_label?: string;
@@ -121,6 +148,13 @@ interface RegisterSetupResponse {
121
148
  delegate_address: string;
122
149
  hosted_mcp_url: string;
123
150
  next_action: string;
151
+ /**
152
+ * #2528: the absolute link that lands the user on this setup's budget
153
+ * approval. Optional because a backend older than #2528 does not send it,
154
+ * and the connector must degrade to its previous prose rather than print
155
+ * `undefined` — see `approvalCtaLine`.
156
+ */
157
+ approval_url?: string;
124
158
  }
125
159
  /**
126
160
  * The agent identity behind an API key (#1700).
@@ -271,7 +305,7 @@ interface RuntimeResolutionOptions {
271
305
  /**
272
306
  * Runtime resolution, detection-first (#1672) and self-resolving (#1719).
273
307
  *
274
- * The setup command carries no `--runtime`; the connector works out the
308
+ * The connector command carries no `--runtime`; the connector works out the
275
309
  * runtime it is executing inside. Precedence:
276
310
  *
277
311
  * 1. `--runtime-force <name>` — always wins (unknown name refuses).
@@ -314,6 +348,94 @@ interface LocalMcpProbeResult {
314
348
  toolNames?: string[];
315
349
  }
316
350
 
351
+ /**
352
+ * Local runtime-spec override (#2424, slice 4 of epic #2420).
353
+ *
354
+ * `prepareSignerRuntime` and `prepareLocalMcpRuntime` install the connector's
355
+ * PINNED siblings — `@haven_ai/signer@<pin>`, `@haven_ai/sdk@<pin>`,
356
+ * `@haven_ai/mcp@<pin>` from `runtime-manifest.ts` — into a version-named
357
+ * directory under `~/.haven`. That is right for every user and wrong for the
358
+ * one developer iterating on the signer or SDK, who until now had to publish
359
+ * to find out whether a change works end to end.
360
+ *
361
+ * Three environment variables let that developer name a different spec:
362
+ *
363
+ * HAVEN_SIGNER_SPEC what `npm install` gets instead of `@haven_ai/signer@<pin>`
364
+ * HAVEN_SDK_SPEC … instead of `@haven_ai/sdk@<pin>`
365
+ * HAVEN_MCP_SPEC … instead of `@haven_ai/mcp@<pin>` (the `--local` topology)
366
+ *
367
+ * A value is anything `npm install` accepts as a package spec whose name is
368
+ * fixed by the variable: `file:/abs/path/to/packages/signer`, a `.tgz` from
369
+ * `npm pack`, or an explicit version such as `@haven_ai/signer@0.0.0-dev.…`.
370
+ *
371
+ * ## Why environment variables and not a `--runtime-spec` flag
372
+ *
373
+ * - The install runs from THREE entry points — `--setup`, `--doctor --repair`
374
+ * and `--rekey-finish` — and every one of them has to honour the same
375
+ * override or the developer's re-key silently reinstalls the registry build.
376
+ * One env read inside the two `prepare*Runtime` functions covers all three;
377
+ * a flag would have to be plumbed through each argv parser and each result
378
+ * type separately, and the first one forgotten is a hole.
379
+ * - The connector command is minted by the dashboard and pasted verbatim, often by
380
+ * an agent. An override that lived in argv would have to be spliced into a
381
+ * command the developer did not write; an env var sits beside it.
382
+ * - It is out of band by construction: an agent following the printed command
383
+ * cannot pass it by accident, and it never appears in the copy-pasted line.
384
+ * - It matches the vocabulary the backend already uses for the same axis
385
+ * (`HAVEN_CONNECTOR_CHANNEL`, #2422/#2423).
386
+ *
387
+ * ## What an active override changes, and what it never changes
388
+ *
389
+ * - The runtime directory is keyed by a short hash of the RESOLVED specs
390
+ * (`override-<hash>`) instead of the manifest version, so an override can
391
+ * never poison the pinned directory the normal path reuses.
392
+ * - An override install is never reused from an earlier run: a rebuilt local
393
+ * package must not be shadowed by a cache hit, and `npm install` of an
394
+ * already-satisfied spec is cheap.
395
+ * - The override is printed loudly in setup output, recorded in the sidecar,
396
+ * written as a comment into the wrapper the agent client launches, and
397
+ * reported by `--doctor` as a failing check.
398
+ * - The post-setup handshake probe still requires every tool in
399
+ * `requiredTools` / `requiredSignerTools`, so a local build that dropped a
400
+ * tool fails setup exactly like a bad registry version would.
401
+ * - No variable set → nothing here runs. `resolveRuntimeSpecOverride` returns
402
+ * `undefined`, the callers fall through to the manifest specs, and the
403
+ * install args, directory, sidecar and wrapper are byte-for-byte what they
404
+ * were before this module existed. `signer-runtime.test.ts` pins that.
405
+ *
406
+ * ## Validation
407
+ *
408
+ * A set-but-malformed value is refused BEFORE `npm` is invoked, naming the
409
+ * variable: empty or whitespace-only, containing whitespace or a control
410
+ * character, or containing a shell metacharacter. The install runs through
411
+ * `execFile` (no shell), so a metacharacter could not inject anything — the
412
+ * refusal is because such a value is almost never a spec npm would accept, and
413
+ * when it is not, the failure would otherwise surface minutes later as an npm
414
+ * error that does not name the variable. The one legitimate casualty is a
415
+ * `file:` path whose segments contain `*`, `[`, `]`, `{` or `}` — legal in a
416
+ * filesystem name, refused here on purpose: the check fails closed, the
417
+ * message names the variable, and a developer can rename the checkout.
418
+ */
419
+ declare const RUNTIME_SPEC_ENV: {
420
+ readonly signer: "HAVEN_SIGNER_SPEC";
421
+ readonly sdk: "HAVEN_SDK_SPEC";
422
+ readonly mcp: "HAVEN_MCP_SPEC";
423
+ };
424
+ type RuntimeSpecPackage = keyof typeof RUNTIME_SPEC_ENV;
425
+ /** Every override that is set — absent keys mean "the pinned manifest spec". */
426
+ type RuntimeSpecOverride = Partial<Record<RuntimeSpecPackage, string>>;
427
+ /**
428
+ * The record written into the runtime sidecar and read back by `--doctor`.
429
+ * `specs` holds only the overridden packages; `resolved_specs` holds every
430
+ * spec the install actually ran with, overridden or pinned, in the order the
431
+ * directory key hashes them.
432
+ */
433
+ interface RuntimeSpecOverrideRecord {
434
+ specs: RuntimeSpecOverride;
435
+ resolved_specs: string[];
436
+ directory_key: string;
437
+ }
438
+
317
439
  interface PrepareLocalMcpRuntimeInput {
318
440
  credentialDirectory: string;
319
441
  identityPath: string;
@@ -331,6 +453,8 @@ interface PreparedLocalMcpRuntime {
331
453
  npmCacheDirectory: string;
332
454
  cliPath: string;
333
455
  messages: string[];
456
+ /** #2424: present only when the install ran under a runtime-spec override. */
457
+ runtimeSpecOverride?: RuntimeSpecOverrideRecord;
334
458
  }
335
459
 
336
460
  interface PrepareSignerRuntimeInput {
@@ -350,11 +474,19 @@ interface PreparedSignerRuntime {
350
474
  npmCacheDirectory: string;
351
475
  cliPath: string;
352
476
  messages: string[];
477
+ /** #2424: present only when the install ran under a runtime-spec override. */
478
+ runtimeSpecOverride?: RuntimeSpecOverrideRecord;
353
479
  }
354
480
  interface SignerRuntimeDeps {
355
481
  runCommand?: (command: string, args: string[]) => Promise<void>;
356
482
  /** Heartbeats during the (possibly minutes-long) npm install (#1586). */
357
483
  onProgress?: (message: string) => void;
484
+ /**
485
+ * #2424: where `HAVEN_SIGNER_SPEC` / `HAVEN_SDK_SPEC` are read from.
486
+ * Defaults to `process.env`; injected so tests can prove both that an
487
+ * override is honoured and that its absence changes nothing.
488
+ */
489
+ env?: NodeJS.ProcessEnv;
358
490
  }
359
491
  /**
360
492
  * Pre-install the edge signer into a version-pinned, connector-managed
@@ -573,7 +705,31 @@ declare function resolveRuntimeByInstalledClientPrompt(options?: ScanInstalledCl
573
705
  io?: PromptIo;
574
706
  }): Promise<RuntimeId>;
575
707
 
576
- declare const CONNECTOR_VERSION = "0.1.34-alpha.0";
708
+ interface SupersededDirectory {
709
+ directory: string;
710
+ /** Agent id from `identity.json`, or the directory name when it is missing. */
711
+ agentId: string;
712
+ }
713
+ interface WiringCollision {
714
+ /** Every bare-pair directory holding a usable key — what this run would displace. */
715
+ superseded: readonly SupersededDirectory[];
716
+ /**
717
+ * A valid, collision-checked slug the user can take to install alongside
718
+ * instead. Derived from the agent name Haven resolved for this setup, so
719
+ * the proposal reads as the agent rather than as a counter.
720
+ */
721
+ suggestedServerName: string;
722
+ }
723
+ type WiringCollisionResolution = {
724
+ action: 'replace';
725
+ } | {
726
+ action: 'alongside';
727
+ serverName: string;
728
+ } | {
729
+ action: 'abort';
730
+ };
731
+
732
+ declare const CONNECTOR_VERSION = "0.1.35-alpha.0";
577
733
  interface ConnectOptions {
578
734
  setupToken: string;
579
735
  apiBaseUrl: string;
@@ -597,18 +753,43 @@ interface ConnectOptions {
597
753
  environmentLabel?: string;
598
754
  /** #1696: wiring slug for a named MCP pair + slug-keyed credential dir. */
599
755
  serverName?: string;
756
+ /**
757
+ * #2551: when the bare pair on this machine is already wired to a different
758
+ * agent with a live key, replace it — re-point `haven` / `haven-signer` at
759
+ * the new agent (the #1569 remove-first install) and retire the superseded
760
+ * directory locally afterwards. Without it, such a collision is put to a
761
+ * human at a TTY and REFUSED everywhere else (`wiring_collision`), before
762
+ * anything is minted or written. Contradicts `serverName`, which installs
763
+ * alongside instead; the CLI refuses the pair.
764
+ */
765
+ replaceExistingWiring?: boolean;
600
766
  connectorVersion?: string;
601
767
  ackSigner?: boolean;
602
768
  ackLocalTools?: boolean;
603
769
  localMcp?: boolean;
604
770
  /**
605
771
  * #1377 D: keep the process alive after registering and poll for the
606
- * user's budget approval (default). Set false for structured/automation
607
- * runs (--json) where prompt output emission matters more than narration.
772
+ * user's budget approval. Set false for structured/automation runs (--json)
773
+ * where prompt output emission matters more than narration. #2484: when
774
+ * UNSPECIFIED (the default), the wait runs only when stdout is a TTY — the
775
+ * wait exists to narrate to a watching human, and a prose run whose stdout
776
+ * is not a TTY (an agent tool call) skips it. An explicit true waits
777
+ * regardless of TTY.
608
778
  */
609
779
  waitForApproval?: boolean;
610
780
  /** Test/injection overrides for the approval poll cadence and clock. */
611
781
  approvalWait?: ApprovalWaitOptions;
782
+ /**
783
+ * #2528: how this run was invoked, reported to the backend at register so
784
+ * the onboarding funnel can segment machine-readable runs from narrated
785
+ * ones (D1, #2529).
786
+ *
787
+ * An explicit option rather than an inference from `waitForApproval`: that
788
+ * flag is already false for a prose run with no TTY (#2484) and can be set
789
+ * false by hand, so reading `--json` off it would mislabel real prose runs.
790
+ * Defaults to `'prose'` — the mode a caller who says nothing is in.
791
+ */
792
+ runMode?: 'json' | 'prose';
612
793
  }
613
794
  /** The stable machine-readable result emitted by `haven-connect --json`. */
614
795
  declare const CONNECT_OUTCOME_SCHEMA_VERSION: 1;
@@ -631,9 +812,19 @@ interface ConnectOutcome {
631
812
  instruction: string;
632
813
  };
633
814
  next_action: string;
815
+ /**
816
+ * `url` is #2528, additive within schema_version 1 — the same convention
817
+ * #2173, #2174, #2551, #2091 and #2279 followed for this object. Present
818
+ * only when the backend returned one: a deployment older than #2528 sends
819
+ * nothing, and a key whose value is `undefined` disappears from
820
+ * `JSON.stringify` anyway, so a consumer must test for it rather than
821
+ * assume it. `required: false` runs carry no url — there is nothing to
822
+ * approve.
823
+ */
634
824
  approval: {
635
825
  required: boolean;
636
826
  expires_at: string | null;
827
+ url?: string;
637
828
  };
638
829
  verification: {
639
830
  tools: readonly ['haven_get_agent', 'haven_get_allowances'];
@@ -664,6 +855,25 @@ interface ConnectOutcome {
664
855
  */
665
856
  hosted_mcp_url?: string;
666
857
  superseded_agent_ids?: readonly string[];
858
+ /**
859
+ * #2551, additive within schema_version 1. Present only on a run that
860
+ * REPLACED existing wiring (`--replace`, or "replace" chosen at the
861
+ * prompt): `true` when every superseded directory was retired locally —
862
+ * tombstoned and its local key files removed — and `false` when the
863
+ * runtime install ended with an errorCode and the retirement was therefore
864
+ * skipped, because the old wiring may still be the only working one. It
865
+ * says nothing about the backend: nothing is revoked by the connector.
866
+ *
867
+ * `retired_agent_ids` names exactly WHICH directories that retirement
868
+ * reached — the collision set, and only the members whose tombstone and
869
+ * teardown both succeeded. It is deliberately a separate list from
870
+ * `superseded_agent_ids`, which is every OTHER directory on the machine
871
+ * (#1688) and so also names NAMED agents that coexist with the replaced
872
+ * bare pair and were never touched; reading the boolean against that list
873
+ * would overclaim (review finding on #2551).
874
+ */
875
+ superseded_agents_retired_locally?: boolean;
876
+ retired_agent_ids?: readonly string[];
667
877
  /**
668
878
  * #2091, additive within schema_version 1: `message` is the redacted human
669
879
  * refusal (automation used to get code + next_action and nothing to act
@@ -692,6 +902,15 @@ interface ConnectOutcome {
692
902
  */
693
903
  installed_clients?: readonly string[];
694
904
  suggested_runtime?: string;
905
+ /**
906
+ * #2551, additive within schema_version 1, on a `wiring_collision`
907
+ * refusal: the agents a bare-pair setup would have displaced, and a
908
+ * valid, collision-checked `--name` the run can be re-issued with. The
909
+ * refusal is a RELAY instruction — the human chooses replace-vs-alongside
910
+ * — so the record carries what that human needs, never a default.
911
+ */
912
+ superseded_agent_ids?: readonly string[];
913
+ suggested_name?: string;
695
914
  };
696
915
  }
697
916
  interface ConnectDeps {
@@ -710,8 +929,22 @@ interface ConnectDeps {
710
929
  env?: NodeJS.ProcessEnv;
711
930
  /** Overridable so the #1719 TTY gate is testable without faking process.stdin. */
712
931
  isTty?: boolean;
932
+ /**
933
+ * Overridable so the #2484 non-TTY narration gate is testable without
934
+ * faking process.stdout. Distinct from `isTty`: that gate decides whether
935
+ * the stdin prompt rung can run (INTERACTIVITY); this one decides whether
936
+ * the budget-approval wait has a watching human to narrate to, so it is
937
+ * keyed on STDOUT.
938
+ */
939
+ isStdoutTty?: boolean;
713
940
  /** Overridable so the #1719 installed-client prompt is testable without readline. */
714
941
  promptRuntime?: () => Promise<RuntimeId>;
942
+ /**
943
+ * Overridable so the #2551 replace-vs-alongside prompt is testable without
944
+ * readline. Reached only through the same `interactive` + TTY gate as
945
+ * `promptRuntime`; a `--json` or piped run never gets here.
946
+ */
947
+ promptWiringCollision?: (collision: WiringCollision, agentName: string) => Promise<WiringCollisionResolution>;
715
948
  /**
716
949
  * Overridable so the #2173 recovery-record write is testable without a real
717
950
  * credential directory — and so a FAILING writer can be injected to prove
@@ -747,8 +980,12 @@ declare function completionOutcome(input: {
747
980
  delegateAddress: string;
748
981
  hostedMcpUrl?: string;
749
982
  supersededAgentIds?: readonly string[];
983
+ /** #2551: only a replace run sets these; see the outcome fields' doc comments. */
984
+ supersededAgentsRetiredLocally?: boolean;
985
+ retiredAgentIds?: readonly string[];
750
986
  setupChallengeExpiresAt?: string;
751
987
  approvalRequired: boolean;
988
+ approvalUrl?: string;
752
989
  }): ConnectOutcome;
753
990
  /**
754
991
  * The failure record for the `--json` contract.
@@ -868,6 +1105,19 @@ interface ConnectErrorDetails {
868
1105
  */
869
1106
  installedClients?: readonly string[];
870
1107
  suggestedRuntime?: string;
1108
+ /**
1109
+ * #2551: the agents a bare-pair setup would displace, when the refusal is
1110
+ * `wiring_collision`. Carried structurally for the same reason as
1111
+ * `allowedRuntimes`: a `--json` caller discards prose, and the ids are what
1112
+ * the human it relays to needs in order to decide.
1113
+ */
1114
+ supersededAgentIds?: readonly string[];
1115
+ /**
1116
+ * #2551: a valid, collision-checked `--name` the run could be re-issued
1117
+ * with to install alongside. A proposal the human may take, never a
1118
+ * selection the connector acts on.
1119
+ */
1120
+ suggestedServerName?: string;
871
1121
  }
872
1122
  declare class ConnectError extends Error {
873
1123
  readonly code: string;
@@ -879,11 +1129,11 @@ declare function isConnectError(err: unknown): err is ConnectError;
879
1129
 
880
1130
  declare const MCP_RUNTIME_MANIFEST: {
881
1131
  readonly mcpPackage: "@haven_ai/mcp";
882
- readonly mcpVersion: "0.1.34-alpha.0";
1132
+ readonly mcpVersion: "0.1.35-alpha.0";
883
1133
  readonly sdkPackage: "@haven_ai/sdk";
884
- readonly sdkVersion: "0.1.34-alpha.0";
1134
+ readonly sdkVersion: "0.1.35-alpha.0";
885
1135
  readonly signerPackage: "@haven_ai/signer";
886
- readonly signerVersion: "0.1.34-alpha.0";
1136
+ readonly signerVersion: "0.1.35-alpha.0";
887
1137
  readonly minimumNodeVersion: "22.0.0";
888
1138
  readonly supportedClients: readonly ["codex-cli", "codex-desktop", "claude-code"];
889
1139
  readonly requiredTools: readonly string[];