@botbuddy/cli 1.8.1 → 1.8.3
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/bin/pw.mjs +10 -0
- package/package.json +2 -1
- package/src/commands.mjs +2 -1
- package/src/pw/coordinator.mjs +1 -1
- package/src/pw/run.mjs +18 -10
package/bin/pw.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// BOT-1491: `pw` is the first-class short binary for the lock-gated Playwright
|
|
3
|
+
// lane driver. `bb-pw` remains a working alias (bin/bb-pw.mjs) and
|
|
4
|
+
// `botbuddy pw …` the subcommand — all three call the same runPw entrypoint.
|
|
5
|
+
import { runPw } from "../src/pw/run.mjs";
|
|
6
|
+
|
|
7
|
+
runPw(process.argv.slice(2)).then((code) => process.exit(code)).catch((error) => {
|
|
8
|
+
console.error(`pw: ${error?.message ?? error}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botbuddy/cli",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
4
|
"description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"botbuddy": "./bin/botbuddy.mjs",
|
|
8
|
+
"pw": "./bin/pw.mjs",
|
|
8
9
|
"bb-pw": "./bin/bb-pw.mjs"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
package/src/commands.mjs
CHANGED
|
@@ -105,7 +105,8 @@ ${bold("AGENT WAITS")}
|
|
|
105
105
|
|
|
106
106
|
${bold("BROWSER LANES")}
|
|
107
107
|
pw <lane> <verb> [args…] Drive a lock-gated Playwright lane
|
|
108
|
-
|
|
108
|
+
(also the short ${cyan("pw")} binary, or ${cyan("bb-pw")} alias)
|
|
109
|
+
pw --help Show lane usage
|
|
109
110
|
|
|
110
111
|
${bold("OTHER")}
|
|
111
112
|
locks -m [--host name] Reserve typed local resources, including Playwright MCP lanes
|
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, 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 };
|
|
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)); /* BOT-1490: canonical_host is a TOP-LEVEL field the server echoes even on an empty page (not-held lane); owner_is_caller is per-row. Older servers send neither → null. */ const canonicalHost = (result && !Array.isArray(result) ? result.canonical_host : null) ?? null; 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, canonicalHost, ownerIsCaller: resource.owner_is_caller ?? null } : { held: false, heldBy: null, heldByName: null, host: null, name: null, slot: null, canonicalHost, ownerIsCaller: null }; }, async emit(event) { await call("record_lane_event", event); }, agentId: identity.agentId };
|
|
12
12
|
}
|
package/src/pw/run.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { loadConfig, getConfig } from "../config.mjs";
|
|
|
11
11
|
// server-side, so the lane name bb-pw builds/matches/prints is the one the lock
|
|
12
12
|
// kernel actually stored ("jonos-mbp:8", not "Jonos-MBP.localdomain:8").
|
|
13
13
|
const hostFor = (env) => canonicalizeHostString(env.PLAYWRIGHT_MCP_HOST || env.HOSTNAME || os.hostname());
|
|
14
|
-
function help(out) { out.write("Usage:
|
|
14
|
+
function help(out) { out.write("Usage: pw [--profile <name>] [--session-id <id>] <lane> <verb> [args…]\n\nAliases: bb-pw <lane> <verb> [args…] · botbuddy pw <lane> <verb> [args…]\n (all three drive the same lock-gated Playwright lane)\n\n--session-id <id> accept a lane held by this arming-session agent id (from\n register_agent); defaults to $BOTBUDDY_SESSION_ID, then the\n id saved by `botbuddy register`.\n"); }
|
|
15
15
|
function redact(value, secretValues = []) { return secretValues.reduce((text, secret) => secret ? text.split(secret).join("[redacted]") : text, String(value ?? "")); }
|
|
16
16
|
// BOT-1488: the register_agent identity for this machine, persisted by
|
|
17
17
|
// `botbuddy register` into ~/.botbuddy/config.json. This is the SESSION agent
|
|
@@ -43,16 +43,24 @@ async function gate({ env, host, lane, deps }) {
|
|
|
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).
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
46
|
+
// (jonos-macbook-pro -> jonos-mbp). BOT-1490: the lock service now echoes that
|
|
47
|
+
// canonical host as a top-level field EVEN on a not-held lane (empty page), so
|
|
48
|
+
// prefer it for every lane name/message; fall back to the resource host
|
|
49
|
+
// (BOT-1488, only present when a resource was returned) then the local string —
|
|
50
|
+
// without replicating the alias map here.
|
|
51
|
+
const canonicalHost = status.canonicalHost ?? status.host ?? host;
|
|
52
|
+
const foundLaneName = status.name ?? `playwright_lane:${canonicalHost}:${lane}`;
|
|
53
|
+
// Allow when a local id matches the holder (BOT-1488: profile / --session-id /
|
|
54
|
+
// $BOTBUDDY_SESSION_ID / ~/.botbuddy/config.json) OR the lock service attributes
|
|
55
|
+
// the holder to the caller's owner (BOT-1490 owner_is_caller — server-authoritative,
|
|
56
|
+
// closing the shared-hostname gap without trusting a local file). The local paths
|
|
57
|
+
// stay as fallbacks for older servers that do not send owner_is_caller.
|
|
58
|
+
if (status.held && (selfAgentIds.has(status.heldBy) || status.ownerIsCaller === true)) return { allowed: true, coordinator, canonicalHost };
|
|
53
59
|
// Distinguish an expired/free lane from one another operator holds so the
|
|
54
|
-
// operator knows whether to re-acquire vs. wait for a handover (BOT-660).
|
|
55
|
-
|
|
60
|
+
// operator knows whether to re-acquire vs. wait for a handover (BOT-660). Name it
|
|
61
|
+
// by the server's canonical host so a not-held message matches the dashboard even
|
|
62
|
+
// when local normalization cannot reach the alias-collapsed key (BOT-1490 AC-5).
|
|
63
|
+
if (!status.held) return { allowed: false, message: `bb-pw: lane lock ${foundLaneName} is not held (caller ${identity.agentId}). Acquire it via BotBuddy first, or set BB_PW_NO_LOCK=1 for local-only work.` };
|
|
56
64
|
const holder = status.heldBy ?? "unknown";
|
|
57
65
|
const holderName = status.heldByName ? ` (${status.heldByName})` : "";
|
|
58
66
|
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.` };
|