@haven_ai/connect 0.5.0-alpha.1 → 0.7.0-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/README.md CHANGED
@@ -703,6 +703,18 @@ which.
703
703
  runtime, rewrite the wrapper + sidecar, and re-write the runtime config from
704
704
  the STORED credentials. It never touches keys and never needs a new token.
705
705
 
706
+ **Several agents on one machine (#3412).** A repair rewrites the runtime config
707
+ from ONE agent's stored credentials. With more than one agent credential
708
+ directory under `~/.haven/agents` — or when `--doctor` was itself given
709
+ `--credentials-dir` — every repair line the doctor prints names the agent it is
710
+ about with `--credentials-dir <dir>`, and a `--repair` without
711
+ `--credentials-dir` is refused — nothing is changed — with one exact command
712
+ per live agent (its agent id beside it); a retired directory is listed as
713
+ "retired — not a repair target" and never offered. Without that, "the newest directory" was a
714
+ guess, and it re-wired a different agent than the one the doctor described.
715
+ Every "update your signer" hint Haven emits names the flagless
716
+ `npx -y @haven_ai/connect@<channel> --doctor`, which then prints that line.
717
+
706
718
  ## Installing an unpublished signer / SDK / MCP build (`HAVEN_SIGNER_SPEC`, #2424)
707
719
 
708
720
  Setup installs the connector's **pinned** siblings — `@haven_ai/signer@<pin>`
package/dist/cli.cjs CHANGED
@@ -620,9 +620,9 @@ var init_runtime_manifest = __esm({
620
620
  mcpPackage: "@haven_ai/mcp",
621
621
  mcpVersion: mcp.MCP_VERSION,
622
622
  sdkPackage: "@haven_ai/sdk",
623
- sdkVersion: "0.5.0-alpha.1",
623
+ sdkVersion: "0.7.0-alpha.0",
624
624
  signerPackage: "@haven_ai/signer",
625
- signerVersion: "0.5.0-alpha.1",
625
+ signerVersion: "0.7.0-alpha.0",
626
626
  // Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
627
627
  // while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
628
628
  // so the guard that was supposed to enforce the floor waved Node v23 through
@@ -4451,7 +4451,7 @@ var init_runtime = __esm({
4451
4451
  init_tombstone();
4452
4452
  init_local_mcp_runtime();
4453
4453
  init_runtime_manifest();
4454
- CONNECTOR_VERSION = "0.5.0-alpha.1";
4454
+ CONNECTOR_VERSION = "0.7.0-alpha.0";
4455
4455
  CONNECTOR_CLIENT_IDENTITY = `@haven_ai/connect/${CONNECTOR_VERSION}`;
4456
4456
  CONNECT_OUTCOME_SCHEMA_VERSION = 1;
4457
4457
  failureOutcomesByError = /* @__PURE__ */ new WeakMap();
@@ -4934,9 +4934,23 @@ function rollUpLevel(checks) {
4934
4934
  function runtimeFlagFor(runtime) {
4935
4935
  return normalizeRuntimeName(runtime) ? ` --runtime ${runtime}` : "";
4936
4936
  }
4937
- function repairCommandFor(runtime, suffix = "") {
4938
- if (normalizeRuntimeName(runtime)) return `Run: ${RERUN} --doctor --repair --runtime ${runtime}${suffix}`;
4939
- return `Run: ${RERUN} --doctor --repair --runtime <runtime>${suffix.replace(/\.$/, "")} \u2014 --repair needs the runtime named; one of: ${RUNTIME_FLAG_VALUE_LIST.join(", ")}.`;
4937
+ async function pathExists3(path) {
4938
+ try {
4939
+ await promises.stat(path);
4940
+ return true;
4941
+ } catch {
4942
+ return false;
4943
+ }
4944
+ }
4945
+ function credentialsDirFlagFor(credentialsDir) {
4946
+ if (!credentialsDir) return "";
4947
+ const quoted = /^[\w@%+=:,./~-]+$/.test(credentialsDir) ? credentialsDir : `'${credentialsDir.replace(/'/g, `'\\''`)}'`;
4948
+ return ` --credentials-dir ${quoted}`;
4949
+ }
4950
+ function repairCommandFor(runtime, suffix = "", credentialsDir) {
4951
+ const dirFlag = credentialsDirFlagFor(credentialsDir);
4952
+ if (normalizeRuntimeName(runtime)) return `Run: ${RERUN} --doctor --repair --runtime ${runtime}${dirFlag}${suffix}`;
4953
+ return `Run: ${RERUN} --doctor --repair --runtime <runtime>${dirFlag}${suffix.replace(/\.$/, "")} \u2014 --repair needs the runtime named; one of: ${RUNTIME_FLAG_VALUE_LIST.join(", ")}.`;
4940
4954
  }
4941
4955
  async function resolveDoctorRuntime(input, directory) {
4942
4956
  const explicit = input.runtime.trim();
@@ -4955,7 +4969,7 @@ async function discoverCredentialDirectory(homeDir, explicit) {
4955
4969
  try {
4956
4970
  entries = await promises.readdir(root);
4957
4971
  } catch {
4958
- return explicit ? { directory: explicit, others: [], parkedOnly: /* @__PURE__ */ new Set() } : { others: [], parkedOnly: /* @__PURE__ */ new Set() };
4972
+ return explicit ? { directory: explicit, others: [], parkedOnly: /* @__PURE__ */ new Set(), identityDirectories: [explicit] } : { others: [], parkedOnly: /* @__PURE__ */ new Set(), identityDirectories: [] };
4959
4973
  }
4960
4974
  const candidates = [];
4961
4975
  const tombstonedOnly = [];
@@ -4980,20 +4994,23 @@ async function discoverCredentialDirectory(homeDir, explicit) {
4980
4994
  }
4981
4995
  const parkedOnlySet = new Set(parkedOnly);
4982
4996
  candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
4997
+ const identityDirectories = candidates.map((c) => c.directory);
4983
4998
  if (explicit) {
4984
4999
  return {
4985
5000
  directory: explicit,
4986
5001
  others: [...candidates.map((c) => c.directory), ...tombstonedOnly, ...parkedOnly].filter((d) => d !== explicit),
4987
- parkedOnly: parkedOnlySet
5002
+ parkedOnly: parkedOnlySet,
5003
+ identityDirectories: [explicit, ...identityDirectories.filter((d) => d !== explicit)]
4988
5004
  };
4989
5005
  }
4990
5006
  if (candidates.length === 0 && tombstonedOnly.length === 0 && parkedOnly.length === 0) {
4991
- return { others: [], parkedOnly: parkedOnlySet };
5007
+ return { others: [], parkedOnly: parkedOnlySet, identityDirectories };
4992
5008
  }
4993
5009
  return {
4994
5010
  directory: candidates[0]?.directory,
4995
5011
  others: [...candidates.slice(1).map((c) => c.directory), ...tombstonedOnly, ...parkedOnly],
4996
- parkedOnly: parkedOnlySet
5012
+ parkedOnly: parkedOnlySet,
5013
+ identityDirectories
4997
5014
  };
4998
5015
  }
4999
5016
  function agentIsWired(configText, names, slug, identity, sidecar, isPrimary, bareOwnerExists) {
@@ -5052,7 +5069,7 @@ function rekeyPendingVerdict(status, hostedDelegateAddress, runtime, slug) {
5052
5069
  detail: `A re-key started ${started} is still open (expires ${status.expiresAt ?? "unknown"}). Paste this address into "Replace signing key" on the Haven agent page: ${address}. Parked at ${status.path}. ` + wedgeNote
5053
5070
  };
5054
5071
  }
5055
- async function runtimeSpecOverrideCheck(directory, sidecar, env) {
5072
+ async function runtimeSpecOverrideCheck(directory, sidecar, env, repairDir) {
5056
5073
  const facts = [];
5057
5074
  const installed = sidecar?.runtime_spec_override;
5058
5075
  if (installed) {
@@ -5081,7 +5098,7 @@ async function runtimeSpecOverrideCheck(directory, sidecar, env) {
5081
5098
  label: "Runtime spec override",
5082
5099
  level: "failed",
5083
5100
  detail: `runtime spec overridden \u2014 not the pinned manifest (${MCP_RUNTIME_MANIFEST.signerPackage}@${MCP_RUNTIME_MANIFEST.signerVersion}, ${MCP_RUNTIME_MANIFEST.sdkPackage}@${MCP_RUNTIME_MANIFEST.sdkVersion}). ${facts.join(". ")}.`,
5084
- repair: `Developer override (#2424). To return to the pinned manifest: unset ${variables}, then run ${RERUN} --doctor --repair --runtime <runtime>. If the override is intentional, this finding is the record of it.`
5101
+ repair: `Developer override (#2424). To return to the pinned manifest: unset ${variables}, then run ${RERUN} --doctor --repair --runtime <runtime>${credentialsDirFlagFor(repairDir)}. If the override is intentional, this finding is the record of it.`
5085
5102
  });
5086
5103
  }
5087
5104
  async function readMcpSidecarOverride(directory) {
@@ -5140,7 +5157,7 @@ async function verdictsForAgent(entry, input, deps) {
5140
5157
  label: "Signer runtime (preinstalled wrapper)",
5141
5158
  level: "failed",
5142
5159
  detail: "No signer-runtime.json sidecar \u2014 the pinned signer runtime was never prepared (or a pre-#1586 npx config).",
5143
- repair: repairCommandFor(input.runtime)
5160
+ repair: repairCommandFor(input.runtime, "", input.repairDir)
5144
5161
  });
5145
5162
  } else if (sidecar.runtime_spec_override) {
5146
5163
  const matches = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
@@ -5152,7 +5169,7 @@ async function verdictsForAgent(entry, input, deps) {
5152
5169
  label: "Signer runtime (preinstalled wrapper)",
5153
5170
  level: matches ? "ok" : "failed",
5154
5171
  detail: matches ? `Installed ${sidecar.signer_package}@${sidecar.signer_version} at ${sidecar.runtime_directory} (override install \u2014 see runtime_spec_override)` : `Override runtime directory is stale or empty (${sidecar.runtime_directory}) \u2014 the CLI or package versions are missing.`,
5155
- ...matches ? {} : { repair: repairCommandFor(input.runtime, " with the same HAVEN_*_SPEC variables set.") }
5172
+ ...matches ? {} : { repair: repairCommandFor(input.runtime, " with the same HAVEN_*_SPEC variables set.", input.repairDir) }
5156
5173
  });
5157
5174
  } else {
5158
5175
  const intact = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
@@ -5166,10 +5183,10 @@ async function verdictsForAgent(entry, input, deps) {
5166
5183
  label: "Signer runtime (preinstalled wrapper)",
5167
5184
  level: ok ? "ok" : intact ? "advisory" : "failed",
5168
5185
  detail: ok ? `Installed ${sidecar.signer_package}@${sidecar.signer_version} at ${sidecar.runtime_directory}` : intact ? `Installed version ${sidecar.signer_version} does not match the connector's pinned ${MCP_RUNTIME_MANIFEST.signerVersion} \u2014 intact, but outdated.` : `Runtime directory is stale or empty (${sidecar.runtime_directory}) \u2014 the CLI or package versions are missing.`,
5169
- ...ok ? {} : { repair: repairCommandFor(input.runtime) }
5186
+ ...ok ? {} : { repair: repairCommandFor(input.runtime, "", input.repairDir) }
5170
5187
  });
5171
5188
  }
5172
- const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env);
5189
+ const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env, input.repairDir);
5173
5190
  if (overrideCheck) checks.push(overrideCheck);
5174
5191
  const hostedUrl = identity?.hosted_mcp_url ?? (identity?.api_url ? `${identity.api_url}/mcp` : void 0);
5175
5192
  if (identity?.api_key && hostedUrl) {
@@ -5259,7 +5276,7 @@ async function verdictsForAgent(entry, input, deps) {
5259
5276
  label: "Signer stdio handshake",
5260
5277
  level: probe.status === "ok" ? "ok" : "failed",
5261
5278
  detail: probe.status === "ok" ? `Signer started, listed ${probe.toolNames?.length ?? 0} tools${probe.serverInfo?.version ? ` (v${probe.serverInfo.version})` : ""}.${compatDetail}` : `Handshake failed: ${probe.status}.`,
5262
- ...probe.status === "ok" ? {} : { repair: repairCommandFor(input.runtime) }
5279
+ ...probe.status === "ok" ? {} : { repair: repairCommandFor(input.runtime, "", input.repairDir) }
5263
5280
  });
5264
5281
  }
