@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/README.md +14 -1
- package/dist/cli.cjs +70 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +70 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +70 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +70 -31
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -392,7 +392,8 @@ also yields one rather than failing a completed setup.
|
|
|
392
392
|
|
|
393
393
|
**The dashboard offers the revoke, never the connector (#2561).** The same ids
|
|
394
394
|
now ride the install-status report, so the Haven dashboard can put a
|
|
395
|
-
|
|
395
|
+
revoke next to the setup that displaced them; it also ends the agent's budget
|
|
396
|
+
with one owner signature (#3542). The connector does not
|
|
396
397
|
and must not do it: `POST /agents/:id/revoke` is owner-authenticated, and an
|
|
397
398
|
agent credential retiring a sibling agent is the "agent editing its own
|
|
398
399
|
authority" the re-key routes refuse. Nothing is revoked automatically — the
|
|
@@ -703,6 +704,18 @@ which.
|
|
|
703
704
|
runtime, rewrite the wrapper + sidecar, and re-write the runtime config from
|
|
704
705
|
the STORED credentials. It never touches keys and never needs a new token.
|
|
705
706
|
|
|
707
|
+
**Several agents on one machine (#3412).** A repair rewrites the runtime config
|
|
708
|
+
from ONE agent's stored credentials. With more than one agent credential
|
|
709
|
+
directory under `~/.haven/agents` — or when `--doctor` was itself given
|
|
710
|
+
`--credentials-dir` — every repair line the doctor prints names the agent it is
|
|
711
|
+
about with `--credentials-dir <dir>`, and a `--repair` without
|
|
712
|
+
`--credentials-dir` is refused — nothing is changed — with one exact command
|
|
713
|
+
per live agent (its agent id beside it); a retired directory is listed as
|
|
714
|
+
"retired — not a repair target" and never offered. Without that, "the newest directory" was a
|
|
715
|
+
guess, and it re-wired a different agent than the one the doctor described.
|
|
716
|
+
Every "update your signer" hint Haven emits names the flagless
|
|
717
|
+
`npx -y @haven_ai/connect@<channel> --doctor`, which then prints that line.
|
|
718
|
+
|
|
706
719
|
## Installing an unpublished signer / SDK / MCP build (`HAVEN_SIGNER_SPEC`, #2424)
|
|
707
720
|
|
|
708
721
|
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.
|
|
623
|
+
sdkVersion: "0.8.0-alpha.0",
|
|
624
624
|
signerPackage: "@haven_ai/signer",
|
|
625
|
-
signerVersion: "0.
|
|
625
|
+
signerVersion: "0.8.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.
|
|
4454
|
+
CONNECTOR_VERSION = "0.8.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
|
|
4938
|
-
|
|
4939
|
-
|
|
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,45 +4969,47 @@ 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 = [];
|
|
4962
4976
|
const parkedOnly = [];
|
|
4963
4977
|
for (const entry of entries) {
|
|
4964
4978
|
const directory = path.join(root, entry);
|
|
4979
|
+
if (await pathExists3(path.join(directory, TOMBSTONE_FILENAME))) {
|
|
4980
|
+
tombstonedOnly.push(directory);
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4965
4983
|
try {
|
|
4966
4984
|
const s = await promises.stat(path.join(directory, "identity.json"));
|
|
4967
4985
|
candidates.push({ directory, mtimeMs: s.mtimeMs });
|
|
4968
4986
|
} catch {
|
|
4969
4987
|
try {
|
|
4970
|
-
await promises.stat(path.join(directory,
|
|
4971
|
-
|
|
4988
|
+
await promises.stat(path.join(directory, REKEY_PENDING_FILENAME));
|
|
4989
|
+
parkedOnly.push(directory);
|
|
4972
4990
|
} catch {
|
|
4973
|
-
try {
|
|
4974
|
-
await promises.stat(path.join(directory, REKEY_PENDING_FILENAME));
|
|
4975
|
-
parkedOnly.push(directory);
|
|
4976
|
-
} catch {
|
|
4977
|
-
}
|
|
4978
4991
|
}
|
|
4979
4992
|
}
|
|
4980
4993
|
}
|
|
4981
4994
|
const parkedOnlySet = new Set(parkedOnly);
|
|
4982
4995
|
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
4996
|
+
const identityDirectories = candidates.map((c) => c.directory);
|
|
4983
4997
|
if (explicit) {
|
|
4984
4998
|
return {
|
|
4985
4999
|
directory: explicit,
|
|
4986
5000
|
others: [...candidates.map((c) => c.directory), ...tombstonedOnly, ...parkedOnly].filter((d) => d !== explicit),
|
|
4987
|
-
parkedOnly: parkedOnlySet
|
|
5001
|
+
parkedOnly: parkedOnlySet,
|
|
5002
|
+
identityDirectories: [explicit, ...identityDirectories.filter((d) => d !== explicit)]
|
|
4988
5003
|
};
|
|
4989
5004
|
}
|
|
4990
5005
|
if (candidates.length === 0 && tombstonedOnly.length === 0 && parkedOnly.length === 0) {
|
|
4991
|
-
return { others: [], parkedOnly: parkedOnlySet };
|
|
5006
|
+
return { others: [], parkedOnly: parkedOnlySet, identityDirectories };
|
|
4992
5007
|
}
|
|
4993
5008
|
return {
|
|
4994
5009
|
directory: candidates[0]?.directory,
|
|
4995
5010
|
others: [...candidates.slice(1).map((c) => c.directory), ...tombstonedOnly, ...parkedOnly],
|
|
4996
|
-
parkedOnly: parkedOnlySet
|
|
5011
|
+
parkedOnly: parkedOnlySet,
|
|
5012
|
+
identityDirectories
|
|
4997
5013
|
};
|
|
4998
5014
|
}
|
|
4999
5015
|
function agentIsWired(configText, names, slug, identity, sidecar, isPrimary, bareOwnerExists) {
|
|
@@ -5052,7 +5068,7 @@ function rekeyPendingVerdict(status, hostedDelegateAddress, runtime, slug) {
|
|
|
5052
5068
|
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
5069
|
};
|
|
5054
5070
|
}
|
|
5055
|
-
async function runtimeSpecOverrideCheck(directory, sidecar, env) {
|
|
5071
|
+
async function runtimeSpecOverrideCheck(directory, sidecar, env, repairDir) {
|
|
5056
5072
|
const facts = [];
|
|
5057
5073
|
const installed = sidecar?.runtime_spec_override;
|
|
5058
5074
|
if (installed) {
|
|
@@ -5081,7 +5097,7 @@ async function runtimeSpecOverrideCheck(directory, sidecar, env) {
|
|
|
5081
5097
|
label: "Runtime spec override",
|
|
5082
5098
|
level: "failed",
|
|
5083
5099
|
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
|
|
5100
|
+
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
5101
|
});
|
|
5086
5102
|
}
|
|
5087
5103
|
async function readMcpSidecarOverride(directory) {
|
|
@@ -5140,7 +5156,7 @@ async function verdictsForAgent(entry, input, deps) {
|
|
|
5140
5156
|
label: "Signer runtime (preinstalled wrapper)",
|
|
5141
5157
|
level: "failed",
|
|
5142
5158
|
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)
|
|
5159
|
+
repair: repairCommandFor(input.runtime, "", input.repairDir)
|
|
5144
5160
|
});
|
|
5145
5161
|
} else if (sidecar.runtime_spec_override) {
|
|
5146
5162
|
const matches = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
|
|
@@ -5152,7 +5168,7 @@ async function verdictsForAgent(entry, input, deps) {
|
|
|
5152
5168
|
label: "Signer runtime (preinstalled wrapper)",
|
|
5153
5169
|
level: matches ? "ok" : "failed",
|
|
5154
5170
|
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.") }
|
|
5171
|
+
...matches ? {} : { repair: repairCommandFor(input.runtime, " with the same HAVEN_*_SPEC variables set.", input.repairDir) }
|
|
5156
5172
|
});
|
|
5157
5173
|
} else {
|
|
5158
5174
|
const intact = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
|
|
@@ -5166,10 +5182,10 @@ async function verdictsForAgent(entry, input, deps) {
|
|
|
5166
5182
|
label: "Signer runtime (preinstalled wrapper)",
|
|
5167
5183
|
level: ok ? "ok" : intact ? "advisory" : "failed",
|
|
5168
5184
|
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) }
|
|
5185
|
+
...ok ? {} : { repair: repairCommandFor(input.runtime, "", input.repairDir) }
|
|
5170
5186
|
});
|
|
5171
5187
|
}
|
|
5172
|
-
const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env);
|
|
5188
|
+
const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env, input.repairDir);
|
|
5173
5189
|
if (overrideCheck) checks.push(overrideCheck);
|
|
5174
5190
|
const hostedUrl = identity?.hosted_mcp_url ?? (identity?.api_url ? `${identity.api_url}/mcp` : void 0);
|
|
5175
5191
|
if (identity?.api_key && hostedUrl) {
|
|
@@ -5259,7 +5275,7 @@ async function verdictsForAgent(entry, input, deps) {
|
|
|
5259
5275
|
label: "Signer stdio handshake",
|
|
5260
5276
|
level: probe.status === "ok" ? "ok" : "failed",
|
|
5261
5277
|
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) }
|
|
5278
|
+
...probe.status === "ok" ? {} : { repair: repairCommandFor(input.runtime, "", input.repairDir) }
|
|
5263
5279
|
});
|
|
5264
5280
|
}
|
|
5265
5281
|
} else {
|
|
@@ -5268,7 +5284,7 @@ async function verdictsForAgent(entry, input, deps) {
|
|
|
5268
5284
|
label: "Signer stdio handshake",
|
|
5269
5285
|
level: "failed",
|
|
5270
5286
|
detail: "Skipped \u2014 no prepared signer runtime to probe.",
|
|
5271
|
-
repair: repairCommandFor(input.runtime)
|
|
5287
|
+
repair: repairCommandFor(input.runtime, "", input.repairDir)
|
|
5272
5288
|
});
|
|
5273
5289
|
}
|
|
5274
5290
|
return { checks, ...signerCapabilities ? { signerCapabilities } : {} };
|
|
@@ -5277,7 +5293,8 @@ async function runDoctor(input, deps = {}) {
|
|
|
5277
5293
|
const homeDir = deps.homeDir ?? os.homedir();
|
|
5278
5294
|
const checks = [];
|
|
5279
5295
|
let signerCapabilities;
|
|
5280
|
-
const { directory, others, parkedOnly } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
|
|
5296
|
+
const { directory, others, parkedOnly, identityDirectories } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
|
|
5297
|
+
const nameRepairDir = identityDirectories.length > 1 || Boolean(input.credentialsDir);
|
|
5281
5298
|
const resolution = await resolveDoctorRuntime(input, directory);
|
|
5282
5299
|
const runtime = resolution.runtime;
|
|
5283
5300
|
const input2 = { ...input, runtime };
|
|
@@ -5334,7 +5351,11 @@ async function runDoctor(input, deps = {}) {
|
|
|
5334
5351
|
...rekeyPending ? { rekeyPending } : {}
|
|
5335
5352
|
};
|
|
5336
5353
|
if (wired) {
|
|
5337
|
-
const result = await checksForAgent(
|
|
5354
|
+
const result = await checksForAgent(
|
|
5355
|
+
{ directory: dir, identity, sidecar },
|
|
5356
|
+
{ ...input2, ...nameRepairDir ? { repairDir: dir } : {} },
|
|
5357
|
+
deps
|
|
5358
|
+
);
|
|
5338
5359
|
entry.checks = result.checks;
|
|
5339
5360
|
capabilitiesByDirectory.set(dir, result.signerCapabilities);
|
|
5340
5361
|
} else if (rekeyPending) {
|
|
@@ -5363,7 +5384,7 @@ async function runDoctor(input, deps = {}) {
|
|
|
5363
5384
|
if (!primaryChecksById.has("credentials")) {
|
|
5364
5385
|
const result = await checksForAgent(
|
|
5365
5386
|
{ directory: primaryDirectory, identity: primaryIdentity, sidecar: primarySidecar },
|
|
5366
|
-
input2,
|
|
5387
|
+
{ ...input2, ...nameRepairDir ? { repairDir: primaryDirectory } : {} },
|
|
5367
5388
|
deps
|
|
5368
5389
|
);
|
|
5369
5390
|
signerCapabilities = result.signerCapabilities;
|
|
@@ -5404,7 +5425,7 @@ async function runDoctor(input, deps = {}) {
|
|
|
5404
5425
|
label: "Runtime MCP config",
|
|
5405
5426
|
level: "failed",
|
|
5406
5427
|
detail: `No runtime config at ${configPath}.`,
|
|
5407
|
-
repair: repairCommandFor(input2.runtime)
|
|
5428
|
+
repair: repairCommandFor(input2.runtime, "", nameRepairDir ? primaryDirectory : void 0)
|
|
5408
5429
|
});
|
|
5409
5430
|
} else {
|
|
5410
5431
|
const primaryIdentity = await readIdentity(primaryDirectory ?? "");
|
|
@@ -5418,7 +5439,7 @@ async function runDoctor(input, deps = {}) {
|
|
|
5418
5439
|
label: "Runtime MCP config",
|
|
5419
5440
|
level: ok ? "ok" : "failed",
|
|
5420
5441
|
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) }
|
|
5442
|
+
...ok ? {} : { repair: repairCommandFor(input2.runtime, "", nameRepairDir ? primaryDirectory : void 0) }
|
|
5422
5443
|
});
|
|
5423
5444
|
}
|
|
5424
5445
|
for (const id of ["hosted_mcp", "identity_match", "rekey_pending"]) {
|
|
@@ -5561,7 +5582,25 @@ async function runDoctor(input, deps = {}) {
|
|
|
5561
5582
|
async function runRepair(input, deps = {}) {
|
|
5562
5583
|
const homeDir = deps.homeDir ?? os.homedir();
|
|
5563
5584
|
const messages = [];
|
|
5564
|
-
const { directory, others } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
|
|
5585
|
+
const { directory, others, identityDirectories } = await discoverCredentialDirectory(homeDir, input.credentialsDir);
|
|
5586
|
+
if (!input.credentialsDir && identityDirectories.length > 1) {
|
|
5587
|
+
const lines = await Promise.all(
|
|
5588
|
+
identityDirectories.map(async (dir) => {
|
|
5589
|
+
const identity2 = await readIdentity(dir);
|
|
5590
|
+
const who = identity2?.agent_id ? `agent ${identity2.agent_id}` : "agent (no agent_id recorded)";
|
|
5591
|
+
const retired = !identity2?.api_key || await pathExists3(path.join(dir, TOMBSTONE_FILENAME));
|
|
5592
|
+
return retired ? ` \u2022 ${who} (${dir}): retired \u2014 not a repair target` : ` \u2022 ${who}: ${repairCommandFor(input.runtime, "", dir).replace(/^Run: /, "")}`;
|
|
5593
|
+
})
|
|
5594
|
+
);
|
|
5595
|
+
return {
|
|
5596
|
+
ok: false,
|
|
5597
|
+
messages: [
|
|
5598
|
+
`${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:`,
|
|
5599
|
+
...lines,
|
|
5600
|
+
"Not sure which agent is which? Compare the agent ids against the Haven agent page, or run --doctor first."
|
|
5601
|
+
]
|
|
5602
|
+
};
|
|
5603
|
+
}
|
|
5565
5604
|
if (others.length > 0) {
|
|
5566
5605
|
messages.push(`Note: ${others.length} other agent credential dir(s) exist \u2014 run --doctor for their status.`);
|
|
5567
5606
|
}
|