@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 +30 -21
- package/dist/cli.cjs +49 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +49 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +49 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
|
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.
|
|
260
|
+
sdkVersion: "0.0.0-dev.202610091612.d805017",
|
|
261
261
|
signerPackage: "@haven_ai/signer",
|
|
262
|
-
signerVersion: "0.0.0-dev.
|
|
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:
|
|
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
|
-
`
|
|
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.
|
|
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
|
-
|
|
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
|
|
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]) {
|