5265
5282
  } else {
@@ -5268,7 +5285,7 @@ async function verdictsForAgent(entry, input, deps) {
5268
5285
  label: "Signer stdio handshake",
5269
5286
  level: "failed",
5270
5287
  detail: "Skipped \u2014 no prepared signer runtime to probe.",
5271
- repair: repairCommandFor(input.runtime)
5288
+ repair: repairCommandFor(input.runtime, "", input.repairDir)
5272
5289
  });
5273
5290
  }
5274
5291
  return { checks, ...signerCapabilities ? { signerCapabilities } : {} };
@@ -5277,7 +5294,8 @@ async function runDoctor(input, deps = {}) {
5277
5294
  const homeDir = deps.homeDir ?? os.homedir();
5278
5295
  const checks = [];
5279
5296
  let signerCapabilities;
5280
- const { directory, others, parkedOnly } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5297
+ const { directory, others, parkedOnly, identityDirectories } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5298
+ const nameRepairDir = identityDirectories.length > 1 || Boolean(input.credentialsDir);
5281
5299
  const resolution = await resolveDoctorRuntime(input, directory);
5282
5300
  const runtime = resolution.runtime;
5283
5301
  const input2 = { ...input, runtime };
@@ -5334,7 +5352,11 @@ async function runDoctor(input, deps = {}) {
5334
5352
  ...rekeyPending ? { rekeyPending } : {}
5335
5353
  };
5336
5354
  if (wired) {
5337
- const result = await checksForAgent({ directory: dir, identity, sidecar }, input2, deps);
5355
+ const result = await checksForAgent(
5356
+ { directory: dir, identity, sidecar },
5357
+ { ...input2, ...nameRepairDir ? { repairDir: dir } : {} },
5358
+ deps
5359
+ );
5338
5360
  entry.checks = result.checks;
5339
5361
  capabilitiesByDirectory.set(dir, result.signerCapabilities);
5340
5362
  } else if (rekeyPending) {
@@ -5363,7 +5385,7 @@ async function runDoctor(input, deps = {}) {
5363
5385
  if (!primaryChecksById.has("credentials")) {
5364
5386
  const result = await checksForAgent(
5365
5387
  { directory: primaryDirectory, identity: primaryIdentity, sidecar: primarySidecar },
5366
- input2,
5388
+ { ...input2, ...nameRepairDir ? { repairDir: primaryDirectory } : {} },
5367
5389
  deps
5368
5390
  );
5369
5391
  signerCapabilities = result.signerCapabilities;
@@ -5404,7 +5426,7 @@ async function runDoctor(input, deps = {}) {
5404
5426
  label: "Runtime MCP config",
5405
5427
  level: "failed",
5406
5428
  detail: `No runtime config at ${configPath}.`,
5407
- repair: repairCommandFor(input2.runtime)
5429
+ repair: repairCommandFor(input2.runtime, "", nameRepairDir ? primaryDirectory : void 0)
5408
5430
  });
5409
5431
  } else {
5410
5432
  const primaryIdentity = await readIdentity(primaryDirectory ?? "");
@@ -5418,7 +5440,7 @@ async function runDoctor(input, deps = {}) {
5418
5440
  label: "Runtime MCP config",
5419
5441
  level: ok ? "ok" : "failed",
5420
5442
  detail: ok ? `Config at ${configPath} references the hosted server and the prepared signer wrapper.` : signerViaNpx ? `Config at ${configPath} still launches the signer via npx \u2014 the pre-#1586 shape that cannot start under a 120s startup timeout.` : `Config at ${configPath} is missing the Haven entries${primarySidecar && !wrapperReferenced ? " (or references a different signer wrapper)" : ""}.`,
5421
- ...ok ? {} : { repair: repairCommandFor(input2.runtime) }
5443
+ ...ok ? {} : { repair: repairCommandFor(input2.runtime, "", nameRepairDir ? primaryDirectory : void 0) }
5422
5444
  });
5423
5445
  }
5424
5446
  for (const id of ["hosted_mcp", "identity_match", "rekey_pending"]) {
@@ -5561,7 +5583,25 @@ async function runDoctor(input, deps = {}) {
5561
5583
  async function runRepair(input, deps = {}) {
5562
5584
  const homeDir = deps.homeDir ?? os.homedir();
5563
5585
  const messages = [];
5564
- const { directory, others } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5586
+ const { directory, others, identityDirectories } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5587
+ if (!input.credentialsDir && identityDirectories.length > 1) {
5588
+ const lines = await Promise.all(
5589
+ identityDirectories.map(async (dir) => {
5590
+ const identity2 = await readIdentity(dir);
5591
+ const who = identity2?.agent_id ? `agent ${identity2.agent_id}` : "agent (no agent_id recorded)";
5592
+ const retired = !identity2?.api_key || await pathExists3(path.join(dir, TOMBSTONE_FILENAME));
5593
+ return retired ? ` \u2022 ${who} (${dir}): retired \u2014 not a repair target` : ` \u2022 ${who}: ${repairCommandFor(input.runtime, "", dir).replace(/^Run: /, "")}`;
5594
+ })
5595
+ );
5596
+ return {
5597
+ ok: false,
5598
+ messages: [
5599
+ `${identityDirectories.length} agent credential directories exist on this machine, and a repair rewrites the runtime config from ONE of them. Name the agent to repair with --credentials-dir \u2014 nothing was changed:`,
5600
+ ...lines,
5601
+ "Not sure which agent is which? Compare the agent ids against the Haven agent page, or run --doctor first."
5602
+ ]
5603
+ };
5604
+ }
5565
5605
  if (others.length > 0) {
5566
5606
  messages.push(`Note: ${others.length} other agent credential dir(s) exist \u2014 run --doctor for their status.`);
5567
5607
  }