usebeeline 0.0.30 → 0.0.31

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.
Files changed (2) hide show
  1. package/dist/usebeeline.mjs +83 -15
  2. package/package.json +1 -1
@@ -3193,14 +3193,6 @@ function toolCallEntries(updates) {
3193
3193
  }
3194
3194
  return [...calls.values()];
3195
3195
  }
3196
- function isMutatingPermissionRequest(request) {
3197
- const tool = request.toolCall;
3198
- const kind = tool?.kind?.toLowerCase();
3199
- if (kind && ["edit", "execute", "delete", "move"].includes(kind))
3200
- return true;
3201
- const description = [tool?.title, tool?.rawInput].filter((value) => value !== void 0).map((value) => typeof value === "string" ? value : JSON.stringify(value)).join(" ").toLowerCase();
3202
- return /(^|[^a-z])(str_replace|write|edit|shell|bash|execute|create|delete|remove|move|rename|patch|apply_patch)([^a-z]|$)/.test(description);
3203
- }
3204
3196
  var AcpClient = class extends EventEmitter {
3205
3197
  child = null;
3206
3198
  buf = "";
@@ -3227,6 +3219,7 @@ var AcpClient = class extends EventEmitter {
3227
3219
  inheritProcessEnv;
3228
3220
  autoApprove;
3229
3221
  permissionHandler;
3222
+ permissionAllowlist;
3230
3223
  constructor(opts) {
3231
3224
  super();
3232
3225
  const command = opts.agentCommand ?? opts.agentBinary;
@@ -3241,6 +3234,7 @@ var AcpClient = class extends EventEmitter {
3241
3234
  this.inheritProcessEnv = opts.inheritProcessEnv ?? process.env.BUZZY_BODY_AGENT_ENV_INHERIT === "1";
3242
3235
  this.autoApprove = opts.autoApprovePermissions ?? true;
3243
3236
  this.permissionHandler = opts.permissionHandler;
3237
+ this.permissionAllowlist = opts.permissionAllowlist;
3244
3238
  }
3245
3239
  async start(timeoutMs = 6e4) {
3246
3240
  if (this.alive)
@@ -3679,7 +3673,14 @@ var AcpClient = class extends EventEmitter {
3679
3673
  if (tracked)
3680
3674
  p.toolCall = { ...tracked, ...p.toolCall };
3681
3675
  let decision = this.autoApprove ? "allow" : "reject";
3682
- if (this.permissionHandler) {
3676
+ if (!this.autoApprove && this.permissionAllowlist) {
3677
+ try {
3678
+ decision = this.permissionAllowlist(p) ? "allow" : "reject";
3679
+ } catch (error) {
3680
+ this.emit("permission/error", error);
3681
+ decision = "reject";
3682
+ }
3683
+ } else if (this.permissionHandler) {
3683
3684
  try {
3684
3685
  decision = await this.permissionHandler(p);
3685
3686
  } catch (error) {
@@ -4170,7 +4171,7 @@ var BEELINE_ROOM_CAPABILITIES = [
4170
4171
  "The repository filesystem is read-only in this Room session.",
4171
4172
  "You may address any Room member, including another agent, by writing @name in your reply; the server routes that mention to them.",
4172
4173
  "Tag another agent only when you need something from them: a question, a handoff, a task. Never tag to acknowledge, agree, or say you are ready. If nothing is actionable, do not reply.",
4173
- "The beeline-readonly-mcp inspection tools and beeline-agent Room action tools are mounted for this session.",
4174
+ "The beeline-readonly-mcp inspection tools and beeline-agent Room action tools are mounted. You can use beeline-readonly-mcp to search and read the bound repository without opening a corner; beeline-agent provides the host-governed Room actions.",
4174
4175
  "When repository work is needed, you MUST call beeline-agent open_corner with a one-paragraph summary of the complete objective. The host-governed call is the only way to start write work.",
4175
4176
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
4176
4177
  ].join(" ");
@@ -4781,6 +4782,7 @@ var READ_ONLY_PERMISSION_TITLES = new Set(READ_ONLY_TOOL_NAMES.flatMap((tool) =>
4781
4782
  `${READ_ONLY_MCP_SERVER_NAME}/${tool}`
4782
4783
  ]));
4783
4784
  var READ_ONLY_TOOL_SET = new Set(READ_ONLY_TOOL_NAMES);
4785
+ var TOOL_NAME_SEPARATORS = ["__", ".", "/", ":", " "];
4784
4786
  var READ_ONLY_SERVER_TITLE_PREFIXES = [
4785
4787
  `mcp__${READ_ONLY_MCP_SERVER_NAME}__`,
4786
4788
  `mcp__${READ_ONLY_MCP_TOOL_SERVER_NAME}__`,
@@ -4798,6 +4800,27 @@ function shellPayload(toolCall) {
4798
4800
  const record2 = rawInput;
4799
4801
  return typeof record2.command === "string" || typeof record2.cmd === "string";
4800
4802
  }
4803
+ function isReadOnlyMcpPermissionRequest(request) {
4804
+ const toolCall = request.toolCall;
4805
+ const title = toolCall?.title?.trim() ?? "";
4806
+ const rawInput = toolCall?.rawInput;
4807
+ const mcpCall = Boolean(rawInput) && typeof rawInput === "object" && !Array.isArray(rawInput) ? rawInput : void 0;
4808
+ if (mcpCall?.server === READ_ONLY_MCP_SERVER_NAME && typeof mcpCall.tool === "string" && READ_ONLY_TOOL_SET.has(mcpCall.tool)) {
4809
+ return true;
4810
+ }
4811
+ if (shellPayload(toolCall))
4812
+ return false;
4813
+ if (READ_ONLY_PERMISSION_TITLES.has(title))
4814
+ return true;
4815
+ if (READ_ONLY_SERVER_TITLE_PREFIXES.some((prefix) => title.startsWith(prefix))) {
4816
+ for (const tool of READ_ONLY_TOOL_NAMES) {
4817
+ if (title === tool || title.endsWith(`(${tool})`) || TOOL_NAME_SEPARATORS.some((separator) => title.endsWith(`${separator}${tool}`))) {
4818
+ return true;
4819
+ }
4820
+ }
4821
+ }
4822
+ return false;
4823
+ }
4801
4824
  function isBeelineAgentMcpPermissionRequest(request) {
4802
4825
  const toolCall = request.toolCall;
4803
4826
  const title = toolCall?.title?.trim() ?? "";
@@ -14329,6 +14352,9 @@ async function wait(ms, signal) {
14329
14352
  // apps/body/dist/monolith-room-turn.js
14330
14353
  import { mkdir as mkdir4 } from "node:fs/promises";
14331
14354
  import { homedir as homedir5 } from "node:os";
14355
+ function isRoomMcpPermissionRequest(request) {
14356
+ return isReadOnlyMcpPermissionRequest(request) || isBeelineAgentMcpPermissionRequest(request);
14357
+ }
14332
14358
  function roomPrincipalMayAddressAgent(authority, humanPermitted) {
14333
14359
  return authority.member && (authority.principalKind === "agent" || authority.principalKind === "human" && humanPermitted);
14334
14360
  }
@@ -14447,7 +14473,7 @@ var MonolithRoomTurnLoop = class {
14447
14473
  agentCwd: this.options.cwd,
14448
14474
  agentLabel: command,
14449
14475
  autoApprovePermissions: false,
14450
- permissionHandler: (request) => Promise.resolve(isBeelineAgentMcpPermissionRequest(request) ? "allow" : isMutatingPermissionRequest(request) ? "reject" : "allow")
14476
+ permissionAllowlist: isRoomMcpPermissionRequest
14451
14477
  };
14452
14478
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
14453
14479
  await this.client.start();
@@ -15244,7 +15270,7 @@ var RoomRuntimeCoordinator = class {
15244
15270
  }
15245
15271
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
15246
15272
  if (!this.running.has(roomId))
15247
- this.startRoom(roomId);
15273
+ await this.startRoom(roomId);
15248
15274
  });
15249
15275
  await mapWithConcurrency([...desiredCorners.values()], ROOM_JOIN_CONCURRENCY, async (corner) => {
15250
15276
  if (!this.running.has(corner.cornerId))
@@ -15284,10 +15310,9 @@ var RoomRuntimeCoordinator = class {
15284
15310
  ...agentHomeRoot ? { agentHomeRoot } : {}
15285
15311
  };
15286
15312
  }
15287
- startRoom(roomId) {
15313
+ async startRoom(roomId) {
15288
15314
  const controller = new AbortController();
15289
- const record2 = this.roomRecord(roomId);
15290
- const cwd = record2?.repo.root ?? this.roomRoot(roomId);
15315
+ const cwd = await this.materializeRoomCheckout(roomId);
15291
15316
  const startedAt = this.now();
15292
15317
  const loop = new MonolithRoomTurnLoop({
15293
15318
  roomId,
@@ -15324,6 +15349,44 @@ var RoomRuntimeCoordinator = class {
15324
15349
  });
15325
15350
  console.log(`[thin-core] serving monolith Room ${roomId}`);
15326
15351
  }
15352
+ /**
15353
+ * A Room is a repository inspection surface, so its session cwd must be the
15354
+ * current server-bound repository rather than a legacy runtime path (or the
15355
+ * otherwise-empty per-Room state directory). The daemon consumes the
15356
+ * short-lived GitHub token itself; it is never included in the Room MCP or
15357
+ * harness environment.
15358
+ */
15359
+ async materializeRoomCheckout(roomId) {
15360
+ const repository = await this.options.daemonApi.execute("getRoomRepositoryState", { roomId });
15361
+ if (repository.resolution !== "repository" || !repository.remote)
15362
+ return this.roomRoot(roomId);
15363
+ const remote = roomCheckoutRemote(repository.remote);
15364
+ const targetBranch = repository.targetBranch || "main";
15365
+ const checkoutId = createHash("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
15366
+ const path = resolve10(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
15367
+ await mkdir5(dirname4(path), { recursive: true, mode: 448 });
15368
+ const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
15369
+ const env = token ? githubGitEnv(token.token) : process.env;
15370
+ if (!existsSync4(resolve10(path, ".git"))) {
15371
+ await execFileAsync3("git", ["clone", "--no-checkout", remote, path], {
15372
+ env,
15373
+ maxBuffer: 4 * 1024 * 1024
15374
+ });
15375
+ }
15376
+ await execFileAsync3("git", [
15377
+ "-C",
15378
+ path,
15379
+ "fetch",
15380
+ "--prune",
15381
+ "origin",
15382
+ `+refs/heads/${targetBranch}:refs/remotes/origin/${targetBranch}`
15383
+ ], { env, maxBuffer: 4 * 1024 * 1024 });
15384
+ await execFileAsync3("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
15385
+ env,
15386
+ maxBuffer: 4 * 1024 * 1024
15387
+ });
15388
+ return path;
15389
+ }
15327
15390
  async startCorner(corner) {
15328
15391
  if (this.running.has(corner.cornerId) || this.startingCorners.has(corner.cornerId))
15329
15392
  return;
@@ -15555,6 +15618,11 @@ function githubHttpsRemote(remote) {
15555
15618
  url.password = "";
15556
15619
  return url.toString().replace(/\/$/, "").replace(/\.git$/i, "") + ".git";
15557
15620
  }
15621
+ function roomCheckoutRemote(remote) {
15622
+ if (remote.startsWith("file://"))
15623
+ return remote;
15624
+ return githubHttpsRemote(remote);
15625
+ }
15558
15626
  function githubGitEnv(token) {
15559
15627
  const authorization = Buffer.from(`x-access-token:${token}`).toString("base64");
15560
15628
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "repository": {
6
6
  "type": "git",