@botbuddy/cli 1.40.10 → 1.40.11
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/package.json +1 -1
- package/src/agent-doctor.mjs +14 -3
- package/src/agent-session.mjs +0 -0
- package/src/machine-host.mjs +26 -0
- package/src/session-bootstrap.mjs +10 -3
package/package.json
CHANGED
package/src/agent-doctor.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// BOT-1649 — diagnose and repair a worktree's short-lived agent credential
|
|
2
2
|
// without ever printing the token unless the operator explicitly requests an
|
|
3
3
|
// export snippet for the current shell.
|
|
4
|
-
import { hostname } from "node:os";
|
|
5
4
|
import { readAgentBinding } from "./wait-profile.mjs";
|
|
6
5
|
import { resolveOwnerToken, CLIENT_KEY_SERVICE, OWNER_TOKEN_SERVICE } from "./cli-credentials.mjs";
|
|
7
6
|
import { readKeychainSecret } from "./agent-credential-store.mjs";
|
|
@@ -9,6 +8,7 @@ import { getConfig } from "./config.mjs";
|
|
|
9
8
|
import { isAgentStateFresh, readAgentState } from "./agent-state.mjs";
|
|
10
9
|
import { selfHealAgentSession } from "./agent-session.mjs";
|
|
11
10
|
import { machineUuid } from "./machine-id.mjs";
|
|
11
|
+
import { machineHost } from "./machine-host.mjs";
|
|
12
12
|
|
|
13
13
|
export function parseDoctorArgs(argv = []) {
|
|
14
14
|
const parsed = { fix: false, printExports: false, json: false, errors: [] };
|
|
@@ -97,6 +97,16 @@ function applyStackLeaseCapability(report, capability) {
|
|
|
97
97
|
if (!capability || typeof capability.status !== "string") return;
|
|
98
98
|
report.stack_lease.capability = capability.status;
|
|
99
99
|
if (capability.reason) report.stack_lease.reason = capability.reason;
|
|
100
|
+
if (capability.reported_host && capability.enrolled_host) {
|
|
101
|
+
report.stack_lease.hostname_drift = {
|
|
102
|
+
reported: capability.reported_host,
|
|
103
|
+
enrolled: capability.enrolled_host,
|
|
104
|
+
remedies: [
|
|
105
|
+
"use the macOS LocalHostName reported by `scutil --get LocalHostName`",
|
|
106
|
+
"or re-enrol the Helper if this is intentionally a different machine",
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
100
110
|
if (capability.status !== "established" && !report.recovery) {
|
|
101
111
|
report.recovery = `stack leases need a device-consented Helper on this machine: ${capability.reason ?? "worktree not attested"}`;
|
|
102
112
|
}
|
|
@@ -126,7 +136,7 @@ export async function doctorAgentAuth({
|
|
|
126
136
|
// inside selfHeal, which the `--fix` gate skips when it reports the key missing.
|
|
127
137
|
ownerToken = readClientKeyStrict,
|
|
128
138
|
selfHeal = selfHealAgentSession,
|
|
129
|
-
hostFn =
|
|
139
|
+
hostFn = machineHost, machineUuidFn = machineUuid,
|
|
130
140
|
} = {}) {
|
|
131
141
|
let owner = null;
|
|
132
142
|
let ownerReadFailed = false;
|
|
@@ -140,6 +150,7 @@ export async function doctorAgentAuth({
|
|
|
140
150
|
// LaunchAgent). Env only, exactly like selfHealAgentSession; never the value.
|
|
141
151
|
const mcpEnv = typeof bound?.mcpEnv === "string" && bound.mcpEnv ? bound.mcpEnv : null;
|
|
142
152
|
const mcpKeyInEnv = Boolean(mcpEnv && typeof env?.[mcpEnv] === "string" && env[mcpEnv]);
|
|
153
|
+
const resolvedHost = await hostFn();
|
|
143
154
|
const report = {
|
|
144
155
|
schema_version: 1,
|
|
145
156
|
worktree: cwd,
|
|
@@ -156,7 +167,7 @@ export async function doctorAgentAuth({
|
|
|
156
167
|
// is known only after a register call, so a plain `bb doctor` reports `unverified`
|
|
157
168
|
// and `--fix` re-registers to (re)establish it (`established` | `unattested`).
|
|
158
169
|
stack_lease: {
|
|
159
|
-
host:
|
|
170
|
+
host: resolvedHost,
|
|
160
171
|
machine_id_readable: Boolean(machineUuidFn()),
|
|
161
172
|
capability: "unverified",
|
|
162
173
|
},
|
package/src/agent-session.mjs
CHANGED
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { hostname, platform } from "node:os";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { canonicalizeHostString } from "./pw/host.mjs";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Return the host identity used by Helper enrolment. macOS's kernel hostname can
|
|
10
|
+
* follow DHCP; Bonjour LocalHostName is stable and is what the Helper records.
|
|
11
|
+
*/
|
|
12
|
+
export async function machineHost({
|
|
13
|
+
platformFn = platform,
|
|
14
|
+
hostnameFn = hostname,
|
|
15
|
+
execFileFn = execFileAsync,
|
|
16
|
+
} = {}) {
|
|
17
|
+
if (platformFn() !== "darwin") return hostnameFn();
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await execFileFn("scutil", ["--get", "LocalHostName"]);
|
|
20
|
+
const localName = canonicalizeHostString(stdout);
|
|
21
|
+
if (localName) return `${localName}.local`;
|
|
22
|
+
} catch {
|
|
23
|
+
// Minimal shells can lack scutil; the existing hostname behaviour is safe.
|
|
24
|
+
}
|
|
25
|
+
return hostnameFn();
|
|
26
|
+
}
|
|
@@ -8,6 +8,8 @@ import { clearAgentSessionIfMatches, drainPendingCleanup, recordPendingCleanup }
|
|
|
8
8
|
import { readAgentBinding } from "./wait-profile.mjs";
|
|
9
9
|
import { resolveAgentKey, resolveOwnerToken } from "./cli-credentials.mjs";
|
|
10
10
|
import { getConfig } from "./config.mjs";
|
|
11
|
+
import { machineHost } from "./machine-host.mjs";
|
|
12
|
+
import { machineUuid } from "./machine-id.mjs";
|
|
11
13
|
|
|
12
14
|
// BOT-1650 → BOT-1649 reconciliation: the harness session lives in the ONE
|
|
13
15
|
// unified client cache (agent-state.mjs), written in main's `agent_session_token`
|
|
@@ -29,7 +31,7 @@ export async function loadHarnessSession({ cwd = process.cwd() } = {}) {
|
|
|
29
31
|
/** Persist a bootstrap-issued session into the unified cache. The bootstrap RPC
|
|
30
32
|
* does not echo the tenant, so derive it from the committed binding exactly as
|
|
31
33
|
* selfHealAgentSession does; host/worktree/minted_at are derived locally. */
|
|
32
|
-
export async function saveHarnessSession(session, { cwd = process.cwd() } = {}) {
|
|
34
|
+
export async function saveHarnessSession(session, { cwd = process.cwd(), machineHostFn = machineHost } = {}) {
|
|
33
35
|
const root = await worktreeRoot(cwd);
|
|
34
36
|
let tenant;
|
|
35
37
|
try { tenant = (await readAgentBinding(root))?.tenant; } catch { tenant = undefined; }
|
|
@@ -39,7 +41,7 @@ export async function saveHarnessSession(session, { cwd = process.cwd() } = {})
|
|
|
39
41
|
agent_session_token: session.session_token,
|
|
40
42
|
session_id: session.session_id ?? null,
|
|
41
43
|
...(tenant ? { tenant } : {}),
|
|
42
|
-
host:
|
|
44
|
+
host: await machineHostFn(),
|
|
43
45
|
worktree: root,
|
|
44
46
|
minted_at: new Date().toISOString(),
|
|
45
47
|
expires_at: session.expires_at,
|
|
@@ -218,6 +220,8 @@ export async function bootstrapHarnessSession({
|
|
|
218
220
|
model = null,
|
|
219
221
|
effort = null,
|
|
220
222
|
harness = type,
|
|
223
|
+
machineHostFn = machineHost,
|
|
224
|
+
machineUuidFn = machineUuid,
|
|
221
225
|
call = callToolJson,
|
|
222
226
|
callOptions = undefined,
|
|
223
227
|
resolveCleanupOptions = durableCleanupOptions,
|
|
@@ -249,11 +253,14 @@ export async function bootstrapHarnessSession({
|
|
|
249
253
|
// bootstrap straight through so the server reuses a still-live incarnation
|
|
250
254
|
// or rotates a truly expired one; never revoke an active wait from a stale
|
|
251
255
|
// local timestamp.
|
|
256
|
+
const host = await machineHostFn();
|
|
257
|
+
const hardwareUuid = machineUuidFn();
|
|
252
258
|
const response = await call("bootstrap_agent_session", {
|
|
253
259
|
name,
|
|
254
260
|
type,
|
|
255
261
|
harness_session_id: harnessSessionId,
|
|
256
|
-
host
|
|
262
|
+
host,
|
|
263
|
+
...(hardwareUuid ? { machine_uuid: hardwareUuid } : {}),
|
|
257
264
|
worktree,
|
|
258
265
|
...(branch ? { branch } : {}),
|
|
259
266
|
...(ticketId ? { ticket_id: ticketId } : {}),
|