@haven_ai/connect 0.0.0-dev.202610091522.ece043a → 0.0.0-dev.202610091612.d805017

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
@@ -144,7 +144,7 @@ report stays `version: 1`; `level` is additive.
144
144
  | Level | Meaning | Example |
145
145
  | --- | --- | --- |
146
146
  | `ok` | Nothing to say. | The installed signer matches the connector's pin. |
147
- | `advisory` | Worth reading; nothing is broken. Exit 0. | `signer_runtime`: the install is intact but behind the pinned version ("intact, but outdated" — both versions named, `--repair` offered); `signer_runtime_unused` (#3123): runtime directories nothing references; `mcp_server_name_rebound` (#3122): two local binding records claim one server name. `superseded_agents` on a recognised runtime with no config file the connector can read (Claude Code, `other`): a second live key is reported, and the check says why "wired" cannot be verified from this machine. |
147
+ | `advisory` | Worth reading; nothing is broken. Exit 0. | `signer_runtime`: the install is intact but behind the pinned version ("intact, but outdated" — both versions named, `--repair` offered); `signer_runtime_unused` (#3123): runtime directories nothing references; `mcp_server_name_rebound` (#3122): two local binding records claim one server name. `superseded_agents` on a recognised runtime with no config file the connector can read (Claude Code, `other`): a second live key is reported, and the check — labelled "Other agent credentials (wiring not verifiable)" — says why "wired" cannot be verified from this machine. On Claude Code a pair named in `~/.claude.json` counts as wired (#3830), so only directories that file does not name reach this advisory, and the inventory row reads "not verified as wired" rather than "superseded"; a directory it upgrades runs the full per-agent checks and can fail the run. |
148
148
  | `failed` | A real failure with one repair action. Exit 1. | A stale or empty runtime directory; a live key in a directory the runtime's config demonstrably does not use. |
149
149
 
150
150
  What stays blocking is live spend authority: a directory whose stored key
@@ -320,10 +320,10 @@ long-lived host afterwards, as with any retirement.
320
320
 
321
321
  Setup guards the **name slot** (a taken `--name`, the bare pair already wired
322
322
  — #2551 refuses or asks) but never the **wallet**: a machine can carry several
323
- agents with live keys, and until #3122 the "your previous agent(s) still exist
324
- with their own keys" heads-up was printed only after the credentials were
325
- written (that completion heads-up, #1688, is unchanged — this slice ADDS an
326
- earlier notice). Two things changed, both from local files only (no network call is
323
+ agents with live keys, and until #3122 the heads-up naming the other agent
324
+ directories was printed only after the credentials were written (that
325
+ completion heads-up, #1688, still prints — reworded by #3830, see below — and
326
+ this slice ADDS an earlier notice). Two things changed, both from local files only (no network call is
327
327
  added; the backend is not asked whether any key still authenticates):
328
328
 
329
329
  - **Before the key is minted or anything is written**, setup names every
@@ -418,21 +418,29 @@ deployment, so the two differing is intended topology, not an environment
418
418
  mismatch. It is non-secret: the same string goes into your own MCP config file,
419
419
  and the API key travels beside it in a header.
420
420
 
421
- `superseded_agent_ids` lists the other agent directories on this machine. A
422
- re-run mints a NEW agent, and without `--replace` retires nothing, so those
423
- older agents still hold live API and signing keys — use **Remove agent…** on the
424
- Haven agent page for each if you meant to replace them. Empty on a clean first run; an empty
425
- list here is not a guarantee, since a scan that cannot read the credential root
426
- also yields one rather than failing a completed setup.
427
-
428
- **The dashboard offers the revoke, never the connector (#2561).** The same ids
429
- now ride the install-status report, so the Haven dashboard can put a
430
- revoke next to the setup that displaced them; it also ends the agent's budget
431
- with one owner signature (#3542). The connector does not
432
- and must not do it: `POST /agents/:id/revoke` is owner-authenticated, and an
433
- agent credential retiring a sibling agent is the "agent editing its own
434
- authority" the re-key routes refuse. Nothing is revoked automatically — the
435
- owner clicks, one agent at a time.
421
+ `superseded_agent_ids` lists the other agent directories on this machine —
422
+ every one, key-less and tombstoned included, on any backend. Despite the field
423
+ name it is **not** a replace set (#3830): since named pairs became the default
424
+ (#3737), a run without `--replace` adds its agent alongside the ones already
425
+ here and retires nothing, so most listed agents are simply other agents, many
426
+ of them wired and in use. Agents a `--replace` run actually retired are
427
+ `retired_agent_ids`. The completion heads-up names the list as "other agent
428
+ directories on this machine" and says this setup revoked none of them; if you
429
+ no longer use one, use **Remove agent…** on its Haven agent page. Empty on a
430
+ clean first run; an empty list here is not a guarantee, since a scan that
431
+ cannot read the credential root also yields one rather than failing a
432
+ completed setup.
433
+
434
+ **The dashboard lists them; revoking stays on each agent's page (#2561,
435
+ #3830).** The same ids ride the install-status report, and the connect modal's
436
+ completed state lists those that are the owner's agents and not revoked
437
+ (active, paused or still in setup), each linking to its agent page. It
438
+ does not call them replaced and offers no revoke there (owner decision,
439
+ 2026-10-09): the report cannot tell a coexisting agent
440
+ from a retired one. The connector never revokes either: `POST
441
+ /agents/:id/revoke` is owner-authenticated, and an agent credential retiring a
442
+ sibling agent is the "agent editing its own authority" the re-key routes
443
+ refuse.
436
444
 
437
445
  On the REPORT the field is a tri-state, unlike the `--json` outcome above: a
438
446
  list, `[]` when the scan ran and found none, and `null` when it could not run.
@@ -753,7 +761,8 @@ repair spelled out: use **Remove agent…** on the Haven agent page, then remove
753
761
  the directory. An already-revoked one reports as informational; an unreachable
754
762
  probe is a note, never a verdict. Connect never revokes or deletes
755
763
  credentials itself — it reports, you decide. The setup completion output
756
- names superseded agents the moment they are created, for the same reason.
764
+ names the other agent directories the moment the new agent is created, for
765
+ the same reason.
757
766
  One honest limit: "newest" is decided by file mtime, so a restored backup or
758
767
  a sync tool that rewrites timestamps can make doctor examine the wrong
759
768
  directory as current — before revoking anything, confirm the agent id
package/dist/cli.cjs CHANGED
@@ -257,9 +257,9 @@ var init_runtime_manifest = __esm({
257
257
  mcpPackage: "@haven_ai/mcp",
258
258
  mcpVersion: mcp.MCP_VERSION,
259
259
  sdkPackage: "@haven_ai/sdk",
260
- sdkVersion: "0.0.0-dev.202610091522.ece043a",
260
+ sdkVersion: "0.0.0-dev.202610091612.d805017",
261
261
  signerPackage: "@haven_ai/signer",
262
- signerVersion: "0.0.0-dev.202610091522.ece043a",
262
+ signerVersion: "0.0.0-dev.202610091612.d805017",
263
263
  // Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
264
264
  // while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
265
265
  // so the guard that was supposed to enforce the floor waved Node v23 through
@@ -4179,10 +4179,10 @@ async function executeConnect(options, deps, trace) {
4179
4179
  } else if (supersededAgentIds.length > 0) {
4180
4180
  log("");
4181
4181
  log(
4182
- `Heads-up: this setup created a NEW agent. Your previous agent(s) \u2014 ${supersededAgentIds.join(", ")} \u2014 still exist with their own keys, and any host that was already running keeps acting as them.`
4182
+ `Heads-up: other agent directories on this machine \u2014 ${supersededAgentIds.join(", ")}. This setup created a NEW agent and revoked none of them; any not revoked in Haven keep their own keys and any budgets.`
4183
4183
  );
4184
4184
  log(
4185
- `If you meant to replace them: use Remove agent\u2026 on the Haven agent page for each (it ends their live budgets), then restart EVERY long-lived host (gateways, TUI workers, editors) \u2014 each holds the MCP wiring snapshot from its own start time, so after repeated setups each can be stuck on a DIFFERENT old agent. Then remove their directories under ~/.haven/agents (or ${RERUN_HINT} --tombstone <dir> to leave a diagnostic in their place). Run ${RERUN_HINT} --doctor to check whether their keys are still live.`
4185
+ `To retire one you no longer use: use Remove agent\u2026 on its Haven agent page (it ends its live budget), restart any long-lived host (gateways, TUI workers, editors) still wired to it \u2014 each holds the MCP wiring snapshot from its own start time \u2014 and remove its directory under ~/.haven/agents (or ${RERUN_HINT} --tombstone <dir> to leave a diagnostic in its place). To rewire the bare haven / haven-signer pair instead of adding an agent, re-run with --replace. Run ${RERUN_HINT} --doctor to check which keys are still live.`
4186
4186
  );
4187
4187
  }
4188
4188
  } catch {
@@ -4672,7 +4672,7 @@ var init_runtime = __esm({
4672
4672
  init_tombstone();
4673
4673
  init_local_mcp_runtime();
4674
4674
  init_runtime_manifest();
4675
- CONNECTOR_VERSION = "0.0.0-dev.202610091522.ece043a";
4675
+ CONNECTOR_VERSION = "0.0.0-dev.202610091612.d805017";
4676
4676
  PRODUCTION_CHAIN_ID = 8453;
4677
4677
  CONNECTOR_CLIENT_IDENTITY = `@haven_ai/connect/${CONNECTOR_VERSION}`;
4678
4678
  CONNECT_OUTCOME_SCHEMA_VERSION = 1;
@@ -5269,6 +5269,35 @@ async function discoverCredentialDirectory(homeDir, explicit) {
5269
5269
  identityDirectories
5270
5270
  };
5271
5271
  }
5272
+ async function readClaudeCodeWiringEvidence(homeDir) {
5273
+ let parsed;
5274
+ try {
5275
+ parsed = JSON.parse(await promises.readFile(path.join(homeDir, ".claude.json"), "utf8"));
5276
+ } catch {
5277
+ return null;
5278
+ }
5279
+ if (typeof parsed !== "object" || parsed === null) return null;
5280
+ const blocks = [parsed.mcpServers];
5281
+ const projects = parsed.projects;
5282
+ if (typeof projects === "object" && projects !== null) {
5283
+ for (const project of Object.values(projects)) {
5284
+ if (typeof project === "object" && project !== null) blocks.push(project.mcpServers);
5285
+ }
5286
+ }
5287
+ const lines = [];
5288
+ for (const block of blocks) {
5289
+ if (typeof block !== "object" || block === null) continue;
5290
+ for (const [name, server] of Object.entries(block)) {
5291
+ const entry = typeof server === "object" && server !== null ? server : {};
5292
+ const parts = [JSON.stringify(name) + ":"];
5293
+ if (typeof entry.command === "string") parts.push(entry.command);
5294
+ if (Array.isArray(entry.args)) parts.push(...entry.args.filter((arg) => typeof arg === "string"));
5295
+ if (typeof entry.url === "string") parts.push(entry.url);
5296
+ lines.push(parts.join(" "));
5297
+ }
5298
+ }
5299
+ return lines.length > 0 ? lines.join("\n") : null;
5300
+ }
5272
5301
  function agentIsWired(configText, names, slug, identity, sidecar, isPrimary, bareOwnerExists) {
5273
5302
  if (configText === null) return isPrimary;
5274
5303
  if (slug) {
@@ -5577,6 +5606,7 @@ async function runDoctor(input, deps = {}) {
5577
5606
  configText = null;
5578
5607
  }
5579
5608
  }
5609
+ const claudeCodeWiring = configText === null && normalizedRuntime === "claude-code" ? await readClaudeCodeWiringEvidence(homeDir) : null;
5580
5610
  const allDirectories = directory ? [directory, ...others] : others;
5581
5611
  let bareOwnerExists = false;
5582
5612
  for (const dir of allDirectories) {
@@ -5610,7 +5640,10 @@ async function runDoctor(input, deps = {}) {
5610
5640
  });
5611
5641
  continue;
5612
5642
  }
5613
- const wired = agentIsWired(configText, names, slug, identity, sidecar, dir === directory, bareOwnerExists);
5643
+ const wired = agentIsWired(configText, names, slug, identity, sidecar, dir === directory, bareOwnerExists) || // Additive only: a name or wrapper path in ~/.claude.json proves this
5644
+ // directory is wired; its absence proves nothing (#3830), so the
5645
+ // fallback verdict above is never downgraded.
5646
+ claudeCodeWiring !== null && agentIsWired(claudeCodeWiring, names, slug, identity, sidecar, false, false);
5614
5647
  const entry = {
5615
5648
  ...slug ? { slug } : {},
5616
5649
  ...identity.agent_id ? { agentId: identity.agent_id } : {},
@@ -5765,9 +5798,13 @@ async function runDoctor(input, deps = {}) {
5765
5798
  const supersededLevel = supersededLive.length === 0 ? "ok" : classificationUnreliable ? "advisory" : "failed";
5766
5799
  checks.push({
5767
5800
  id: "superseded_agents",
5768
- label: "Superseded agent credentials",
5801
+ // #3830: an advisory means the connector could not tell wired from
5802
+ // superseded, so its label must not assert "superseded" either. The
5803
+ // inventory `classification` stays as is (#3121 decision 5); the label
5804
+ // is what a person reads.
5805
+ label: supersededLevel === "advisory" ? "Other agent credentials (wiring not verifiable)" : "Superseded agent credentials",
5769
5806
  level: supersededLevel,
5770
- detail: supersededLevel === "failed" ? `${otherEntries.length} other credential dir(s) found \u2014 ${parts.join("; ")}. A host started before your latest setup keeps authenticating (and spending) as the old agent.` : supersededLevel === "advisory" ? `${otherEntries.length} other credential dir(s) found \u2014 ${parts.join("; ")}. Runtime '${input2.runtime}' has no config file the connector can read, so which of these agents are wired cannot be verified from this machine: a live key here may be an agent you use deliberately, or one a host started before your latest setup is still spending as.` : `${otherEntries.length} other credential dir(s) found \u2014 ${parts.join("; ")}.`,
5807
+ detail: supersededLevel === "failed" ? `${otherEntries.length} other credential dir(s) found \u2014 ${parts.join("; ")}. A host started before your latest setup keeps authenticating (and spending) as the old agent.` : supersededLevel === "advisory" ? `${otherEntries.length} other credential dir(s) found \u2014 ${parts.join("; ")}. Runtime '${input2.runtime}' has no config file the connector writes, and none it can read names these agents, so which of them are wired cannot be verified from this machine: a live key here may be an agent you use deliberately, or one a host started before your latest setup is still spending as.` : `${otherEntries.length} other credential dir(s) found \u2014 ${parts.join("; ")}.`,
5771
5808
  ...supersededLevel === "failed" ? {
5772
5809
  repair: `Use Remove agent\u2026 on the Haven agent page for ${supersededLive.join(", ")} (it ends each one\u2019s live budgets), then remove the old director(y/ies) under ~/.haven/agents. Connect never revokes or deletes for you.`
5773
5810
  } : supersededLevel === "advisory" ? {
@@ -6668,13 +6705,16 @@ async function runCli(argv, io = {
6668
6705
  `));
6669
6706
  }
6670
6707
  const otherAgents = report.agents.filter((agent) => agent.directory !== report.credentialDirectory);
6708
+ const wiringUnverifiable = report.checks.some(
6709
+ (check) => check.id === "superseded_agents" && check.level === "advisory"
6710
+ );
6671
6711
  if (otherAgents.length > 0) {
6672
6712
  io.stdout("\nOther agents on this machine:\n");
6673
6713
  for (const agent of otherAgents) {
6674
6714
  const name = agent.slug ? `${agent.slug} (${agent.agentId ?? "unknown"})` : agent.agentId ?? "unknown";
6675
6715
  const failed = agent.checks.filter((check) => check.level === "failed");
6676
6716
  const advised = agent.checks.filter((check) => check.level === "advisory");
6677
- const verdict = agent.classification === "wired" ? failed.length > 0 ? `wired, ${failed.length} check(s) FAILED` : advised.length > 0 ? `wired, ${advised.length} advisory finding(s)` : "wired, all checks passed" : agent.classification === "parked" ? "parked re-key only \u2014 no identity.json in this directory, but key material is still there" : agent.classification;
6717
+ const verdict = agent.classification === "wired" ? failed.length > 0 ? `wired, ${failed.length} check(s) FAILED` : advised.length > 0 ? `wired, ${advised.length} advisory finding(s)` : "wired, all checks passed" : agent.classification === "parked" ? "parked re-key only \u2014 no identity.json in this directory, but key material is still there" : agent.classification === "superseded" && wiringUnverifiable ? "not verified as wired \u2014 no config the connector can read names it" : agent.classification;
6678
6718
  io.stdout(redactSecrets(` ${failed.length > 0 ? "\u2717" : advised.length > 0 ? "!" : "\u2022"} ${name}: ${verdict}
6679
6719
  `));
6680
6720
  for (const check of [...failed, ...advised]) {