@botbuddy/cli 1.8.0 → 1.8.1
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/pw/coordinator.mjs +1 -1
- package/src/pw/run.mjs +14 -4
package/package.json
CHANGED
package/src/pw/coordinator.mjs
CHANGED
|
@@ -8,5 +8,5 @@ export function createProfileCoordinator({ profile, identity, fetchImpl = fetch
|
|
|
8
8
|
const json = await response.json(); if (json.error) throw new Error(json.error.message || "BotBuddy lock verification failed");
|
|
9
9
|
const text = json.result?.content?.find((item) => item.type === "text")?.text; try { return text ? JSON.parse(text) : {}; } catch { throw new Error("BotBuddy lock verification returned invalid JSON"); }
|
|
10
10
|
}
|
|
11
|
-
return { kind: "profile", async status({ host, slot }) { const result = await call("list_resources", { host, subtype: "playwright_lane" }); const list = Array.isArray(result) ? result : result.resources ?? []; const resource = list.find((item) => item.name === `playwright_lane:${host}:${slot}` || String(item.slot) === String(slot)); return resource ? { held: resource.status !== "free", heldBy: resource.owner_agent_id ?? null, heldByName: resource.agents?.name ?? null } : { held: false, heldBy: null, heldByName: null }; }, async emit(event) { await call("record_lane_event", event); }, agentId: identity.agentId };
|
|
11
|
+
return { kind: "profile", async status({ host, slot }) { const result = await call("list_resources", { host, subtype: "playwright_lane" }); const list = Array.isArray(result) ? result : result.resources ?? []; const resource = list.find((item) => item.name === `playwright_lane:${host}:${slot}` || String(item.slot) === String(slot)); return resource ? { held: resource.status !== "free", heldBy: resource.owner_agent_id ?? null, heldByName: resource.agents?.name ?? null, host: resource.host ?? null, name: resource.name ?? null, slot: resource.slot ?? null } : { held: false, heldBy: null, heldByName: null, host: null, name: null, slot: null }; }, async emit(event) { await call("record_lane_event", event); }, agentId: identity.agentId };
|
|
12
12
|
}
|
package/src/pw/run.mjs
CHANGED
|
@@ -42,13 +42,20 @@ async function gate({ env, host, lane, deps }) {
|
|
|
42
42
|
const selfAgentIds = new Set([identity.agentId, sessionAgentId, registeredAgentId].filter(Boolean));
|
|
43
43
|
try {
|
|
44
44
|
const status = await coordinator.status({ host, slot: lane });
|
|
45
|
-
|
|
45
|
+
// The server's alias map can collapse the locally-normalized host further
|
|
46
|
+
// (jonos-macbook-pro -> jonos-mbp). When the lock service returned the resource,
|
|
47
|
+
// prefer its canonical host + name so messages/telemetry match what it stored,
|
|
48
|
+
// rather than the string bb-pw built — the ticket's "match by the canonical host
|
|
49
|
+
// field the server returns" path, without replicating the alias map here.
|
|
50
|
+
const canonicalHost = status.host ?? host;
|
|
51
|
+
const foundLaneName = status.name ?? laneName;
|
|
52
|
+
if (status.held && selfAgentIds.has(status.heldBy)) return { allowed: true, coordinator, canonicalHost };
|
|
46
53
|
// Distinguish an expired/free lane from one another operator holds so the
|
|
47
54
|
// operator knows whether to re-acquire vs. wait for a handover (BOT-660).
|
|
48
55
|
if (!status.held) return { allowed: false, message: `bb-pw: lane lock ${laneName} is not held (caller ${identity.agentId}). Acquire it via BotBuddy first, or set BB_PW_NO_LOCK=1 for local-only work.` };
|
|
49
56
|
const holder = status.heldBy ?? "unknown";
|
|
50
57
|
const holderName = status.heldByName ? ` (${status.heldByName})` : "";
|
|
51
|
-
return { allowed: false, message: `bb-pw: lane lock ${
|
|
58
|
+
return { allowed: false, message: `bb-pw: lane lock ${foundLaneName} is held by ${holder}${holderName}, not you (caller ${identity.agentId}). Acquire it first or set BB_PW_NO_LOCK=1 for local-only work.` };
|
|
52
59
|
} catch (error) {
|
|
53
60
|
return { allowed: false, message: `bb-pw: could not verify lane lock ${laneName} (${error.message}). Set BB_PW_NO_LOCK=1 for local-only work.` };
|
|
54
61
|
}
|
|
@@ -61,9 +68,12 @@ export async function runPw(argv, deps = {}) {
|
|
|
61
68
|
if (plan.scope === "global") { if (plan.mode === "reap") { await (deps.reap ?? (await import("./reap.mjs")).reap)({ env, stdout }); return 0; } if (env.BB_PW_NO_LOCK !== "1") { stderr.write("bb-pw: close-all and kill-all require BB_PW_NO_LOCK=1 because they can affect lanes you do not own.\n"); return 3; } return spawnExec(plan, env); }
|
|
62
69
|
const telemetry = deps.telemetry ?? (await import("./telemetry.mjs")).makeTelemetry({ env }); const host = deps.host ?? hostFor(env);
|
|
63
70
|
const auth = await gate({ env, host, lane: plan.lane, deps }); if (!auth.allowed) { stderr.write(`${auth.message}\n`); return 3; }
|
|
64
|
-
|
|
71
|
+
// Report the host the lock service actually stored (alias-collapsed), so the
|
|
72
|
+
// status view and lane telemetry match the server, not the local string (AC-4).
|
|
73
|
+
const effHost = auth.canonicalHost ?? host;
|
|
74
|
+
if (plan.mode === "status") { const lock = auth.coordinator?.status ? await auth.coordinator.status({ host: effHost, slot: plan.lane }) : null; stdout.write(JSON.stringify({ lane: plan.lane, session: plan.session, host: effHost, lock, spooled_events: telemetry.count(plan.lane) }, null, 2) + "\n"); return 0; }
|
|
65
75
|
if (actionTypeFromMethod(plan.verb) !== "other") telemetry.append(plan.lane, { ts: Date.now(), method: plan.verb, url: actionTypeFromMethod(plan.verb) === NAV ? plan.telemetryUrl : null });
|
|
66
76
|
const inspect = plan.mode === "socket" || (actionTypeFromMethod(plan.verb) === "interaction" && (plan.targetKind === "ref" || plan.fresh)); let code;
|
|
67
77
|
if (inspect) { if (plan.fresh) await socketRun({ ...plan, socketArgs: ["snapshot"] }, env).catch(() => {}); const result = await socketRun({ ...plan, socketArgs: plan.socketArgs ?? plan.execArgv.slice(1) }, env); if (result.text) stdout.write(`${redact(result.text, plan.secretValues)}\n`); if (!result.ok) stderr.write(`${redact(plan.targetKind === "ref" && isStaleRefError(result.error) ? staleRefRemediation(plan.target) : result.error, plan.secretValues)}\n`); code = result.ok ? 0 : 1; } else code = await spawnExec(plan, env);
|
|
68
|
-
if (plan.rollup && code === 0) await telemetry.rollup({ lane: plan.lane, coordinator: auth.coordinator, host }); return code;
|
|
78
|
+
if (plan.rollup && code === 0) await telemetry.rollup({ lane: plan.lane, coordinator: auth.coordinator, host: effHost }); return code;
|
|
69
79
|
}
|