@haven_ai/connect 0.6.0-alpha.0 → 0.8.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/dist/index.cjs CHANGED
@@ -619,9 +619,9 @@ var init_runtime_manifest = __esm({
619
619
  mcpPackage: "@haven_ai/mcp",
620
620
  mcpVersion: mcp.MCP_VERSION,
621
621
  sdkPackage: "@haven_ai/sdk",
622
- sdkVersion: "0.6.0-alpha.0",
622
+ sdkVersion: "0.8.0-alpha.0",
623
623
  signerPackage: "@haven_ai/signer",
624
- signerVersion: "0.6.0-alpha.0",
624
+ signerVersion: "0.8.0-alpha.0",
625
625
  // Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
626
626
  // while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
627
627
  // so the guard that was supposed to enforce the floor waved Node v23 through
@@ -4450,7 +4450,7 @@ var init_runtime = __esm({
4450
4450
  init_tombstone();
4451
4451
  init_local_mcp_runtime();
4452
4452
  init_runtime_manifest();
4453
- exports.CONNECTOR_VERSION = "0.6.0-alpha.0";
4453
+ exports.CONNECTOR_VERSION = "0.8.0-alpha.0";
4454
4454
  CONNECTOR_CLIENT_IDENTITY = `@haven_ai/connect/${exports.CONNECTOR_VERSION}`;
4455
4455
  exports.CONNECT_OUTCOME_SCHEMA_VERSION = 1;
4456
4456
  failureOutcomesByError = /* @__PURE__ */ new WeakMap();
@@ -4933,9 +4933,23 @@ function rollUpLevel(checks) {
4933
4933
  function runtimeFlagFor(runtime) {
4934
4934
  return normalizeRuntimeName(runtime) ? ` --runtime ${runtime}` : "";
4935
4935
  }
4936
- function repairCommandFor(runtime, suffix = "") {
4937
- if (normalizeRuntimeName(runtime)) return `Run: ${RERUN} --doctor --repair --runtime ${runtime}${suffix}`;
4938
- return `Run: ${RERUN} --doctor --repair --runtime <runtime>${suffix.replace(/\.$/, "")} \u2014 --repair needs the runtime named; one of: ${RUNTIME_FLAG_VALUE_LIST.join(", ")}.`;
4936
+ async function pathExists3(path) {
4937
+ try {
4938
+ await promises.stat(path);
4939
+ return true;
4940
+ } catch {
4941
+ return false;
4942
+ }
4943
+ }
4944
+ function credentialsDirFlagFor(credentialsDir) {
4945
+ if (!credentialsDir) return "";
4946
+ const quoted = /^[\w@%+=:,./~-]+$/.test(credentialsDir) ? credentialsDir : `'${credentialsDir.replace(/'/g, `'\\''`)}'`;
4947
+ return ` --credentials-dir ${quoted}`;
4948
+ }
4949
+ function repairCommandFor(runtime, suffix = "", credentialsDir) {
4950
+ const dirFlag = credentialsDirFlagFor(credentialsDir);
4951
+ if (normalizeRuntimeName(runtime)) return `Run: ${RERUN} --doctor --repair --runtime ${runtime}${dirFlag}${suffix}`;
4952
+ return `Run: ${RERUN} --doctor --repair --runtime <runtime>${dirFlag}${suffix.replace(/\.$/, "")} \u2014 --repair needs the runtime named; one of: ${RUNTIME_FLAG_VALUE_LIST.join(", ")}.`;
4939
4953
  }
4940
4954
  async function resolveDoctorRuntime(input, directory) {
4941
4955
  const explicit = input.runtime.trim();
@@ -4954,45 +4968,47 @@ async function discoverCredentialDirectory(homeDir, explicit) {
4954
4968
  try {
4955
4969
  entries = await promises.readdir(root);
4956
4970
  } catch {
4957
- return explicit ? { directory: explicit, others: [], parkedOnly: /* @__PURE__ */ new Set() } : { others: [], parkedOnly: /* @__PURE__ */ new Set() };
4971
+ return explicit ? { directory: explicit, others: [], parkedOnly: /* @__PURE__ */ new Set(), identityDirectories: [explicit] } : { others: [], parkedOnly: /* @__PURE__ */ new Set(), identityDirectories: [] };
4958
4972
  }
4959
4973
  const candidates = [];
4960
4974
  const tombstonedOnly = [];
4961
4975
  const parkedOnly = [];
4962
4976
  for (const entry of entries) {
4963
4977
  const directory = path.join(root, entry);
4978
+ if (await pathExists3(path.join(directory, TOMBSTONE_FILENAME))) {
4979
+ tombstonedOnly.push(directory);
4980
+ continue;
4981
+ }
4964
4982
  try {
4965
4983
  const s = await promises.stat(path.join(directory, "identity.json"));
4966
4984
  candidates.push({ directory, mtimeMs: s.mtimeMs });
4967
4985
  } catch {
4968
4986
  try {
4969
- await promises.stat(path.join(directory, TOMBSTONE_FILENAME));
4970
- tombstonedOnly.push(directory);
4987
+ await promises.stat(path.join(directory, REKEY_PENDING_FILENAME));
4988
+ parkedOnly.push(directory);
4971
4989
  } catch {
4972
- try {
4973
- await promises.stat(path.join(directory, REKEY_PENDING_FILENAME));
4974
- parkedOnly.push(directory);
4975
- } catch {
4976
- }
4977
4990
  }
4978
4991
  }
4979
4992
  }
4980
4993
  const parkedOnlySet = new Set(parkedOnly);
4981
4994
  candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
4995
+ const identityDirectories = candidates.map((c) => c.directory);
4982
4996
  if (explicit) {
4983
4997
  return {
4984
4998
  directory: explicit,
4985
4999
  others: [...candidates.map((c) => c.directory), ...tombstonedOnly, ...parkedOnly].filter((d) => d !== explicit),
4986
- parkedOnly: parkedOnlySet
5000
+ parkedOnly: parkedOnlySet,
5001
+ identityDirectories: [explicit, ...identityDirectories.filter((d) => d !== explicit)]
4987
5002
  };
4988
5003
  }
4989
5004
  if (candidates.length === 0 && tombstonedOnly.length === 0 && parkedOnly.length === 0) {
4990
- return { others: [], parkedOnly: parkedOnlySet };
5005
+ return { others: [], parkedOnly: parkedOnlySet, identityDirectories };
4991
5006
  }
4992
5007
  return {
4993
5008
  directory: candidates[0]?.directory,
4994
5009
  others: [...candidates.slice(1).map((c) => c.directory), ...tombstonedOnly, ...parkedOnly],
4995
- parkedOnly: parkedOnlySet
5010
+ parkedOnly: parkedOnlySet,
5011
+ identityDirectories
4996
5012
  };
4997
5013
  }
4998
5014
  function agentIsWired(configText, names, slug, identity, sidecar, isPrimary, bareOwnerExists) {
@@ -5051,7 +5067,7 @@ function rekeyPendingVerdict(status, hostedDelegateAddress, runtime, slug) {
5051
5067
  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
5052
5068
  };
5053
5069
  }
5054
- async function runtimeSpecOverrideCheck(directory, sidecar, env) {
5070
+ async function runtimeSpecOverrideCheck(directory, sidecar, env, repairDir) {
5055
5071
  const facts = [];
5056
5072
  const installed = sidecar?.runtime_spec_override;
5057
5073
  if (installed) {
@@ -5080,7 +5096,7 @@ async function runtimeSpecOverrideCheck(directory, sidecar, env) {
5080
5096
  label: "Runtime spec override",
5081
5097
  level: "failed",
5082
5098
  detail: `runtime spec overridden \u2014 not the pinned manifest (${exports.MCP_RUNTIME_MANIFEST.signerPackage}@${exports.MCP_RUNTIME_MANIFEST.signerVersion}, ${exports.MCP_RUNTIME_MANIFEST.sdkPackage}@${exports.MCP_RUNTIME_MANIFEST.sdkVersion}). ${facts.join(". ")}.`,
5083
- 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.`
5099
+ 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.`
5084
5100
  });
5085
5101
  }
5086
5102
  async function readMcpSidecarOverride(directory) {
@@ -5139,7 +5155,7 @@ async function verdictsForAgent(entry, input, deps) {
5139
5155
  label: "Signer runtime (preinstalled wrapper)",
5140
5156
  level: "failed",
5141
5157
  detail: "No signer-runtime.json sidecar \u2014 the pinned signer runtime was never prepared (or a pre-#1586 npx config).",
5142
- repair: repairCommandFor(input.runtime)
5158
+ repair: repairCommandFor(input.runtime, "", input.repairDir)
5143
5159
  });
5144
5160
  } else if (sidecar.runtime_spec_override) {
5145
5161
  const matches = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
@@ -5151,7 +5167,7 @@ async function verdictsForAgent(entry, input, deps) {
5151
5167
  label: "Signer runtime (preinstalled wrapper)",
5152
5168
  level: matches ? "ok" : "failed",
5153
5169
  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.`,
5154
- ...matches ? {} : { repair: repairCommandFor(input.runtime, " with the same HAVEN_*_SPEC variables set.") }
5170
+ ...matches ? {} : { repair: repairCommandFor(input.runtime, " with the same HAVEN_*_SPEC variables set.", input.repairDir) }
5155
5171
  });
5156
5172
  } else {
5157
5173
  const intact = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
@@ -5165,10 +5181,10 @@ async function verdictsForAgent(entry, input, deps) {
5165
5181
  label: "Signer runtime (preinstalled wrapper)",
5166
5182
  level: ok ? "ok" : intact ? "advisory" : "failed",
5167
5183
  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 ${exports.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.`,
5168
- ...ok ? {} : { repair: repairCommandFor(input.runtime) }
5184
+ ...ok ? {} : { repair: repairCommandFor(input.runtime, "", input.repairDir) }
5169
5185
  });
5170
5186
  }
5171
- const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env);
5187
+ const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env, input.repairDir);
5172
5188
  if (overrideCheck) checks.push(overrideCheck);
5173
5189
  const hostedUrl = identity?.hosted_mcp_url ?? (identity?.api_url ? `${identity.api_url}/mcp` : void 0);
5174
5190
  if (identity?.api_key && hostedUrl) {
@@ -5258,7 +5274,7 @@ async function verdictsForAgent(entry, input, deps) {
5258
5274
  label: "Signer stdio handshake",
5259
5275
  level: probe.status === "ok" ? "ok" : "failed",
5260
5276
  detail: probe.status === "ok" ? `Signer started, listed ${probe.toolNames?.length ?? 0} tools${probe.serverInfo?.version ? ` (v${probe.serverInfo.version})` : ""}.${compatDetail}` : `Handshake failed: ${probe.status}.`,
5261
- ...probe.status === "ok" ? {} : { repair: repairCommandFor(input.runtime) }
5277
+ ...probe.status === "ok" ? {} : { repair: repairCommandFor(input.runtime, "", input.repairDir) }
5262
5278
  });
5263
5279
  }
5264
5280
  } else {
@@ -5267,7 +5283,7 @@ async function verdictsForAgent(entry, input, deps) {
5267
5283
  label: "Signer stdio handshake",
5268
5284
  level: "failed",
5269
5285
  detail: "Skipped \u2014 no prepared signer runtime to probe.",
5270
- repair: repairCommandFor(input.runtime)
5286
+ repair: repairCommandFor(input.runtime, "", input.repairDir)
5271
5287
  });
5272
5288
  }
5273
5289
  return { checks, ...signerCapabilities ? { signerCapabilities } : {} };
@@ -5276,7 +5292,8 @@ async function runDoctor(input, deps = {}) {
5276
5292
  const homeDir = deps.homeDir ?? os.homedir();
5277
5293
  const checks = [];
5278
5294
  let signerCapabilities;
5279
- const { directory, others, parkedOnly } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5295
+ const { directory, others, parkedOnly, identityDirectories } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5296
+ const nameRepairDir = identityDirectories.length > 1 || Boolean(input.credentialsDir);
5280
5297
  const resolution = await resolveDoctorRuntime(input, directory);
5281
5298
  const runtime = resolution.runtime;
5282
5299
  const input2 = { ...input, runtime };
@@ -5333,7 +5350,11 @@ async function runDoctor(input, deps = {}) {
5333
5350
  ...rekeyPending ? { rekeyPending } : {}
5334
5351
  };
5335
5352
  if (wired) {
5336
- const result = await checksForAgent({ directory: dir, identity, sidecar }, input2, deps);
5353
+ const result = await checksForAgent(
5354
+ { directory: dir, identity, sidecar },
5355
+ { ...input2, ...nameRepairDir ? { repairDir: dir } : {} },
5356
+ deps
5357
+ );
5337
5358
  entry.checks = result.checks;
5338
5359
  capabilitiesByDirectory.set(dir, result.signerCapabilities);
5339
5360
  } else if (rekeyPending) {
@@ -5362,7 +5383,7 @@ async function runDoctor(input, deps = {}) {
5362
5383
  if (!primaryChecksById.has("credentials")) {
5363
5384
  const result = await checksForAgent(
5364
5385
  { directory: primaryDirectory, identity: primaryIdentity, sidecar: primarySidecar },
5365
- input2,
5386
+ { ...input2, ...nameRepairDir ? { repairDir: primaryDirectory } : {} },
5366
5387
  deps
5367
5388
  );
5368
5389
  signerCapabilities = result.signerCapabilities;
@@ -5403,7 +5424,7 @@ async function runDoctor(input, deps = {}) {
5403
5424
  label: "Runtime MCP config",
5404
5425
  level: "failed",
5405
5426
  detail: `No runtime config at ${configPath}.`,
5406
- repair: repairCommandFor(input2.runtime)
5427
+ repair: repairCommandFor(input2.runtime, "", nameRepairDir ? primaryDirectory : void 0)
5407
5428
  });
5408
5429
  } else {
5409
5430
  const primaryIdentity = await readIdentity(primaryDirectory ?? "");
@@ -5417,7 +5438,7 @@ async function runDoctor(input, deps = {}) {
5417
5438
  label: "Runtime MCP config",
5418
5439
  level: ok ? "ok" : "failed",
5419
5440
  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)" : ""}.`,
5420
- ...ok ? {} : { repair: repairCommandFor(input2.runtime) }
5441
+ ...ok ? {} : { repair: repairCommandFor(input2.runtime, "", nameRepairDir ? primaryDirectory : void 0) }
5421
5442
  });
5422
5443
  }
5423
5444
  for (const id of ["hosted_mcp", "identity_match", "rekey_pending"]) {
@@ -5560,7 +5581,25 @@ async function runDoctor(input, deps = {}) {
5560
5581
  async function runRepair(input, deps = {}) {
5561
5582
  const homeDir = deps.homeDir ?? os.homedir();
5562
5583
  const messages = [];
5563
- const { directory, others } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5584
+ const { directory, others, identityDirectories } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
5585
+ if (!input.credentialsDir && identityDirectories.length > 1) {
5586
+ const lines = await Promise.all(
5587
+ identityDirectories.map(async (dir) => {
5588
+ const identity2 = await readIdentity(dir);
5589
+ const who = identity2?.agent_id ? `agent ${identity2.agent_id}` : "agent (no agent_id recorded)";
5590
+ const retired = !identity2?.api_key || await pathExists3(path.join(dir, TOMBSTONE_FILENAME));
5591
+ return retired ? ` \u2022 ${who} (${dir}): retired \u2014 not a repair target` : ` \u2022 ${who}: ${repairCommandFor(input.runtime, "", dir).replace(/^Run: /, "")}`;
5592
+ })
5593
+ );
5594
+ return {
5595
+ ok: false,
5596
+ messages: [
5597
+ `${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:`,
5598
+ ...lines,
5599
+ "Not sure which agent is which? Compare the agent ids against the Haven agent page, or run --doctor first."
5600
+ ]
5601
+ };
5602
+ }
5564
5603
  if (others.length > 0) {
5565
5604
  messages.push(`Note: ${others.length} other agent credential dir(s) exist \u2014 run --doctor for their status.`);
5566
5605
  }