@haven_ai/connect 0.0.0-dev.202609231741.b88d644 → 0.0.0-dev.202609232125.72f3d20
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 +8 -2
- package/dist/cli.cjs +55 -15
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +55 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +55 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +55 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -175,7 +175,12 @@ minutes. Two rules follow:
|
|
|
175
175
|
and a refusal is `{"tombstoned": false, "error": {"code", "next_action"}}`
|
|
176
176
|
with exit 1 — so check the result rather than assuming silence means success
|
|
177
177
|
(#2175). The `message` field is present only for connector-authored refusals;
|
|
178
|
-
an unexpected filesystem error keeps its raw text on stderr alone.
|
|
178
|
+
an unexpected filesystem error keeps its raw text on stderr alone. If only the
|
|
179
|
+
surviving copy fails — the directory IS tombstoned — the run still succeeds
|
|
180
|
+
with `"recordPath": null` and `"mirrorError"` set to the errno code (or
|
|
181
|
+
`mirror_write_failed` when there is none), and `--unwire --json` carries the
|
|
182
|
+
same as `mirror_error`; `--replace` still removes the old directory's key
|
|
183
|
+
files and names the failure in `retirement_mirror_errors` (#3259).
|
|
179
184
|
|
|
180
185
|
### Unwiring an agent (`--unwire`, #2169)
|
|
181
186
|
|
|
@@ -352,7 +357,8 @@ read-only verification tools, `hosted_mcp_url`, `superseded_agent_ids`,
|
|
|
352
357
|
`existing_agents_before_write` (#3122 — the other live-keyed directories named
|
|
353
358
|
before the first write, each with the account it spends from), and — on a run
|
|
354
359
|
that replaced existing wiring — `superseded_agents_retired_locally` with
|
|
355
|
-
`retired_agent_ids
|
|
360
|
+
`retired_agent_ids`, plus `retirement_mirror_errors` (#3259) only when a retired
|
|
361
|
+
directory's surviving ledger record could not be written; `server_name_rebound_from` (#3122) appears only when the
|
|
356
362
|
run took a server name over from another directory's local binding record. It
|
|
357
363
|
contains no API key, private key, credential
|
|
358
364
|
contents, full credential paths, or full delegate address. The same redacted
|
package/dist/cli.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.0.0-dev.
|
|
622
|
+
sdkVersion: "0.0.0-dev.202609232125.72f3d20",
|
|
623
623
|
signerPackage: "@haven_ai/signer",
|
|
624
|
-
signerVersion: "0.0.0-dev.
|
|
624
|
+
signerVersion: "0.0.0-dev.202609232125.72f3d20",
|
|
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
|
|
@@ -3098,9 +3098,14 @@ async function writeAgentTombstone(input) {
|
|
|
3098
3098
|
const record = JSON.stringify(info, null, 2) + "\n";
|
|
3099
3099
|
await promises.writeFile(path.join(input.directory, TOMBSTONE_FILENAME), record, "utf8");
|
|
3100
3100
|
const root = input.tombstonesDir ?? defaultTombstonesDir();
|
|
3101
|
-
await promises.mkdir(root, { recursive: true, mode: 448 });
|
|
3102
3101
|
const recordPath = path.join(root, `${info.agent_id}.json`);
|
|
3103
|
-
|
|
3102
|
+
try {
|
|
3103
|
+
await promises.mkdir(root, { recursive: true, mode: 448 });
|
|
3104
|
+
await promises.writeFile(recordPath, record, { mode: MIRROR_MODE });
|
|
3105
|
+
} catch (err) {
|
|
3106
|
+
const code = err?.code;
|
|
3107
|
+
return { ...info, recordPath: null, mirrorError: typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "mirror_write_failed" };
|
|
3108
|
+
}
|
|
3104
3109
|
return { ...info, recordPath };
|
|
3105
3110
|
}
|
|
3106
3111
|
async function readAgentTombstone(directory) {
|
|
@@ -3169,7 +3174,7 @@ async function unwireAgent(input) {
|
|
|
3169
3174
|
const slug = input.slug ?? sidecar?.server_name;
|
|
3170
3175
|
const names = serverNamesFor(slug);
|
|
3171
3176
|
const runtimes = [];
|
|
3172
|
-
const tombstoned = await tombstoneDirectoryIfAbsent({
|
|
3177
|
+
const { written: tombstoned, mirrorError } = await tombstoneDirectoryIfAbsent({
|
|
3173
3178
|
directory: input.directory,
|
|
3174
3179
|
agentId,
|
|
3175
3180
|
reason: input.reason ?? "unwired via --unwire",
|
|
@@ -3263,7 +3268,16 @@ async function unwireAgent(input) {
|
|
|
3263
3268
|
const bindingReleased = await clearMcpServerBinding(input.directory);
|
|
3264
3269
|
const teardown = await decideTeardown(identity, input);
|
|
3265
3270
|
if (teardown.status !== "retained") await teardownLocalKeyMaterial(input.directory, identity);
|
|
3266
|
-
return {
|
|
3271
|
+
return {
|
|
3272
|
+
directory: input.directory,
|
|
3273
|
+
agentId,
|
|
3274
|
+
slug,
|
|
3275
|
+
tombstoned,
|
|
3276
|
+
runtimes,
|
|
3277
|
+
teardown,
|
|
3278
|
+
bindingReleased,
|
|
3279
|
+
...mirrorError === void 0 ? {} : { mirrorError }
|
|
3280
|
+
};
|
|
3267
3281
|
}
|
|
3268
3282
|
async function decideTeardown(identity, input) {
|
|
3269
3283
|
const ended = "Local recovery of a stranded delegate balance ends with it: the sweep-recovery routes accept only this agent's API key and delegate signature, and neither exists on this machine any more.";
|
|
@@ -3308,9 +3322,9 @@ async function decideTeardown(identity, input) {
|
|
|
3308
3322
|
}
|
|
3309
3323
|
}
|
|
3310
3324
|
async function tombstoneDirectoryIfAbsent(input) {
|
|
3311
|
-
if (await readOptionalText(path.join(input.directory, TOMBSTONE_FILENAME)) !== null) return false;
|
|
3312
|
-
await writeAgentTombstone(input);
|
|
3313
|
-
return true;
|
|
3325
|
+
if (await readOptionalText(path.join(input.directory, TOMBSTONE_FILENAME)) !== null) return { written: false };
|
|
3326
|
+
const { mirrorError } = await writeAgentTombstone(input);
|
|
3327
|
+
return mirrorError === void 0 ? { written: true } : { written: true, mirrorError };
|
|
3314
3328
|
}
|
|
3315
3329
|
async function teardownLocalKeyMaterial(directory, identity) {
|
|
3316
3330
|
await Promise.all([
|
|
@@ -4845,7 +4859,7 @@ init_unwire();
|
|
|
4845
4859
|
init_tombstone();
|
|
4846
4860
|
init_local_mcp_runtime();
|
|
4847
4861
|
init_runtime_manifest();
|
|
4848
|
-
var CONNECTOR_VERSION = "0.0.0-dev.
|
|
4862
|
+
var CONNECTOR_VERSION = "0.0.0-dev.202609232125.72f3d20";
|
|
4849
4863
|
var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
|
|
4850
4864
|
var failureOutcomesByError = /* @__PURE__ */ new WeakMap();
|
|
4851
4865
|
function failureOutcomeFor(runtimeHint, error) {
|
|
@@ -5133,6 +5147,7 @@ async function executeConnect(options, deps, trace) {
|
|
|
5133
5147
|
}
|
|
5134
5148
|
let supersededAgentsRetiredLocally;
|
|
5135
5149
|
const retiredAgentIds = [];
|
|
5150
|
+
const retirementMirrorErrors = {};
|
|
5136
5151
|
if (replacing) {
|
|
5137
5152
|
if (runtimeInstall.errorCode) {
|
|
5138
5153
|
supersededAgentsRetiredLocally = false;
|
|
@@ -5143,7 +5158,7 @@ async function executeConnect(options, deps, trace) {
|
|
|
5143
5158
|
supersededAgentsRetiredLocally = true;
|
|
5144
5159
|
for (const entry of replacing.superseded) {
|
|
5145
5160
|
try {
|
|
5146
|
-
await tombstoneDirectoryIfAbsent({
|
|
5161
|
+
const { mirrorError } = await tombstoneDirectoryIfAbsent({
|
|
5147
5162
|
directory: entry.directory,
|
|
5148
5163
|
agentId: entry.agentId,
|
|
5149
5164
|
reason: "replaced by a new setup (--replace)",
|
|
@@ -5152,6 +5167,12 @@ async function executeConnect(options, deps, trace) {
|
|
|
5152
5167
|
// root → ~/.haven/tombstones, as before; any other → <root>/.tombstones).
|
|
5153
5168
|
tombstonesDir: tombstonesDirForCredentialRoot(options.credentialsDir)
|
|
5154
5169
|
});
|
|
5170
|
+
if (mirrorError !== void 0) {
|
|
5171
|
+
retirementMirrorErrors[entry.agentId] = mirrorError;
|
|
5172
|
+
log(
|
|
5173
|
+
`Warning: the surviving tombstone record for ${entry.agentId} could not be written to ${tombstonesDirForCredentialRoot(options.credentialsDir)} (${mirrorError}). The in-place TOMBSTONE.json stands; --doctor will not list this retirement once the directory is deleted.`
|
|
5174
|
+
);
|
|
5175
|
+
}
|
|
5155
5176
|
await teardownLocalKeyMaterial(entry.directory, await readIdentityFile(entry.directory));
|
|
5156
5177
|
await clearMcpServerBinding(entry.directory);
|
|
5157
5178
|
retiredAgentIds.push(entry.agentId);
|
|
@@ -5223,6 +5244,7 @@ async function executeConnect(options, deps, trace) {
|
|
|
5223
5244
|
supersededAgentIds,
|
|
5224
5245
|
supersededAgentsRetiredLocally,
|
|
5225
5246
|
...replacing ? { retiredAgentIds } : {},
|
|
5247
|
+
...Object.keys(retirementMirrorErrors).length > 0 ? { retirementMirrorErrors } : {},
|
|
5226
5248
|
existingAgentsBeforeWrite: existingAgents.map((a) => ({ agent_id: a.agentId, account_address: a.accountAddress })),
|
|
5227
5249
|
...reboundFrom ? { serverNameReboundFrom: reboundFrom } : {},
|
|
5228
5250
|
setupChallengeExpiresAt: setup.challenge.expires_at,
|
|
@@ -5289,6 +5311,7 @@ function completionOutcome(input) {
|
|
|
5289
5311
|
superseded_agent_ids: input.supersededAgentIds ?? [],
|
|
5290
5312
|
...input.supersededAgentsRetiredLocally !== void 0 ? { superseded_agents_retired_locally: input.supersededAgentsRetiredLocally } : {},
|
|
5291
5313
|
...input.retiredAgentIds ? { retired_agent_ids: input.retiredAgentIds } : {},
|
|
5314
|
+
...input.retirementMirrorErrors ? { retirement_mirror_errors: input.retirementMirrorErrors } : {},
|
|
5292
5315
|
// #3122: always emitted on a completed run (empty list included), for the
|
|
5293
5316
|
// same reason as superseded_agent_ids above.
|
|
5294
5317
|
existing_agents_before_write: input.existingAgentsBeforeWrite ?? [],
|
|
@@ -5962,6 +5985,7 @@ async function runCli(argv, io = {
|
|
|
5962
5985
|
agentId = identity.agent_id ?? "unknown";
|
|
5963
5986
|
} catch {
|
|
5964
5987
|
}
|
|
5988
|
+
const ledgerDir = tombstonesDirForAgentDirectory2(parsed.tombstone.directory);
|
|
5965
5989
|
const info = await writeAgentTombstone2({
|
|
5966
5990
|
directory: parsed.tombstone.directory,
|
|
5967
5991
|
agentId,
|
|
@@ -5970,7 +5994,7 @@ async function runCli(argv, io = {
|
|
|
5970
5994
|
// #3251: the ledger of the root that holds the retired directory —
|
|
5971
5995
|
// ~/.haven/agents/<x> → ~/.haven/tombstones, exactly as before; a
|
|
5972
5996
|
// directory under any other root → <root>/.tombstones.
|
|
5973
|
-
tombstonesDir:
|
|
5997
|
+
tombstonesDir: ledgerDir
|
|
5974
5998
|
});
|
|
5975
5999
|
if (parsed.json) {
|
|
5976
6000
|
io.stdout(`${redactSecrets(JSON.stringify({ tombstoned: true, ...info }))}
|
|
@@ -5982,11 +6006,18 @@ async function runCli(argv, io = {
|
|
|
5982
6006
|
"Key files were NOT touched and nothing was revoked \u2014 revoke the agent on the Haven agent page if you have not already.\n"
|
|
5983
6007
|
);
|
|
5984
6008
|
io.stdout(
|
|
5985
|
-
`A surviving tombstone record was mirrored to ${redactSecrets(info.recordPath)}
|
|
6009
|
+
info.recordPath !== null ? `A surviving tombstone record was mirrored to ${redactSecrets(info.recordPath)}
|
|
6010
|
+
` : (
|
|
6011
|
+
// #3259: the directory IS tombstoned; only the surviving copy failed.
|
|
6012
|
+
redactSecrets(
|
|
6013
|
+
`! The surviving tombstone record could NOT be written to ${ledgerDir} (${info.mirrorError}). The in-place TOMBSTONE.json stands, but --doctor will not list this retirement once the directory is deleted.
|
|
5986
6014
|
`
|
|
6015
|
+
)
|
|
6016
|
+
)
|
|
5987
6017
|
);
|
|
5988
6018
|
io.stdout(
|
|
5989
|
-
"Restart EVERY long-lived MCP host (gateway, TUI workers, editors): each holds the wiring snapshot from its own start time, and the tombstone only speaks when a stale host next probes the old path.
|
|
6019
|
+
"Restart EVERY long-lived MCP host (gateway, TUI workers, editors): each holds the wiring snapshot from its own start time, and the tombstone only speaks when a stale host next probes the old path." + // #3259 review: never claim a copy that the line above says was not written.
|
|
6020
|
+
(info.recordPath !== null ? " The mirrored record keeps this retirement observable even after the agent directory itself is deleted." : "") + "\n"
|
|
5990
6021
|
);
|
|
5991
6022
|
}
|
|
5992
6023
|
return 0;
|
|
@@ -6005,6 +6036,7 @@ async function runCli(argv, io = {
|
|
|
6005
6036
|
const homeDir = homedir11();
|
|
6006
6037
|
const root = parsed.options.credentialsDir ?? join13(homeDir, ".haven", "agents");
|
|
6007
6038
|
const directory = parsed.unwireDir ?? (parsed.options.serverName ? join13(root, parsed.options.serverName) : root);
|
|
6039
|
+
const ledgerDir = tombstonesDirForAgentDirectory2(directory, homeDir);
|
|
6008
6040
|
try {
|
|
6009
6041
|
const result = await unwireAgent2({
|
|
6010
6042
|
directory,
|
|
@@ -6019,7 +6051,7 @@ async function runCli(argv, io = {
|
|
|
6019
6051
|
// directory itself, not a root. ~/.haven/agents/<slug> →
|
|
6020
6052
|
// ~/.haven/tombstones, exactly as before; any other root →
|
|
6021
6053
|
// <root>/.tombstones.
|
|
6022
|
-
tombstonesDir:
|
|
6054
|
+
tombstonesDir: ledgerDir
|
|
6023
6055
|
});
|
|
6024
6056
|
const failures = result.runtimes.filter((r) => r.status === "refused" || r.status === "unreadable");
|
|
6025
6057
|
const retained = result.teardown.status === "retained";
|
|
@@ -6032,6 +6064,8 @@ async function runCli(argv, io = {
|
|
|
6032
6064
|
slug: result.slug ?? null,
|
|
6033
6065
|
directory: result.directory,
|
|
6034
6066
|
tombstoned: result.tombstoned,
|
|
6067
|
+
// #3259: additive — present only when the surviving ledger record failed.
|
|
6068
|
+
...result.mirrorError !== void 0 ? { mirror_error: result.mirrorError } : {},
|
|
6035
6069
|
runtimes: result.runtimes.map((r) => ({
|
|
6036
6070
|
runtime: r.runtime,
|
|
6037
6071
|
label: r.label,
|
|
@@ -6057,6 +6091,12 @@ async function runCli(argv, io = {
|
|
|
6057
6091
|
io.stdout(
|
|
6058
6092
|
result.tombstoned ? " \xB7 Tombstoned first: any long-lived host still resolving the old wrapper gets the HAVEN-TOMBSTONE diagnosis.\n" : " \xB7 Directory was already tombstoned.\n"
|
|
6059
6093
|
);
|
|
6094
|
+
if (result.mirrorError !== void 0) {
|
|
6095
|
+
io.stdout(redactSecrets(
|
|
6096
|
+
` ! Surviving tombstone record: NOT written to ${ledgerDir} (${result.mirrorError}) \u2014 the in-place tombstone stands; --doctor will not list this retirement once the directory is deleted.
|
|
6097
|
+
`
|
|
6098
|
+
));
|
|
6099
|
+
}
|
|
6060
6100
|
for (const r of result.runtimes) {
|
|
6061
6101
|
const mark = r.status === "removed" ? "\u2713" : r.status === "clean" ? "\u2013" : "\u2717";
|
|
6062
6102
|
io.stdout(redactSecrets(` ${mark} ${r.label}: ${r.status}${r.detail ? ` \u2014 ${r.detail}` : ""}
|