@haven_ai/connect 0.0.0-dev.202609230656.9b1d80a → 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 +11 -3
- package/dist/cli.cjs +92 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +92 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +92 -22
- 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 +92 -22
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -159,7 +159,9 @@ minutes. Two rules follow:
|
|
|
159
159
|
This replaces the directory's signer wrapper with a diagnostic that logs the
|
|
160
160
|
retirement (agent id, date, reason, restart guidance) to the host's MCP
|
|
161
161
|
stderr log on every probe, and records it in `TOMBSTONE.json` for
|
|
162
|
-
`--doctor
|
|
162
|
+
`--doctor` — with a copy in `~/.haven/tombstones/` (or `.tombstones/` inside
|
|
163
|
+
a custom credentials root), so the retirement stays visible after the
|
|
164
|
+
directory is deleted. It touches no key material and revokes nothing — revoke the
|
|
163
165
|
agent on the Haven agent page yourself. Delete the tombstone only once every
|
|
164
166
|
long-lived host has been restarted.
|
|
165
167
|
|
|
@@ -173,7 +175,12 @@ minutes. Two rules follow:
|
|
|
173
175
|
and a refusal is `{"tombstoned": false, "error": {"code", "next_action"}}`
|
|
174
176
|
with exit 1 — so check the result rather than assuming silence means success
|
|
175
177
|
(#2175). The `message` field is present only for connector-authored refusals;
|
|
176
|
-
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).
|
|
177
184
|
|
|
178
185
|
### Unwiring an agent (`--unwire`, #2169)
|
|
179
186
|
|
|
@@ -350,7 +357,8 @@ read-only verification tools, `hosted_mcp_url`, `superseded_agent_ids`,
|
|
|
350
357
|
`existing_agents_before_write` (#3122 — the other live-keyed directories named
|
|
351
358
|
before the first write, each with the account it spends from), and — on a run
|
|
352
359
|
that replaced existing wiring — `superseded_agents_retired_locally` with
|
|
353
|
-
`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
|
|
354
362
|
run took a server name over from another directory's local binding record. It
|
|
355
363
|
contains no API key, private key, credential
|
|
356
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
|
|
@@ -1590,7 +1590,7 @@ async function probeLocalSignerCredential(signerPath) {
|
|
|
1590
1590
|
}
|
|
1591
1591
|
}
|
|
1592
1592
|
async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4) {
|
|
1593
|
-
return new Promise((
|
|
1593
|
+
return new Promise((resolve11) => {
|
|
1594
1594
|
const child = child_process.spawn(command, args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
1595
1595
|
let stdout = "";
|
|
1596
1596
|
let settled = false;
|
|
@@ -1602,7 +1602,7 @@ async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4)
|
|
|
1602
1602
|
settled = true;
|
|
1603
1603
|
clearTimeout(timeout);
|
|
1604
1604
|
child.kill();
|
|
1605
|
-
|
|
1605
|
+
resolve11(result);
|
|
1606
1606
|
};
|
|
1607
1607
|
const timeout = setTimeout(() => finish({ status: "timeout" }), timeoutMs);
|
|
1608
1608
|
child.on("error", () => finish({ status: "process_error" }));
|
|
@@ -3000,16 +3000,28 @@ var init_runtime_install = __esm({
|
|
|
3000
3000
|
// src/tombstone.ts
|
|
3001
3001
|
var tombstone_exports = {};
|
|
3002
3002
|
__export(tombstone_exports, {
|
|
3003
|
+
ROOT_TOMBSTONES_DIRNAME: () => ROOT_TOMBSTONES_DIRNAME,
|
|
3003
3004
|
TOMBSTONE_FILENAME: () => TOMBSTONE_FILENAME,
|
|
3004
3005
|
TOMBSTONE_MARKER: () => TOMBSTONE_MARKER,
|
|
3005
3006
|
defaultTombstonesDir: () => defaultTombstonesDir,
|
|
3006
3007
|
readAgentTombstone: () => readAgentTombstone,
|
|
3007
3008
|
readTombstoneRecords: () => readTombstoneRecords,
|
|
3009
|
+
tombstonesDirForAgentDirectory: () => tombstonesDirForAgentDirectory,
|
|
3010
|
+
tombstonesDirForCredentialRoot: () => tombstonesDirForCredentialRoot,
|
|
3008
3011
|
writeAgentTombstone: () => writeAgentTombstone
|
|
3009
3012
|
});
|
|
3010
3013
|
function defaultTombstonesDir(baseDir) {
|
|
3011
3014
|
return path.join(baseDir ?? path.join(os.homedir(), ".haven"), "tombstones");
|
|
3012
3015
|
}
|
|
3016
|
+
function tombstonesDirForCredentialRoot(credentialRoot, homeDir = os.homedir()) {
|
|
3017
|
+
if (!credentialRoot) return defaultTombstonesDir(path.join(homeDir, ".haven"));
|
|
3018
|
+
const root = path.resolve(credentialRoot);
|
|
3019
|
+
if (root === path.resolve(homeDir, ".haven", "agents")) return defaultTombstonesDir(path.join(homeDir, ".haven"));
|
|
3020
|
+
return path.join(root, ROOT_TOMBSTONES_DIRNAME);
|
|
3021
|
+
}
|
|
3022
|
+
function tombstonesDirForAgentDirectory(directory, homeDir = os.homedir()) {
|
|
3023
|
+
return tombstonesDirForCredentialRoot(path.dirname(path.resolve(directory)), homeDir);
|
|
3024
|
+
}
|
|
3013
3025
|
async function readTombstoneRecords(tombstonesDir) {
|
|
3014
3026
|
const root = tombstonesDir ?? defaultTombstonesDir();
|
|
3015
3027
|
let entries = [];
|
|
@@ -3086,9 +3098,14 @@ async function writeAgentTombstone(input) {
|
|
|
3086
3098
|
const record = JSON.stringify(info, null, 2) + "\n";
|
|
3087
3099
|
await promises.writeFile(path.join(input.directory, TOMBSTONE_FILENAME), record, "utf8");
|
|
3088
3100
|
const root = input.tombstonesDir ?? defaultTombstonesDir();
|
|
3089
|
-
await promises.mkdir(root, { recursive: true, mode: 448 });
|
|
3090
3101
|
const recordPath = path.join(root, `${info.agent_id}.json`);
|
|
3091
|
-
|
|
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
|
+
}
|
|
3092
3109
|
return { ...info, recordPath };
|
|
3093
3110
|
}
|
|
3094
3111
|
async function readAgentTombstone(directory) {
|
|
@@ -3100,12 +3117,13 @@ async function readAgentTombstone(directory) {
|
|
|
3100
3117
|
return null;
|
|
3101
3118
|
}
|
|
3102
3119
|
}
|
|
3103
|
-
var TOMBSTONE_FILENAME, MIRROR_MODE, TOMBSTONE_MARKER;
|
|
3120
|
+
var TOMBSTONE_FILENAME, ROOT_TOMBSTONES_DIRNAME, MIRROR_MODE, TOMBSTONE_MARKER;
|
|
3104
3121
|
var init_tombstone = __esm({
|
|
3105
3122
|
"src/tombstone.ts"() {
|
|
3106
3123
|
init_redact();
|
|
3107
3124
|
init_connect_error();
|
|
3108
3125
|
TOMBSTONE_FILENAME = "TOMBSTONE.json";
|
|
3126
|
+
ROOT_TOMBSTONES_DIRNAME = ".tombstones";
|
|
3109
3127
|
MIRROR_MODE = 384;
|
|
3110
3128
|
TOMBSTONE_MARKER = "HAVEN-TOMBSTONE";
|
|
3111
3129
|
}
|
|
@@ -3156,7 +3174,7 @@ async function unwireAgent(input) {
|
|
|
3156
3174
|
const slug = input.slug ?? sidecar?.server_name;
|
|
3157
3175
|
const names = serverNamesFor(slug);
|
|
3158
3176
|
const runtimes = [];
|
|
3159
|
-
const tombstoned = await tombstoneDirectoryIfAbsent({
|
|
3177
|
+
const { written: tombstoned, mirrorError } = await tombstoneDirectoryIfAbsent({
|
|
3160
3178
|
directory: input.directory,
|
|
3161
3179
|
agentId,
|
|
3162
3180
|
reason: input.reason ?? "unwired via --unwire",
|
|
@@ -3250,7 +3268,16 @@ async function unwireAgent(input) {
|
|
|
3250
3268
|
const bindingReleased = await clearMcpServerBinding(input.directory);
|
|
3251
3269
|
const teardown = await decideTeardown(identity, input);
|
|
3252
3270
|
if (teardown.status !== "retained") await teardownLocalKeyMaterial(input.directory, identity);
|
|
3253
|
-
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
|
+
};
|
|
3254
3281
|
}
|
|
3255
3282
|
async function decideTeardown(identity, input) {
|
|
3256
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.";
|
|
@@ -3295,9 +3322,9 @@ async function decideTeardown(identity, input) {
|
|
|
3295
3322
|
}
|
|
3296
3323
|
}
|
|
3297
3324
|
async function tombstoneDirectoryIfAbsent(input) {
|
|
3298
|
-
if (await readOptionalText(path.join(input.directory, TOMBSTONE_FILENAME)) !== null) return false;
|
|
3299
|
-
await writeAgentTombstone(input);
|
|
3300
|
-
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 };
|
|
3301
3328
|
}
|
|
3302
3329
|
async function teardownLocalKeyMaterial(directory, identity) {
|
|
3303
3330
|
await Promise.all([
|
|
@@ -4330,7 +4357,8 @@ async function runDoctor(input, deps = {}) {
|
|
|
4330
4357
|
if (retired.length > 0) parts.push(`tombstoned (keys removed): ${retired.join(", ")}`);
|
|
4331
4358
|
if (unverifiable.length > 0) parts.push(`could not verify: ${unverifiable.join(", ")}`);
|
|
4332
4359
|
const knownIds = new Set([...inventory].map((e) => e.agentId ?? path.basename(e.directory)));
|
|
4333
|
-
const
|
|
4360
|
+
const ledgerDir = input.credentialsDir ? tombstonesDirForAgentDirectory(input.credentialsDir, homeDir) : path.join(homeDir, ".haven", "tombstones");
|
|
4361
|
+
const ghostRecords = (await readTombstoneRecords(ledgerDir)).filter(
|
|
4334
4362
|
(rec) => !knownIds.has(rec.agent_id)
|
|
4335
4363
|
);
|
|
4336
4364
|
if (ghostRecords.length > 0) {
|
|
@@ -4828,9 +4856,10 @@ async function pathExists2(path) {
|
|
|
4828
4856
|
|
|
4829
4857
|
// src/runtime.ts
|
|
4830
4858
|
init_unwire();
|
|
4859
|
+
init_tombstone();
|
|
4831
4860
|
init_local_mcp_runtime();
|
|
4832
4861
|
init_runtime_manifest();
|
|
4833
|
-
var CONNECTOR_VERSION = "0.0.0-dev.
|
|
4862
|
+
var CONNECTOR_VERSION = "0.0.0-dev.202609232125.72f3d20";
|
|
4834
4863
|
var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
|
|
4835
4864
|
var failureOutcomesByError = /* @__PURE__ */ new WeakMap();
|
|
4836
4865
|
function failureOutcomeFor(runtimeHint, error) {
|
|
@@ -5118,6 +5147,7 @@ async function executeConnect(options, deps, trace) {
|
|
|
5118
5147
|
}
|
|
5119
5148
|
let supersededAgentsRetiredLocally;
|
|
5120
5149
|
const retiredAgentIds = [];
|
|
5150
|
+
const retirementMirrorErrors = {};
|
|
5121
5151
|
if (replacing) {
|
|
5122
5152
|
if (runtimeInstall.errorCode) {
|
|
5123
5153
|
supersededAgentsRetiredLocally = false;
|
|
@@ -5128,12 +5158,21 @@ async function executeConnect(options, deps, trace) {
|
|
|
5128
5158
|
supersededAgentsRetiredLocally = true;
|
|
5129
5159
|
for (const entry of replacing.superseded) {
|
|
5130
5160
|
try {
|
|
5131
|
-
await tombstoneDirectoryIfAbsent({
|
|
5161
|
+
const { mirrorError } = await tombstoneDirectoryIfAbsent({
|
|
5132
5162
|
directory: entry.directory,
|
|
5133
5163
|
agentId: entry.agentId,
|
|
5134
5164
|
reason: "replaced by a new setup (--replace)",
|
|
5135
|
-
replacedBy: registration.agent_id
|
|
5165
|
+
replacedBy: registration.agent_id,
|
|
5166
|
+
// #3251: the ledger follows this run's credential root (default
|
|
5167
|
+
// root → ~/.haven/tombstones, as before; any other → <root>/.tombstones).
|
|
5168
|
+
tombstonesDir: tombstonesDirForCredentialRoot(options.credentialsDir)
|
|
5136
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
|
+
}
|
|
5137
5176
|
await teardownLocalKeyMaterial(entry.directory, await readIdentityFile(entry.directory));
|
|
5138
5177
|
await clearMcpServerBinding(entry.directory);
|
|
5139
5178
|
retiredAgentIds.push(entry.agentId);
|
|
@@ -5205,6 +5244,7 @@ async function executeConnect(options, deps, trace) {
|
|
|
5205
5244
|
supersededAgentIds,
|
|
5206
5245
|
supersededAgentsRetiredLocally,
|
|
5207
5246
|
...replacing ? { retiredAgentIds } : {},
|
|
5247
|
+
...Object.keys(retirementMirrorErrors).length > 0 ? { retirementMirrorErrors } : {},
|
|
5208
5248
|
existingAgentsBeforeWrite: existingAgents.map((a) => ({ agent_id: a.agentId, account_address: a.accountAddress })),
|
|
5209
5249
|
...reboundFrom ? { serverNameReboundFrom: reboundFrom } : {},
|
|
5210
5250
|
setupChallengeExpiresAt: setup.challenge.expires_at,
|
|
@@ -5271,6 +5311,7 @@ function completionOutcome(input) {
|
|
|
5271
5311
|
superseded_agent_ids: input.supersededAgentIds ?? [],
|
|
5272
5312
|
...input.supersededAgentsRetiredLocally !== void 0 ? { superseded_agents_retired_locally: input.supersededAgentsRetiredLocally } : {},
|
|
5273
5313
|
...input.retiredAgentIds ? { retired_agent_ids: input.retiredAgentIds } : {},
|
|
5314
|
+
...input.retirementMirrorErrors ? { retirement_mirror_errors: input.retirementMirrorErrors } : {},
|
|
5274
5315
|
// #3122: always emitted on a completed run (empty list included), for the
|
|
5275
5316
|
// same reason as superseded_agent_ids above.
|
|
5276
5317
|
existing_agents_before_write: input.existingAgentsBeforeWrite ?? [],
|
|
@@ -5459,7 +5500,7 @@ function describeWaitBound(timeoutMs) {
|
|
|
5459
5500
|
async function waitForBudgetApproval(api, setupId, apiKey, log, options = {}) {
|
|
5460
5501
|
const intervalMs = options.intervalMs ?? 5e3;
|
|
5461
5502
|
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
5462
|
-
const sleep = options.sleep ?? ((ms) => new Promise((
|
|
5503
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
|
|
5463
5504
|
const maxPolls = Math.max(1, Math.floor(timeoutMs / intervalMs));
|
|
5464
5505
|
const remindEvery = Math.max(1, Math.floor(3e4 / intervalMs));
|
|
5465
5506
|
let waitingAnnounced = false;
|
|
@@ -5932,7 +5973,7 @@ async function runCli(argv, io = {
|
|
|
5932
5973
|
return 0;
|
|
5933
5974
|
}
|
|
5934
5975
|
if (parsed.tombstone) {
|
|
5935
|
-
const { writeAgentTombstone: writeAgentTombstone2 } = await Promise.resolve().then(() => (init_tombstone(), tombstone_exports));
|
|
5976
|
+
const { writeAgentTombstone: writeAgentTombstone2, tombstonesDirForAgentDirectory: tombstonesDirForAgentDirectory2 } = await Promise.resolve().then(() => (init_tombstone(), tombstone_exports));
|
|
5936
5977
|
const { readFile: readFile15 } = await import('fs/promises');
|
|
5937
5978
|
const { join: join13 } = await import('path');
|
|
5938
5979
|
try {
|
|
@@ -5944,11 +5985,16 @@ async function runCli(argv, io = {
|
|
|
5944
5985
|
agentId = identity.agent_id ?? "unknown";
|
|
5945
5986
|
} catch {
|
|
5946
5987
|
}
|
|
5988
|
+
const ledgerDir = tombstonesDirForAgentDirectory2(parsed.tombstone.directory);
|
|
5947
5989
|
const info = await writeAgentTombstone2({
|
|
5948
5990
|
directory: parsed.tombstone.directory,
|
|
5949
5991
|
agentId,
|
|
5950
5992
|
reason: parsed.tombstone.reason ?? "retired by operator via --tombstone",
|
|
5951
|
-
replacedBy: parsed.tombstone.replacedBy
|
|
5993
|
+
replacedBy: parsed.tombstone.replacedBy,
|
|
5994
|
+
// #3251: the ledger of the root that holds the retired directory —
|
|
5995
|
+
// ~/.haven/agents/<x> → ~/.haven/tombstones, exactly as before; a
|
|
5996
|
+
// directory under any other root → <root>/.tombstones.
|
|
5997
|
+
tombstonesDir: ledgerDir
|
|
5952
5998
|
});
|
|
5953
5999
|
if (parsed.json) {
|
|
5954
6000
|
io.stdout(`${redactSecrets(JSON.stringify({ tombstoned: true, ...info }))}
|
|
@@ -5960,11 +6006,18 @@ async function runCli(argv, io = {
|
|
|
5960
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"
|
|
5961
6007
|
);
|
|
5962
6008
|
io.stdout(
|
|
5963
|
-
`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.
|
|
5964
6014
|
`
|
|
6015
|
+
)
|
|
6016
|
+
)
|
|
5965
6017
|
);
|
|
5966
6018
|
io.stdout(
|
|
5967
|
-
"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"
|
|
5968
6021
|
);
|
|
5969
6022
|
}
|
|
5970
6023
|
return 0;
|
|
@@ -5977,11 +6030,13 @@ async function runCli(argv, io = {
|
|
|
5977
6030
|
}
|
|
5978
6031
|
if (parsed.unwire) {
|
|
5979
6032
|
const { unwireAgent: unwireAgent2 } = await Promise.resolve().then(() => (init_unwire(), unwire_exports));
|
|
6033
|
+
const { tombstonesDirForAgentDirectory: tombstonesDirForAgentDirectory2 } = await Promise.resolve().then(() => (init_tombstone(), tombstone_exports));
|
|
5980
6034
|
const { homedir: homedir11 } = await import('os');
|
|
5981
6035
|
const { join: join13 } = await import('path');
|
|
5982
6036
|
const homeDir = homedir11();
|
|
5983
6037
|
const root = parsed.options.credentialsDir ?? join13(homeDir, ".haven", "agents");
|
|
5984
6038
|
const directory = parsed.unwireDir ?? (parsed.options.serverName ? join13(root, parsed.options.serverName) : root);
|
|
6039
|
+
const ledgerDir = tombstonesDirForAgentDirectory2(directory, homeDir);
|
|
5985
6040
|
try {
|
|
5986
6041
|
const result = await unwireAgent2({
|
|
5987
6042
|
directory,
|
|
@@ -5989,7 +6044,14 @@ async function runCli(argv, io = {
|
|
|
5989
6044
|
reason: parsed.unwire.reason,
|
|
5990
6045
|
replacedBy: parsed.unwire.replacedBy,
|
|
5991
6046
|
destroyKeyMaterial: parsed.unwire.destroyKeyMaterial,
|
|
5992
|
-
homeDir
|
|
6047
|
+
homeDir,
|
|
6048
|
+
// #3251: the ledger of the root that holds the directory being
|
|
6049
|
+
// retired. Derived from the RESOLVED directory, not from
|
|
6050
|
+
// --credentials-dir: `--unwire --credentials-dir <path>` names the agent
|
|
6051
|
+
// directory itself, not a root. ~/.haven/agents/<slug> →
|
|
6052
|
+
// ~/.haven/tombstones, exactly as before; any other root →
|
|
6053
|
+
// <root>/.tombstones.
|
|
6054
|
+
tombstonesDir: ledgerDir
|
|
5993
6055
|
});
|
|
5994
6056
|
const failures = result.runtimes.filter((r) => r.status === "refused" || r.status === "unreadable");
|
|
5995
6057
|
const retained = result.teardown.status === "retained";
|
|
@@ -6002,6 +6064,8 @@ async function runCli(argv, io = {
|
|
|
6002
6064
|
slug: result.slug ?? null,
|
|
6003
6065
|
directory: result.directory,
|
|
6004
6066
|
tombstoned: result.tombstoned,
|
|
6067
|
+
// #3259: additive — present only when the surviving ledger record failed.
|
|
6068
|
+
...result.mirrorError !== void 0 ? { mirror_error: result.mirrorError } : {},
|
|
6005
6069
|
runtimes: result.runtimes.map((r) => ({
|
|
6006
6070
|
runtime: r.runtime,
|
|
6007
6071
|
label: r.label,
|
|
@@ -6027,6 +6091,12 @@ async function runCli(argv, io = {
|
|
|
6027
6091
|
io.stdout(
|
|
6028
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"
|
|
6029
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
|
+
}
|
|
6030
6100
|
for (const r of result.runtimes) {
|
|
6031
6101
|
const mark = r.status === "removed" ? "\u2713" : r.status === "clean" ? "\u2013" : "\u2717";
|
|
6032
6102
|
io.stdout(redactSecrets(` ${mark} ${r.label}: ${r.status}${r.detail ? ` \u2014 ${r.detail}` : ""}
|