usebeeline 0.1.19 → 0.1.20
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 +4 -3
- package/dist/usebeeline.mjs +1574 -1542
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -3025,7 +3025,7 @@ var require_websocket = __commonJS({
|
|
|
3025
3025
|
var http = __require("http");
|
|
3026
3026
|
var net2 = __require("net");
|
|
3027
3027
|
var tls = __require("tls");
|
|
3028
|
-
var { randomBytes: randomBytes10, createHash:
|
|
3028
|
+
var { randomBytes: randomBytes10, createHash: createHash15 } = __require("crypto");
|
|
3029
3029
|
var { Duplex, Readable } = __require("stream");
|
|
3030
3030
|
var { URL: URL2 } = __require("url");
|
|
3031
3031
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -3693,7 +3693,7 @@ var require_websocket = __commonJS({
|
|
|
3693
3693
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
3694
3694
|
return;
|
|
3695
3695
|
}
|
|
3696
|
-
const digest =
|
|
3696
|
+
const digest = createHash15("sha1").update(key + GUID).digest("base64");
|
|
3697
3697
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
3698
3698
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
3699
3699
|
return;
|
|
@@ -4062,7 +4062,7 @@ var require_websocket_server = __commonJS({
|
|
|
4062
4062
|
var EventEmitter2 = __require("events");
|
|
4063
4063
|
var http = __require("http");
|
|
4064
4064
|
var { Duplex } = __require("stream");
|
|
4065
|
-
var { createHash:
|
|
4065
|
+
var { createHash: createHash15 } = __require("crypto");
|
|
4066
4066
|
var extension2 = require_extension();
|
|
4067
4067
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
4068
4068
|
var subprotocol2 = require_subprotocol();
|
|
@@ -4369,7 +4369,7 @@ var require_websocket_server = __commonJS({
|
|
|
4369
4369
|
);
|
|
4370
4370
|
}
|
|
4371
4371
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
4372
|
-
const digest =
|
|
4372
|
+
const digest = createHash15("sha1").update(key + GUID).digest("base64");
|
|
4373
4373
|
const headers = [
|
|
4374
4374
|
"HTTP/1.1 101 Switching Protocols",
|
|
4375
4375
|
"Upgrade: websocket",
|
|
@@ -16517,7 +16517,9 @@ async function revokeGrants(mcp, ref) {
|
|
|
16517
16517
|
// apps/body/dist/squire-mcp-client.js
|
|
16518
16518
|
init_squire_host();
|
|
16519
16519
|
var INITIALIZE_TIMEOUT_MS = 3e4;
|
|
16520
|
-
var
|
|
16520
|
+
var SQUIRE_LONGEST_CALL_MS = 18e4;
|
|
16521
|
+
var CALL_TIMEOUT_MARGIN_MS = 3e4;
|
|
16522
|
+
var CALL_TIMEOUT_MS = SQUIRE_LONGEST_CALL_MS + CALL_TIMEOUT_MARGIN_MS;
|
|
16521
16523
|
var StdioSquireMcpClient = class {
|
|
16522
16524
|
options;
|
|
16523
16525
|
child;
|
|
@@ -16535,7 +16537,7 @@ var StdioSquireMcpClient = class {
|
|
|
16535
16537
|
/** One Squire MCP tool call; resolves with the tool's parsed result. */
|
|
16536
16538
|
async call(tool, args = {}) {
|
|
16537
16539
|
await this.ensureSession();
|
|
16538
|
-
const response = await this.request("tools/call", { name: tool, arguments: args });
|
|
16540
|
+
const response = await this.request("tools/call", { name: tool, arguments: args }, tool);
|
|
16539
16541
|
if (response.isError) {
|
|
16540
16542
|
const text3 = response.content?.find((entry) => entry.type === "text")?.text ?? "tool error";
|
|
16541
16543
|
throw new Error(`${tool} failed: ${text3}`);
|
|
@@ -16634,16 +16636,17 @@ var StdioSquireMcpClient = class {
|
|
|
16634
16636
|
this.child?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, ...params ? { params } : {} })}
|
|
16635
16637
|
`);
|
|
16636
16638
|
}
|
|
16637
|
-
request(method, params) {
|
|
16639
|
+
request(method, params, toolName2) {
|
|
16638
16640
|
const id = this.nextId++;
|
|
16639
16641
|
const child = this.child;
|
|
16640
16642
|
if (!child)
|
|
16641
16643
|
return Promise.reject(new Error("Squire MCP session is not running"));
|
|
16642
16644
|
return new Promise((resolve45, reject) => {
|
|
16645
|
+
const timeoutMs = method === "initialize" ? INITIALIZE_TIMEOUT_MS : CALL_TIMEOUT_MS;
|
|
16643
16646
|
const timer = setTimeout(() => {
|
|
16644
16647
|
this.pending.delete(id);
|
|
16645
|
-
reject(new Error(`${method} timed out`));
|
|
16646
|
-
},
|
|
16648
|
+
reject(new Error(`${toolName2 ?? method} timed out after ${timeoutMs}ms`));
|
|
16649
|
+
}, timeoutMs);
|
|
16647
16650
|
this.pending.set(id, { resolve: resolve45, reject, timer });
|
|
16648
16651
|
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
16649
16652
|
`);
|
|
@@ -28862,7 +28865,6 @@ var DaemonApiClient = class {
|
|
|
28862
28865
|
connectorAssignmentListener;
|
|
28863
28866
|
agentSignInListener;
|
|
28864
28867
|
cornerCompleteListener;
|
|
28865
|
-
cornerRestartListener;
|
|
28866
28868
|
helperReleaseListener;
|
|
28867
28869
|
forceUpdateListener;
|
|
28868
28870
|
helperIdentity = {
|
|
@@ -28990,10 +28992,6 @@ var DaemonApiClient = class {
|
|
|
28990
28992
|
setCornerCompleteListener(listener) {
|
|
28991
28993
|
this.cornerCompleteListener = listener;
|
|
28992
28994
|
}
|
|
28993
|
-
/** A no-code corner entered the code lane and must rebuild its local runtime. */
|
|
28994
|
-
setCornerRestartListener(listener) {
|
|
28995
|
-
this.cornerRestartListener = listener;
|
|
28996
|
-
}
|
|
28997
28995
|
setHelperReleaseListener(listener) {
|
|
28998
28996
|
this.helperReleaseListener = listener;
|
|
28999
28997
|
}
|
|
@@ -29090,10 +29088,6 @@ var DaemonApiClient = class {
|
|
|
29090
29088
|
this.cornerCompleteListener?.(event.roomId);
|
|
29091
29089
|
return;
|
|
29092
29090
|
}
|
|
29093
|
-
if (event.type === "corner-restart" && typeof event.roomId === "string") {
|
|
29094
|
-
this.cornerRestartListener?.(event.roomId);
|
|
29095
|
-
return;
|
|
29096
|
-
}
|
|
29097
29091
|
if (event.type === "connector-assignment") {
|
|
29098
29092
|
this.connectorAssignmentListener?.();
|
|
29099
29093
|
return;
|
|
@@ -29590,7 +29584,7 @@ init_self_update_manifest();
|
|
|
29590
29584
|
// apps/body/dist/managed-update.js
|
|
29591
29585
|
init_self_update();
|
|
29592
29586
|
import { spawn as spawn12 } from "node:child_process";
|
|
29593
|
-
import { createHash as
|
|
29587
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
29594
29588
|
import { mkdir as mkdir14, rm as rm8, stat as stat6, writeFile as writeFile11 } from "node:fs/promises";
|
|
29595
29589
|
import { dirname as dirname15, resolve as resolve22 } from "node:path";
|
|
29596
29590
|
init_launchd();
|
|
@@ -31648,7 +31642,7 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
31648
31642
|
}
|
|
31649
31643
|
|
|
31650
31644
|
// apps/body/dist/agent-home.js
|
|
31651
|
-
import { createHash as
|
|
31645
|
+
import { createHash as createHash7, randomUUID as randomUUID6 } from "node:crypto";
|
|
31652
31646
|
import { chmod as chmod5, copyFile, lstat as lstat3, mkdir as mkdir11, readFile as readFile16, readlink, readdir as readdir4, realpath, rename as rename7, rm as rm6, symlink as symlink2, unlink as unlink5, writeFile as writeFile9 } from "node:fs/promises";
|
|
31653
31647
|
import { homedir as homedir11 } from "node:os";
|
|
31654
31648
|
import { basename as basename3, dirname as dirname12, join as join12, relative, resolve as resolve16, sep } from "node:path";
|
|
@@ -31933,9 +31927,18 @@ var CREDENTIAL_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
31933
31927
|
".docker",
|
|
31934
31928
|
"keyrings",
|
|
31935
31929
|
"gh",
|
|
31936
|
-
"trusty-squire"
|
|
31937
|
-
"beeline"
|
|
31930
|
+
"trusty-squire"
|
|
31938
31931
|
]);
|
|
31932
|
+
function isBeelineKeyStore(segments) {
|
|
31933
|
+
return segments.some((segment, index) => {
|
|
31934
|
+
if (segment.toLowerCase() !== "beeline" || index === 0)
|
|
31935
|
+
return false;
|
|
31936
|
+
return segments[index - 1].toLowerCase() === ".config";
|
|
31937
|
+
});
|
|
31938
|
+
}
|
|
31939
|
+
function isAgentRuntimeFile(segments) {
|
|
31940
|
+
return segments.some((segment, index) => segment.toLowerCase() === "beeline" && segments[index + 1]?.toLowerCase() === "agents" && index + 3 === segments.length - 1);
|
|
31941
|
+
}
|
|
31939
31942
|
var CREDENTIAL_EXTENSIONS = /\.(pem|p12|pfx|key|jks|keystore|kdbx|asc|gpg|ppk|crt|pkcs12)$/i;
|
|
31940
31943
|
var CREDENTIAL_WORD = /(^|[-_.])(secret|secrets|credential|credentials|password|passwd|token|apikey|api_key|keystore|privatekey)([-_.]|$)/i;
|
|
31941
31944
|
var ENV_FILE = /(^|[.\-_])env(\.|$)|^\.env/i;
|
|
@@ -31967,7 +31970,12 @@ function namesCredential(word) {
|
|
|
31967
31970
|
return true;
|
|
31968
31971
|
if (base.endsWith(".env") || ENV_FILE.test(base))
|
|
31969
31972
|
return true;
|
|
31970
|
-
|
|
31973
|
+
if (segments.slice(0, -1).some((segment) => CREDENTIAL_DIRECTORIES.has(segment.toLowerCase()))) {
|
|
31974
|
+
return true;
|
|
31975
|
+
}
|
|
31976
|
+
if (isBeelineKeyStore(segments))
|
|
31977
|
+
return true;
|
|
31978
|
+
return isAgentRuntimeFile(segments);
|
|
31971
31979
|
}
|
|
31972
31980
|
function namesCredentialFile(argv) {
|
|
31973
31981
|
return argv.slice(1).some(namesCredential);
|
|
@@ -32253,1080 +32261,422 @@ var MESSAGE_SEARCH_FILLER_WORDS = [
|
|
|
32253
32261
|
];
|
|
32254
32262
|
var FILLER_WORDS = new Set(MESSAGE_SEARCH_FILLER_WORDS);
|
|
32255
32263
|
|
|
32256
|
-
//
|
|
32257
|
-
|
|
32258
|
-
var
|
|
32259
|
-
var
|
|
32260
|
-
|
|
32261
|
-
|
|
32262
|
-
|
|
32263
|
-
|
|
32264
|
-
|
|
32265
|
-
|
|
32266
|
-
|
|
32267
|
-
|
|
32268
|
-
|
|
32269
|
-
|
|
32270
|
-
|
|
32271
|
-
|
|
32272
|
-
|
|
32273
|
-
|
|
32274
|
-
|
|
32275
|
-
|
|
32276
|
-
|
|
32277
|
-
|
|
32278
|
-
|
|
32279
|
-
}
|
|
32280
|
-
selected.reverse();
|
|
32281
|
-
if (selected.length < lines.length) {
|
|
32282
|
-
selected.unshift(`[${lines.length - selected.length} older messages omitted from this prompt]`);
|
|
32264
|
+
// apps/body/dist/beeline-skill.js
|
|
32265
|
+
var USING_BEELINE_SKILL_NAME = "using-beeline";
|
|
32266
|
+
var BEELINE_TRIAGE_SKILL_NAME = "beeline-triage";
|
|
32267
|
+
var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
|
|
32268
|
+
var BEELINE_SPEC_SKILL_NAME = "beeline-spec";
|
|
32269
|
+
var BEELINE_ROOM_MECHANICS = [
|
|
32270
|
+
"Files and photos on the message you are answering are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline). A file on an earlier message shows only as a \u{1F4CE} marker with an id; call beeline-agent download_attachment with that message id and attachment id, then read the returned path. Never fetch a media URL; if the download fails, tell the requester you could not open the file.",
|
|
32271
|
+
"To create a file you can send, call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture.",
|
|
32272
|
+
"To run something later or repeatedly, call beeline-agent create_schedule (interval in minutes or a 5-field cron, optional maxRuns); list_schedules / update_schedule / delete_schedule manage them, including schedules other agents created for themselves.",
|
|
32273
|
+
`To react to a message in this Room, call beeline-agent react_to_message with its message id and one supported emoji (${MESSAGE_REACTION_EMOJIS.join(" ")}).`,
|
|
32274
|
+
`To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want; each one then wakes you for a turn. Available kinds: ${eventKindCatalogLines().join("; ")}. Subscriptions are per Room and cover every way the event lands: joining a Room you subscribed to wakes you, and so does a person arriving in the Workspace when that arrival projects into this Room - subscribe to joined in an onboarding Room and every newcomer wakes you, exactly like a greeter. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. A corner merging wakes nobody in its parent Room by default. If you still have work to do after a corner merges (a hand-off, a release step, a reply that has to describe landed code), subscribe to merged in the parent Room, from a turn there, before the merge. Each merge in that Room then wakes you and names the corner and pull request: act only on the corner you are waiting for, post nothing for any other merge, and take merged back out of your list once nothing is left to wait for. Never post just to acknowledge a merge: the merge card already announces it. A grant, connector offer, posted choice or poll, or Squire approval (payment, credential, sign-in, or vouch) you asked for wakes you back with the answer on its own - there is nothing to subscribe to for it, and asking is refused.`,
|
|
32275
|
+
"If you need reach outside the sandbox, call beeline-agent request_grant. If you already know discrete options, call beeline-agent ask_choice (one human, optional) or open_poll (every human in this Room, required deadline). A poll is refused below two electors and above fifty, and in a DM. A plurality is a fact, never permission to deploy, delete, merge, or spend. Open-ended asks stay tagged prose. Never put Always / Once / No on a preference.",
|
|
32276
|
+
"To state something that happened so the Room and other agents can act on it, call beeline-agent emit_event with your own agent:<slug> kind, one sentence, and optionally the agent members to wake. Chains of events are bounded and a refused emit posts nothing."
|
|
32277
|
+
].join(" ");
|
|
32278
|
+
function runningBeelineReleaseId(env = process.env, read2 = (path) => readFileSync4(path, "utf8")) {
|
|
32279
|
+
try {
|
|
32280
|
+
const lib = env.BEELINE_LIB_DIR;
|
|
32281
|
+
if (!lib)
|
|
32282
|
+
return "source";
|
|
32283
|
+
const manifest = JSON.parse(read2(resolve13(lib, "bundle.json")));
|
|
32284
|
+
return [manifest.version, manifest.commit].filter(Boolean).join("-") || "source";
|
|
32285
|
+
} catch {
|
|
32286
|
+
return "source";
|
|
32283
32287
|
}
|
|
32284
|
-
return selected;
|
|
32285
|
-
}
|
|
32286
|
-
function boundRoomTaskBody(body, modelContextTokens) {
|
|
32287
|
-
const context = modelContextTokens && modelContextTokens > 0 ? modelContextTokens : 32768;
|
|
32288
|
-
return boundMessageText(body, Math.min(8e3, Math.floor(context / 4)));
|
|
32289
32288
|
}
|
|
32290
|
-
function
|
|
32291
|
-
|
|
32292
|
-
|
|
32293
|
-
|
|
32289
|
+
function usingBeelineSkillMarkdown(releaseId) {
|
|
32290
|
+
return `---
|
|
32291
|
+
name: using-beeline
|
|
32292
|
+
description: How to answer inside a Beeline Room.
|
|
32293
|
+
---
|
|
32294
|
+
|
|
32295
|
+
<!-- beeline-release: ${releaseId} -->
|
|
32296
|
+
|
|
32297
|
+
# Using Beeline
|
|
32298
|
+
|
|
32299
|
+
These mechanics apply in Rooms and corners. ${BEELINE_ROOM_MECHANICS}
|
|
32300
|
+
|
|
32301
|
+
## Conflicting human instructions
|
|
32302
|
+
|
|
32303
|
+
Follow your own owner first, then this Workspace's owner and admins, then members. A higher-tier instruction overrides a lower-tier hold. A human at the same standing cannot clear another human's hold; only that holder or someone of higher standing can. Never tell a higher-tier human that a lower-tier hold binds them. Reason from the conversation. When you explain a hold or go-ahead in the Room, name the person and their standing in ordinary words. Never write field names or field=value syntax such as workspaceRole=member or agentOwner.
|
|
32304
|
+
|
|
32305
|
+
## Tools and the Workbench
|
|
32306
|
+
|
|
32307
|
+
A **tool** is something you can use once a human adds it; a **key** is the credential that tool holds for that human. You spend a key through the mounted connector and never see the credential itself.
|
|
32308
|
+
|
|
32309
|
+
The tools this build knows, what each is for, and which the owner of this machine already has are one call away: beeline-agent workbench_status. That read is the Workbench of the machine and owner you actually run on, not the person who asked. Connected apps are listed once per app with a stable ID. Their sign-in and credentials stay with the server. Trusty Squire is vaulted credentials plus a browser for the separate key and browser routes: operate_drive drives a whole flow, and flat steps (operate_click, operate_type, operate_select, operate_press, operate_scroll, operate_wait, operate_navigate, operate_read_inbox, operate_extract, and the rest of its flat operate_* actions) handle one action at a time. Tailscale installs its CLI on the selected helper and opens Tailscale's own browser sign-in; once connected, use the CLI for tailnet resources and tailscale file cp or tailscale file get for Taildrop. The wallet is created only from the Workbench page. When you cannot reach a tool, say: I can't reach X on this machine because Y; to fix it, Z. Give one explanation, then take or offer Z.
|
|
32310
|
+
|
|
32311
|
+
**Inspect your own host before answering.** When a question is about a system on the machine you run on, inspect its live state with the tools you have: processes, logs, local endpoints or service status. Say what you checked. Memory and Room history are not evidence of current state.
|
|
32312
|
+
|
|
32313
|
+
**Use available tools before asking the person.** Before asking a person for a code, a status or a file, call workbench_status and check whether a tool or connected app you already have can get it, then use it. This includes an emailed verification code in an already-connected inbox. Ask only when none can, and say why. The key-ownership and grant rules still apply.
|
|
32314
|
+
|
|
32315
|
+
**Offer the tool at the moment you need it.** When the work in front of you needs a tool the person does not have, do not send them to a settings page and do not stop at naming it. Call workbench_status first - a tool they already have is used, not offered. In a Room or DM, call offer_connector with the connectorType and one short reason: a card appears there, spoken by you, addressed to the person you are answering, with one action. Connector offers are unavailable in corners; use a Room or DM for those. Your turn pauses on the card - say in prose what you are waiting for and end the turn; you are woken when it is added, and then you carry on.
|
|
32316
|
+
|
|
32317
|
+
**Research before you offer, and say so.** Never offer a tool you cannot describe. If someone asks you to install or add something you do not already know - by name, by purpose, or by what it will hold - say plainly that you are looking it up first, find out what it is and what it does with credentials, and state what you learned in your reply BEFORE the card appears. A person reading "Add X?" must already have read, in your own words, what X is. Refusing to act blind is part of being trusted with keys.
|
|
32318
|
+
|
|
32319
|
+
An offer is setup, never authority: it does not replace a grant, write permission, target-branch confirmation, or the merge gate, and it never needs a raw credential in chat. You never pair a tool yourself and never ask anyone for a key value; once a tool is added, provisioning happens inside it and receipts reach the person through the tool's own status message.
|
|
32320
|
+
|
|
32321
|
+
Keys belong to the human who provisioned them. Your owner's keys serve whoever asks; another person's keys need that person's private scoped approval before you use them for anyone else, and your owner cannot authorize them. Use the connector without exposing or sharing raw keys. The Workbench page remains the place a person manages tools and keys by hand (Settings \u2192 Workbench); you point there to MANAGE what exists, not to add what you need.
|
|
32322
|
+
|
|
32323
|
+
## Where you run
|
|
32324
|
+
|
|
32325
|
+
Each agent runs on one owner's machine, with that owner's keys, hosts, and network. Agents in one Room can differ in what they reach. Memory is shared across all machines.
|
|
32326
|
+
|
|
32327
|
+
- Check your own capabilities with workbench_status and your tools. Never infer another agent's.
|
|
32328
|
+
- A failure saved from another machine is not proof here. Try it first.
|
|
32329
|
+
- If you cannot do X here, say "I can't do X on this machine because Y", then hand off to an agent that memory shows has done X, or ask the requester who has access.
|
|
32330
|
+
|
|
32331
|
+
## Memory
|
|
32332
|
+
|
|
32333
|
+
You keep memory yourself. No background reviewer exists: what you do not keep current, nothing does.
|
|
32334
|
+
|
|
32335
|
+
| Store | Holds | Write with |
|
|
32336
|
+
| --- | --- | --- |
|
|
32337
|
+
| \`workspace-core\` procedure | Capabilities per machine; the Squire credential checklist | save_skill, whole text |
|
|
32338
|
+
| Profile facts | Facts about the person who asked | save_memory |
|
|
32339
|
+
| Workspace facts | Facts about anyone or anything else | save_memory |
|
|
32340
|
+
| Procedures | Steps for a task that repeats | save_skill |
|
|
32341
|
+
| Workflows | Roles and steps for a run across agents | save_workflow; run with start_workflow |
|
|
32342
|
+
| History | Every message in the Workspace | Nothing; read with search_history |
|
|
32343
|
+
|
|
32344
|
+
- **Look up.** At task start, call load_workspace_skill for \`workspace-core\`. Before you say "can't" or "not saved", run search_memory, list_credentials and workbench_status.
|
|
32345
|
+
- **Use.** Call report_memory_used for what your answer used. When a saved item fails, fix it in the same turn.
|
|
32346
|
+
- **Save.** Put each fact in one store only. Never save "X is impossible". Save the specific failure and the machine: "On Niglet's host, the highroll Fly token returns 403."
|
|
32347
|
+
- **Prune.** Rewrite \`workspace-core\` whole and keep it at most 2,200 characters. An agent-saved Workspace fact that nobody uses expires in the weekly pass. Replace a procedure or workflow by saving its slug again; remove it with delete_skill.
|
|
32348
|
+
|
|
32349
|
+
## Connect an app
|
|
32350
|
+
|
|
32351
|
+
When a request needs an app or service, call beeline-agent \`workbench_status\` first. For a connected app via composio, use its ID with \`list_app_tools\`, then call \`execute_app_tool\` with the selected tool slug and arguments. For an app via squire-api, call its API with \`use_credential\` (service = the app key); via squire-browser, use the \`operate_*\` tools; via registry-mcp, use its mounted MCP tools. The server executes against that person\u2019s account and decides permission on every call. If tool discovery reports an error, follow its cause; \`needs_connection\` means the account must be reconnected, and \`needs_permission\` means wait for the connected person's decision. Do not switch paths after denial or provider unavailability. No app token, provider key, or sign-in URL belongs in chat or a model prompt.
|
|
32352
|
+
|
|
32353
|
+
For an app that is not connected, call \`connect_app\` with its name, one short reason, and a request-specific \`continuation\`: one sentence stating what you will do right after sign-in. Do not include a link, credential, account identifier, or private data in that sentence. The server chooses the route and returns \`next\`. In a corner, call it from that corner so the sign-in card and connection wake return there. A managed OAuth app presents a sign-in card to its owner; end the turn and wait for the connection wake. If the provider reports managed OAuth unsupported, follow the server's Squire route and its instructions. A provider outage is unavailable, not permission to change routes. After the person connects, read Workbench again and use the app ID. Every use is authorized as \`app:<key>\` and recorded once.
|
|
32354
|
+
|
|
32355
|
+
## Showing a mock
|
|
32356
|
+
|
|
32357
|
+
When a design decision needs eyes, show it instead of describing it. Build ONE self-contained HTML page and post it with beeline-agent post_artifact (mime "text/html", pass the document as html). Everything is inline: a single <style> element for all CSS and data: URLs for any image - no script, no external dependencies, no network references. The validator refuses every <script>, <link>, <iframe>, <object>, <embed>, <form>, inline event handler, and http(s) URL, so a page that reaches for the network never posts. Use the Obsidian Refined tokens: grayscale surfaces, one brass accent #d7af5f, 3px radii, IBM Plex Sans/Mono type. Lay the user stories out as frames - one bordered, labelled block per story, so each story can be judged on its own. After posting, ask for feedback here in the corner: the artifact is the thing people react to, not your prose.
|
|
32358
|
+
|
|
32359
|
+
## Showing a photograph
|
|
32360
|
+
|
|
32361
|
+
When the mock needs a real product photo, do not draw an SVG stand-in. Call beeline-agent fetch_image with the photo's http(s) URL. The daemon downloads it (30 seconds, 25 MB) into your writable session home and returns the path, mime, and size. Read those bytes, base64-encode them, and put them in the HTML as a data: URL (\`<img src="data:image/jpeg;base64,\u2026">\`). Then post_artifact as usual. The validator still refuses every http(s) image reference \u2014 the photograph has to be inline. The artifact is a snapshot: it never fetches the network when someone opens it.
|
|
32362
|
+
`;
|
|
32294
32363
|
}
|
|
32295
|
-
function
|
|
32296
|
-
return
|
|
32364
|
+
function beelineReviewSkillMarkdown(releaseId) {
|
|
32365
|
+
return `---
|
|
32366
|
+
name: beeline-review
|
|
32367
|
+
description: Review a corner pull request against the brief's outline, the approval quote, and the Beeline merge gate.
|
|
32368
|
+
---
|
|
32369
|
+
|
|
32370
|
+
<!-- beeline-release: ${releaseId} -->
|
|
32371
|
+
|
|
32372
|
+
# Beeline pull-request review
|
|
32373
|
+
|
|
32374
|
+
Follow these steps in order. Do not skip or reorder them.
|
|
32375
|
+
|
|
32376
|
+
## 1. Isolate the revision
|
|
32377
|
+
|
|
32378
|
+
- Run \`gh pr view N --json headRefOid,files\` and record \`headRefOid\`.
|
|
32379
|
+
- Run \`gh pr diff N\`.
|
|
32380
|
+
- Review the objective, files, and complete PR diff before making another working copy.
|
|
32381
|
+
- Check out that exact head only when an empirical command must read or run the tree. Never use the author's worktree.
|
|
32382
|
+
- When a checkout is needed, create a temporary detached worktree, install an EXIT trap that removes it with \`git worktree remove --force\` and deletes its temporary directory, and run every command there. Cleanup is mandatory on PASS, FAIL, and command error.
|
|
32383
|
+
- For a deterministic one-shot bootstrap use \`npm run review:exact-head -- <sha> [--typecheck <pkg> \u2026] [--] <pkg>:<glob> \u2026\`: it checks that exact head out, runs \`npm ci\`, builds the workspace package exports, runs the given typechecks and targeted tests with non-interactive PASS/FAIL summaries, and removes the worktree itself (pass \`--keep\` to keep it and print the removal command).
|
|
32384
|
+
- Review and test only the recorded revision. If the head moves, clean up the scratch worktree and start over.
|
|
32385
|
+
|
|
32386
|
+
## 2. P0 - OUTLINE FULFILLED, DEMONSTRATED
|
|
32387
|
+
|
|
32388
|
+
Read the server-assigned brief and its current revision, including every required file. Quote the approval with its message ID and approver before considering the short objective. The spec's outline (user stories, acceptance criteria, non-goals, risks) is the scope, and the approval quote is the human's own words: it wins any conflict with the spec. The short objective is navigation-only text and the PR description cannot add, remove, or narrow scope. A revision with no recorded approval is reviewed against its spec alone. If a required file is unavailable, or the revision changes during review, refuse approval and describe what is missing. A requirement may become out of scope only through a new human-authorized brief revision; reviewer discretion, implementation difficulty, and silence never remove it. A passing narrow test does not prove a broader requirement. Review a deliberate human choice as written, even if it is unusual.
|
|
32389
|
+
|
|
32390
|
+
Before general correctness, produce an outline ledger in spec order: list every \`## User stories\` and \`## Acceptance criteria\` line exactly once as met, unmet, unverified, or out of scope, followed by concrete evidence (for a criterion, the named test run against the PR head) - no stories to list is a FAIL, sent back to the author to write the outline; confirm nothing under \`## Non-goals\` was done; confirm every \`## Risks\` line was addressed or explicitly accepted with a stated reason. For a frontend change, open the rendered screens and compare them against the attached mock, noting every visible deviation. Any missing, unmet, unverified story or acceptance criterion, done non-goal, unaddressed risk, or unreviewed mock mismatch blocks PASS. Out of scope is valid only when the current revision itself records the human-authorized removal.
|
|
32391
|
+
|
|
32392
|
+
Before judging the implementation, independently repeat the two judgment legs from request triage:
|
|
32393
|
+
|
|
32394
|
+
- **Work warranted:** For a bug, reproduce the reported behavior on the target branch. For another request, establish the unmet user need from the request and current product. Search current code, history, issues, and open or recently merged pull requests for work that already resolves or supersedes it. Treat title similarity only as a candidate, not proof of duplication. FAIL confirmed duplicate or obsolete work.
|
|
32395
|
+
- **Desirable:** Check repository-owned goals, invariants, architecture, and established product behavior. Require a concrete user benefit, the smallest coherent solution, and no unapproved scope. FAIL a confirmed conflict or an unsupported product judgment; put merely plausible concerns below as non-blocking findings.
|
|
32396
|
+
|
|
32397
|
+
- Quote the approval text, with its message ID, exactly as stored. Do not quote the short objective as product truth.
|
|
32398
|
+
- Derive the end-user story from the spec and the approval quote in one sentence: \`a user who does X sees Y\`.
|
|
32399
|
+
- Make Y happen against the built PR head: run the app or affected service and perform X.
|
|
32400
|
+
- If no interactive surface is reachable, run the narrowest test or script that exercises the exact user path and prints the observable Y.
|
|
32401
|
+
- Record the command and the observed Y.
|
|
32402
|
+
- A unit test of an inner function, a log line, \`the code looks right\`, or any other proxy does not count.
|
|
32403
|
+
- If the user-visible Y cannot be produced, FAIL now. Nothing below can rescue the review.
|
|
32404
|
+
- For a bug, if a \`Reproduction <id>\` was recorded, quote it, re-run that exact user path on the PR head, and record that the wrong result is gone. FAIL if that proof does not name the identifier, even when other tests pass. If none was obtained, require the proof to say so plainly and show the regression instead; do not fail the review for a missing identifier.
|
|
32405
|
+
- State whether the diff meets every user story and the approval quote without unapproved scope.
|
|
32406
|
+
|
|
32407
|
+
## 3. Empirical pass second
|
|
32408
|
+
|
|
32409
|
+
Check the brief revision and code head for base synchronization, tests, documentation, lint and types, publication, and CI. A required check that was skipped, failed, or not verified blocks PASS. An empty rollup alone is not proof of passing checks. Use pr_checks_status for the exact current head: checks="passed" with checkCount=0 is the server-confirmed no-checks path and satisfies the CI prerequisite for review. Do not refuse review because no literal green check exists, and do not require mergeAllowed=true before reviewing: the missing reviewer PASS itself keeps that gate closed. Pending, failed, or unknown checks still block approval. Reuse valid author evidence; run targeted independent checks where needed. A screenshot or rendered app is needed for visual claims, and server-boundary behavior for authorization claims.
|
|
32410
|
+
|
|
32411
|
+
- Run the repository typecheck and tests touched by the diff.
|
|
32412
|
+
- If the spec or the approval quote names a user path, exercise that path.
|
|
32413
|
+
- Record every command and exit code.
|
|
32414
|
+
- A review with no executed command is invalid and must FAIL.
|
|
32415
|
+
|
|
32416
|
+
### Design rules
|
|
32417
|
+
|
|
32418
|
+
When the diff touches UI and the repository has a DESIGN.md, check every changed style against its rules: colours from theme tokens only, the type roles, the spacing scale, the allowed radii, elevation, the shared components, and touch targets.
|
|
32419
|
+
|
|
32420
|
+
- Run the repository's design lints. In Beeline, from \`apps/mobile\`: \`npx vitest run sources/buzz/calm-lint sources/buzz/design-lint\`.
|
|
32421
|
+
- FAIL when a design baseline count grows, or when an allowlist entry is added without naming the design decision behind it.
|
|
32422
|
+
- Require a screenshot or rendered proof of every visual change in each theme the app ships (in Beeline, Obsidian and Bone).
|
|
32423
|
+
- A design-rule violation is an engineering finding and blocks PASS.
|
|
32424
|
+
|
|
32425
|
+
## 4. Adversarial pass
|
|
32426
|
+
|
|
32427
|
+
- For every changed function, name one concrete input or sequence that breaks it.
|
|
32428
|
+
- If none is found, write \`none found\` for that function.
|
|
32429
|
+
|
|
32430
|
+
## 5. Verify before reporting
|
|
32431
|
+
|
|
32432
|
+
- Confirm every finding by reading the exact line or by running a command.
|
|
32433
|
+
- Put unconfirmed concerns under plausible findings. They never block.
|
|
32434
|
+
|
|
32435
|
+
## 6. Bloat guard
|
|
32436
|
+
|
|
32437
|
+
- Compare net lines with the spec, its user stories and non-goals, and the approval quote.
|
|
32438
|
+
- FAIL backwards-compatibility shims, dual paths, feature flags, or abstractions with one caller.
|
|
32439
|
+
- FAIL machinery the spec and the approval quote did not ask for.
|
|
32440
|
+
|
|
32441
|
+
## 7. Security and data
|
|
32442
|
+
|
|
32443
|
+
- Check credentials, authorization boundaries, and destructive migrations.
|
|
32444
|
+
|
|
32445
|
+
## 8. Gate and verdict
|
|
32446
|
+
|
|
32447
|
+
- Review the exact green head named in your reviewer instruction; a server-confirmed zero-check passed head counts as green. If the head moved, do not approve it.
|
|
32448
|
+
- Re-read the assigned brief revision before the verdict. A repair changes the head and invalidates affected evidence; a requirement correction invalidates the relevant verdict even if code did not change. Keep product-completeness findings (a missing or contradicted user story, done non-goal, unaddressed risk, mock mismatch, or approval quote) separate from engineering findings (correctness, security, maintainability, tests). Give every confirmed finding a stable ID that survives rereview, the affected outline line or \`engineering\`, location, severity, evidence, and repair disposition. Reuse the same ID until that finding is resolved. Mechanical repairs return to the author; ask a human only for a genuinely unresolved product choice. Do not run a nested validation pipeline or push the author's branch.
|
|
32449
|
+
- Always use this exact verdict shape:
|
|
32450
|
+
|
|
32451
|
+
\`approval quoted with its message ID:\`
|
|
32452
|
+
\`user story:\`
|
|
32453
|
+
\`work warranted evidence:\`
|
|
32454
|
+
\`desirability evidence:\`
|
|
32455
|
+
\`reproduction id (or none obtained):\`
|
|
32456
|
+
\`proof of that reproduction (or none obtained + regression):\`
|
|
32457
|
+
\`how Y was demonstrated (or FAIL):\`
|
|
32458
|
+
\`commands run + results:\`
|
|
32459
|
+
\`brief revision:\`
|
|
32460
|
+
\`outline ledger (every user story, acceptance criterion, non-goal and risk + status + evidence; mock-vs-screen notes for frontend work):\`
|
|
32461
|
+
\`product-completeness findings (block):\`
|
|
32462
|
+
\`engineering findings (block):\`
|
|
32463
|
+
\`plausible findings (do not block):\`
|
|
32464
|
+
\`net lines:\`
|
|
32465
|
+
\`decision: PASS|FAIL\`
|
|
32466
|
+
|
|
32467
|
+
Then take exactly one action:
|
|
32468
|
+
|
|
32469
|
+
- FAIL: reply \`@author\` with the confirmed findings and the reviewed head SHA to fix.
|
|
32470
|
+
- PASS: call \`approve_merge\` with the reviewed head SHA, then reply \`approved <reviewed sha>\` without tagging the author. Do not tell the author to merge.
|
|
32471
|
+
- A human's question or proposal in the corner discussion with no answer is a blocking finding: FAIL and name it.
|
|
32472
|
+
- Approving is your last step as reviewer. Your PASS is the yes that wakes the author, who merges that exact head with \`merge_corner\` once checks are green and no hold stands. You never merge it yourself.
|
|
32473
|
+
`;
|
|
32297
32474
|
}
|
|
32298
|
-
function
|
|
32299
|
-
|
|
32300
|
-
|
|
32301
|
-
|
|
32302
|
-
|
|
32303
|
-
|
|
32475
|
+
function beelineSpecSkillMarkdown(releaseId) {
|
|
32476
|
+
return `---
|
|
32477
|
+
name: beeline-spec
|
|
32478
|
+
description: Prepare or revise a durable corner assignment from Room decisions before repository work. Do not use for ordinary conversation or unrelated artifact delivery.
|
|
32479
|
+
---
|
|
32480
|
+
|
|
32481
|
+
<!-- beeline-release: ${releaseId} -->
|
|
32482
|
+
|
|
32483
|
+
# Durable corner brief
|
|
32484
|
+
|
|
32485
|
+
Create an assignment a fresh session can execute without the parent transcript. A brief has three parts:
|
|
32486
|
+
|
|
32487
|
+
- \`spec\`: one Markdown doc you write, with these headings:
|
|
32488
|
+
- \`## Intent\`: the human's own words, quoted exactly, each with its Room message ID. Never substitute a summary.
|
|
32489
|
+
- \`## User stories\`: what this enables, one per line as "a person who does X sees Y". Only stories traceable to the quoted Intent; never import a plan, a teammate's suggestion, or your own idea as a story. Keep unaffected lines as they are across revisions.
|
|
32490
|
+
- \`## Acceptance criteria\`: the requester's own rules from \`## Intent\`, quoted verbatim and numbered, each naming the test (existing or to be written) that fails if the rule is broken anywhere. Criteria are the invariant; stories are examples of it. Write "None beyond the stories." when the request states no rule.
|
|
32491
|
+
- \`## Non-goals\`: explicit exclusions.
|
|
32492
|
+
- \`## Risks\`: what could go wrong, and for whom.
|
|
32493
|
+
- \`## References\`: each reference or mock with what it is and whose call it is; the mock posted here (DESIGN.md, else existing app styles) is the reviewer's acceptance reference, not any other optional or agent-generated mock.
|
|
32494
|
+
Add implementation guidance under further headings when it helps.
|
|
32495
|
+
- files (\`attachments\`): the Room files the worker needs, each with its purpose and whether it is required.
|
|
32496
|
+
- \`approval\`: one human Room message ID. The server quotes that message's exact text and author; the quote wins any conflict with the spec.
|
|
32497
|
+
|
|
32498
|
+
Do not infer approval from silence, assent to different prose, or the mere existence of a plan. Keep agent recommendations labelled as recommendations. Do not promote a plausible UX detail, algorithm, failure message, or extra test into a user story merely because it sounds helpful.
|
|
32499
|
+
|
|
32500
|
+
## Compact path for a settled small fix
|
|
32501
|
+
|
|
32502
|
+
Use this when the requested behavior, touched surface, exclusions, and proof are already obvious. Inspect the narrow current behavior, write a short spec (quoted intent, observable user stories, non-goals, risks, references), and use the initiating command as the approval. Always compose the full outline and post it to the Room before calling open_corner, even when the initiating command already settles scope and stands as the approval: posting is not optional merely because approval is inferred, since the human must be able to see what you are about to build. \u201CChange the message action label from Remove to Delete; leave confirmation and accessibility wording unchanged\u201D needs the visible-label user story and both exclusions, not another confirmation question.
|
|
32503
|
+
|
|
32504
|
+
## Complex-work planning loop
|
|
32505
|
+
|
|
32506
|
+
Use this default-on loop when work crosses components, changes architecture or authorization, needs a visual choice, has multiple user stories, or still contains a material unknown.
|
|
32507
|
+
|
|
32508
|
+
### 1. Scope and current state
|
|
32509
|
+
|
|
32510
|
+
- Collect every relevant human message, including corrections; later corrections supersede only what they explicitly change. Quote their exact text and IDs under \`## Intent\`.
|
|
32511
|
+
- Inspect current code, data contracts, tests, recent related work, repository invariants, and existing behavior. State what exists, what is missing, and what is deliberately out of scope.
|
|
32512
|
+
- Separate facts, human decisions, agent recommendations, and unresolved choices.
|
|
32513
|
+
|
|
32514
|
+
### 2. User stories and product boundary
|
|
32515
|
+
|
|
32516
|
+
- Write the actor/action/observable-result stories.
|
|
32517
|
+
- Map each story to its own observable line and explicit non-goals.
|
|
32518
|
+
- If one unresolved choice would materially change behavior, scope, irreversible effects, or the intended result, present only that choice or the affected brief slice to the human. Use the answering message as the \`approval\`. Never treat no answer as approval.
|
|
32519
|
+
|
|
32520
|
+
### 3. Architecture and data flow
|
|
32521
|
+
|
|
32522
|
+
- Trace inputs, authorities, state transitions, persistence, outputs, and trust boundaries.
|
|
32523
|
+
- Name the existing components to reuse and the smallest coherent changes.
|
|
32524
|
+
- Describe migration and compatibility behavior only where the human intent or existing data requires it.
|
|
32525
|
+
|
|
32526
|
+
### 4. Failure modes and test map
|
|
32527
|
+
|
|
32528
|
+
- Enumerate missing/stale data, retries, concurrency, partial failure, authorization failure, rollback, and boundary-specific hazards that apply.
|
|
32529
|
+
- Map every user story to its proof boundary: unit, integration, device/browser, server authorization, migration, or live harness. No story may be left without planned evidence.
|
|
32530
|
+
|
|
32531
|
+
### 5. Mocks and references
|
|
32532
|
+
|
|
32533
|
+
- When a visual or interaction decision needs eyes, create and post one self-contained mock using the using-beeline mock procedure.
|
|
32534
|
+
- A mock/file posted with \`post_artifact\` in this planning turn is added automatically to the brief attachment manifest; do not manually copy its object ID. Still list it under \`## References\` with whose call it is. Existing human-posted files must be named explicitly in attachments.
|
|
32535
|
+
|
|
32536
|
+
### 6. Implementation tasks
|
|
32537
|
+
|
|
32538
|
+
- Synthesize ordered, independently verifiable tasks. For each task name the affected boundary, user stories, expected files/components, failure handling, and proof.
|
|
32539
|
+
- Keep the spec actionable, not a transcript summary.
|
|
32540
|
+
|
|
32541
|
+
### 7. Bounded adversarial second read (default on)
|
|
32542
|
+
|
|
32543
|
+
- Spend one pass, bounded to the drafted scope, trying to disprove completeness: find a missing story, authority mismatch, unhandled failure, user story without proof, mock ambiguity, accidental scope expansion, or task-order hazard.
|
|
32544
|
+
- Return findings to the planning agent and repair the draft. Escalate only a genuinely material product choice to the human; engineering choices and mechanical gaps are resolved in the plan.
|
|
32545
|
+
- End after one adversarial pass unless its repair exposes one new material product choice. Do not recursively review the review.
|
|
32546
|
+
|
|
32547
|
+
## Dispatch and revision
|
|
32548
|
+
|
|
32549
|
+
Existing authorization permits dispatch when the initiating command already settles the exact material scope; that command is the approval, but the outline is still posted to the Room first so the human can see it before open_corner runs. Drafting a brief does not create approval. When the scope was not already explicit, post the spec and ask the corner's opener to approve it, then use their reply as the \`approval\`. Honor an explicit request to review first.
|
|
32550
|
+
|
|
32551
|
+
Pass the brief as \`open_corner.brief\`. Read the current revision before revising and use \`revise_corner_brief\` with the complete replacement spec, the approving message, and a concise change description. Preserve unaffected intent quotes, user stories, and references. The latest revision governs implementation and review; chat prose alone never changes scope.
|
|
32552
|
+
`;
|
|
32304
32553
|
}
|
|
32305
|
-
function
|
|
32306
|
-
|
|
32307
|
-
|
|
32308
|
-
|
|
32309
|
-
|
|
32554
|
+
function beelineTriageSkillMarkdown(releaseId) {
|
|
32555
|
+
return `---
|
|
32556
|
+
name: beeline-triage
|
|
32557
|
+
description: Clarify and assess a user request before opening a Beeline corner, then bind a bugfix to its recorded reproduction. Use before open_corner and while implementing a bug in a corner.
|
|
32558
|
+
---
|
|
32559
|
+
|
|
32560
|
+
<!-- beeline-release: ${releaseId} -->
|
|
32561
|
+
|
|
32562
|
+
# Beeline request triage
|
|
32563
|
+
|
|
32564
|
+
Run these checks before opening a corner.
|
|
32565
|
+
|
|
32566
|
+
## 1. Is it clear?
|
|
32567
|
+
|
|
32568
|
+
- Rewrite the request as a concrete outcome and the user stories it enables.
|
|
32569
|
+
- State material exclusions (non-goals) and the risks needed to prevent unrequested work or surprise.
|
|
32570
|
+
- Keep the corner objective complete and within 24 words.
|
|
32571
|
+
- First ask whether the request has any material ambiguity - one that would change scope, behavior, or risk - and resolve it with the human before doing anything else; never resolve it by adopting a teammate's plan, a prior document, or your own assumption.
|
|
32572
|
+
- Once it's resolved, say plainly if the request itself is suboptimal and give your own recommendation - you are expected to disagree, not just permitted to.
|
|
32573
|
+
- On a judgment call you're still not sure of, tag a Room colleague for an adversarial second opinion and ask them to reply tagging you back; skip this only when you are the only agent in the Room.
|
|
32574
|
+
|
|
32575
|
+
## 2. Is work warranted?
|
|
32576
|
+
|
|
32577
|
+
- For a bug, try to reproduce the exact user-visible behavior and record the evidence.
|
|
32578
|
+
- For another request, identify the unmet user need and the current behavior that does not meet it.
|
|
32579
|
+
- Search current code, history, issues, and open or recently merged pull requests for released or unreleased work that resolves or supersedes the request.
|
|
32580
|
+
- Treat similar titles as candidates. Confirm behavior and scope before calling work duplicate.
|
|
32581
|
+
- If the need cannot be established, reproduction fails, or other work may obviate it, warn; do not block.
|
|
32582
|
+
|
|
32583
|
+
## 3. Is it desirable?
|
|
32584
|
+
|
|
32585
|
+
- Compare the request with repository-owned goals, invariants, architecture, and established product behavior.
|
|
32586
|
+
- Look for concrete user benefit, the smallest coherent solution, and ongoing maintenance cost.
|
|
32587
|
+
- Do not invent product strategy. If the evidence is absent or conflicting, warn; do not block.
|
|
32588
|
+
|
|
32589
|
+
## Output
|
|
32590
|
+
|
|
32591
|
+
When dispatching work, pass the complete brief to open_corner under existing authorization, after posting the outline as beeline-spec requires. Add applicable warnings to the brief:
|
|
32592
|
+
|
|
32593
|
+
\`Triage warning \u2014 warranted: <evidence-backed reason>\`
|
|
32594
|
+
\`Triage warning \u2014 desirable: <evidence-backed reason>\`
|
|
32595
|
+
|
|
32596
|
+
When a bug reproduction succeeds, also emit:
|
|
32597
|
+
|
|
32598
|
+
\`Reproduction <id>: <user path> \u2192 <observable wrong result>\`
|
|
32599
|
+
|
|
32600
|
+
Do not emit a warning merely because evidence is incomplete when the repository offers no practical way to obtain it. Never describe a warning as approval or rejection. Warnings inform the user and implementer; they do not block work.
|
|
32601
|
+
|
|
32602
|
+
## Bugfix execution
|
|
32603
|
+
|
|
32604
|
+
Once a corner is open for a reported bug, follow these steps in order. They are instruction, not a server gate. They do not condition the fix on reproduction. Warranted-work and desirability warnings still do not block.
|
|
32605
|
+
|
|
32606
|
+
### 1. Attempt to reproduce
|
|
32607
|
+
|
|
32608
|
+
- Attempt to reproduce the bug as triage isolated it, using every tool the host offers: emulator, Playwright, browser, test runner.
|
|
32609
|
+
- Record what was tried and what was observed.
|
|
32610
|
+
- If a reproduction is obtained, record the exact user path and the observable wrong result under \`Reproduction <id>\`. Reuse the identifier triage emitted when it recorded one.
|
|
32611
|
+
- If reproduction fails, warn and continue exactly as triage already does. Never stop. Never condition the fix on reproduction.
|
|
32612
|
+
|
|
32613
|
+
### 2. Narrow fix
|
|
32614
|
+
|
|
32615
|
+
- Narrow the fix to the reported behavior.
|
|
32616
|
+
- When a reproduction exists, change only what removes that recorded reproduction.
|
|
32617
|
+
- Nearby improvements stay out of scope.
|
|
32618
|
+
|
|
32619
|
+
### 3. Proof matching triage
|
|
32620
|
+
|
|
32621
|
+
- When a reproduction exists, re-run it, cite the same identifier, and show it now passing, plus the regression that would fail if the bug returned.
|
|
32622
|
+
- When none was obtained, state that plainly and show the regression instead.
|
|
32623
|
+
- Name the identifier in the pull request when one exists so the reviewer can check that proof, not merely that some test exists.
|
|
32624
|
+
`;
|
|
32310
32625
|
}
|
|
32311
32626
|
|
|
32312
|
-
// apps/body/dist/
|
|
32313
|
-
|
|
32314
|
-
|
|
32315
|
-
|
|
32316
|
-
|
|
32317
|
-
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
].join("\n");
|
|
32627
|
+
// apps/body/dist/external-mcp-capabilities.js
|
|
32628
|
+
var SQUIRE_MCP_VERSION = "latest";
|
|
32629
|
+
var SQUIRE_MCP_PACKAGE = `@trusty-squire/mcp@${SQUIRE_MCP_VERSION}`;
|
|
32630
|
+
function isTrustySquireMcpLaunch(command, args = []) {
|
|
32631
|
+
return [command, ...args].some((value) => {
|
|
32632
|
+
const normalized = value.trim().replace(/\\/g, "/").toLowerCase();
|
|
32633
|
+
return /(^|[\s/="'\[,])@trusty-squire\/mcp(?:@[^\s/"'\],}]+)?(?=$|[\s/"'\],}])/i.test(normalized) || /(^|\/)trusty-squire(?:-mcp)?(?:\.[a-z0-9]+)?$/i.test(normalized);
|
|
32634
|
+
});
|
|
32321
32635
|
}
|
|
32322
|
-
var
|
|
32323
|
-
"
|
|
32324
|
-
"
|
|
32325
|
-
"
|
|
32326
|
-
"review-corner",
|
|
32327
|
-
"no-code-corner"
|
|
32328
|
-
];
|
|
32329
|
-
var CORNERS = [
|
|
32330
|
-
"code-corner",
|
|
32331
|
-
"review-corner",
|
|
32332
|
-
"no-code-corner"
|
|
32636
|
+
var SQUIRE_GOVERNED_TOOLS = [
|
|
32637
|
+
"use_credential",
|
|
32638
|
+
"grant_app_access",
|
|
32639
|
+
"revoke_app_access"
|
|
32333
32640
|
];
|
|
32334
|
-
var
|
|
32335
|
-
|
|
32336
|
-
|
|
32337
|
-
|
|
32338
|
-
|
|
32339
|
-
|
|
32340
|
-
|
|
32341
|
-
|
|
32342
|
-
|
|
32343
|
-
|
|
32344
|
-
|
|
32345
|
-
|
|
32346
|
-
|
|
32347
|
-
|
|
32348
|
-
...brief.spec.matchAll(/^## (?:Non-goals|Not doing)\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/gim)
|
|
32349
|
-
].map((match) => match[1] ?? "");
|
|
32350
|
-
const prohibition = /^(?:repository edits?|repository changes?|pull requests?|(?:edit|modify|change) (?:the )?(?:repository|repo|code)|push(?:es)?|commit(?:s)?)[.!;]*$/i;
|
|
32351
|
-
for (const nonGoals of nonGoalsSections) {
|
|
32352
|
-
for (const line of nonGoals.split("\n")) {
|
|
32353
|
-
const bullet = /^\s*[-*]\s+/.test(line);
|
|
32354
|
-
const clause = line.trim().replace(/^[-*]\s+/, "");
|
|
32355
|
-
const negative = /^(?:no|never|do not|forbid(?:s|den)?)\s+/i;
|
|
32356
|
-
if (!bullet && !negative.test(clause))
|
|
32357
|
-
continue;
|
|
32358
|
-
const items = clause.replace(negative, "").split(/,\s*|\s+(?:and|or)\s+/i);
|
|
32359
|
-
if (items.some((item) => prohibition.test(item.trim())))
|
|
32360
|
-
return false;
|
|
32361
|
-
}
|
|
32641
|
+
var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
|
|
32642
|
+
|
|
32643
|
+
// apps/body/dist/host-mcp-route.js
|
|
32644
|
+
import { join as join11, resolve as resolve14 } from "node:path";
|
|
32645
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
32646
|
+
init_squire_host();
|
|
32647
|
+
function grantedMcpServerNames(grants) {
|
|
32648
|
+
const names = /* @__PURE__ */ new Set();
|
|
32649
|
+
for (const grant of grants ?? []) {
|
|
32650
|
+
if (grant.kind !== "mcp")
|
|
32651
|
+
continue;
|
|
32652
|
+
const name = grant.target.trim();
|
|
32653
|
+
if (name)
|
|
32654
|
+
names.add(name);
|
|
32362
32655
|
}
|
|
32363
|
-
|
|
32364
|
-
if (assigned !== void 0 && (!assigned.trim() || /^(?:\s*[-*]?\s*)(?:\(?none\)?|no assigned files)\b/i.test(assigned)))
|
|
32365
|
-
return false;
|
|
32366
|
-
return true;
|
|
32367
|
-
}
|
|
32368
|
-
var CORNER_RUNTIME_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, criteria, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
|
|
32369
|
-
Meet every story; respect every non-goal and risk.
|
|
32370
|
-
Before any code or build action, the brief must hold this ask's outline; opening and prompting inside it is not that outline. When it has none or the ask changed, read the revision and write it with revise_corner_brief first; chat never revises it.
|
|
32371
|
-
Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
|
|
32372
|
-
Reproduce as triage isolated it with available emulator, Playwright, browser and test runner. Record attempts and observations. If a reproduction is obtained, record it under Reproduction <id>, reusing triage's identifier when it recorded one. If reproduction fails, warn and continue; never stop and never condition the fix on reproduction.
|
|
32373
|
-
Fix only the spec and approval quote. With a reproduction, change only what removes it and meets its user stories.
|
|
32374
|
-
Deliver files with post_artifact; answer plain chat questions directly.
|
|
32375
|
-
No unrequested features, flags, compatibility shims or refactors.`;
|
|
32376
|
-
var CORNER_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, criteria, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
|
|
32377
|
-
Meet every story; respect every non-goal and risk. Use its file manifest. Use record_validation_stage for this revision and head. Do not call a missing stage passed.
|
|
32378
|
-
${VALIDATION_STAGE_OWNERSHIP}
|
|
32379
|
-
Before any code or build action, the brief must hold this ask's outline; opening and prompting inside it is not that outline. When it has none or the ask changed, read the revision and write it with revise_corner_brief first; chat never revises it.
|
|
32380
|
-
Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
|
|
32381
|
-
Reproduce as triage isolated it with available emulator, Playwright, browser and test runner. Record attempts and observations. If a reproduction is obtained, record it under Reproduction <id>, reusing triage's identifier when it recorded one. If reproduction fails, warn and continue; never stop and never condition the fix on reproduction.
|
|
32382
|
-
Fix only the spec and approval quote. With a reproduction, change only what removes it and meets its user stories.
|
|
32383
|
-
Only when the brief calls for repository changes, follow the PR procedure below. Otherwise do not commit, push or open a PR; deliver with post_artifact.
|
|
32384
|
-
Before the PR, demonstrate Y: run the built app or affected service and perform X. If no interactive surface is reachable, run the narrowest test or script exercising the exact user path and printing Y. An inner-function unit test, log line or code read is not a demonstration.
|
|
32385
|
-
The PR body MUST have ## Reproduced and ## Demonstrated. Cite Reproduction <id> in both, with steps, the wrong result and the now-passing result. In ## Demonstrated, when none was obtained, state that plainly ("not obtained") and show the regression; for feature work say "not a defect report".
|
|
32386
|
-
No unrequested features, flags, compatibility shims or refactors.`;
|
|
32387
|
-
var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. The active turn prompt names the latest stable green PR head. Review and approve only that exact head; if no stable green head is named, end the turn without a verdict. Never merge yourself.";
|
|
32388
|
-
var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "There is no stable green PR head for this active reviewer turn. Do not review or call approve_merge. End this turn without a verdict; the next green transition will wake you.";
|
|
32389
|
-
var CORNER_DELIVERY_NUDGE = "Only when the brief calls for repository changes, inspect the repository state and finish delivering the work unless a human hold stands: commit and push the intended changes and open the pull request if one does not exist. Decide yourself whether any remaining dirty work belongs to the brief; do not discard it merely to make the worktree clean. The pull request body must carry ## Reproduced and ## Demonstrated; if they are missing, add them before ending the turn. Otherwise do not commit, push or open a PR; deliver with post_artifact.";
|
|
32390
|
-
var CORNER_LAND_RULE = "Then, if a human's question or proposal in the corner has no answer, name it and do not merge; otherwise call merge_corner.";
|
|
32391
|
-
var CORNER_YOLO_MERGE_NUDGE = `Yolo is on. Call pr_checks_status now. If checks="failed", fix the failure and push. If checks="unknown", reply in this corner with the tool reason and stop instead of retrying. Otherwise stop: a reviewer's PASS wakes you to merge with merge_corner. Never merge it any other way.`;
|
|
32392
|
-
var UPGRADE_INTENT_RULE = "Call this on your own judgment when the work in this corner needs the repository; nobody has to ask. Call it while answering a human message in this corner, then write the brief in the restarted code session.";
|
|
32393
|
-
var CORNER_PLACEHOLDER_BRIEF_RULE = "This brief is the placeholder the upgrade wrote from the request. Read the code, write the real spec with revise_corner_brief, then post it in this corner and ask the corner's opener to approve it before editing. This is best effort: if the opener is away or the request was already explicit, use your judgment and proceed. When the opener replies, record their reply as the approval in the next revision.";
|
|
32394
|
-
function isPlaceholderCornerBrief(brief) {
|
|
32395
|
-
return brief.revision === 1 && brief.authorId === SYSTEM_IDENTITY_ID;
|
|
32656
|
+
return [...names];
|
|
32396
32657
|
}
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
|
|
32400
|
-
return "Once the pull request exists, reply with its full URL and end the turn; do not check or wait for CI. This Room has no configured reviewer, so the merge gate never opens for you: never merge; a person merges it.";
|
|
32401
|
-
const reviewer = `the reviewer (${handle(reviewerHandle)})`;
|
|
32402
|
-
return yoloMode ? `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Green checks wake ${reviewer}; its PASS wakes you to merge. ${CORNER_LAND_RULE} You are also woken to fix failing checks, requested changes, or a refused merge; a fixed head needs green checks and a fresh PASS.` : `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Yolo is off, so the merge gate stays closed for you: never merge; a person merges it after ${reviewer} approves.`;
|
|
32658
|
+
function ungatedHostServers(hostServers, granted) {
|
|
32659
|
+
const allowed = new Set(granted);
|
|
32660
|
+
return hostServers.filter((name) => !allowed.has(name));
|
|
32403
32661
|
}
|
|
32404
|
-
function
|
|
32405
|
-
if (!
|
|
32406
|
-
return
|
|
32407
|
-
const
|
|
32408
|
-
|
|
32409
|
-
|
|
32410
|
-
return
|
|
32662
|
+
async function claimGrantedHostRoutes(result) {
|
|
32663
|
+
if (!result || typeof result !== "object" || !("grants" in result))
|
|
32664
|
+
return [];
|
|
32665
|
+
const grants = result.grants;
|
|
32666
|
+
if (!Array.isArray(grants))
|
|
32667
|
+
return [];
|
|
32668
|
+
return grantedMcpServerNames(grants.flatMap((entry) => {
|
|
32669
|
+
if (!entry || typeof entry !== "object")
|
|
32670
|
+
return [];
|
|
32671
|
+
const grant = entry;
|
|
32672
|
+
return grant.kind === "mcp" && typeof grant.target === "string" && (grant.status === "once" || grant.status === "approved") ? [{ kind: "mcp", target: grant.target }] : [];
|
|
32673
|
+
}));
|
|
32411
32674
|
}
|
|
32412
|
-
function
|
|
32413
|
-
|
|
32414
|
-
return void 0;
|
|
32415
|
-
return "Once the pull request exists, reply with its full URL and end the turn. You are this Room's reviewer, so no other agent can review your pull request: do not request one or tag any agent for review. It merges only on a human's yes: ask a Workspace owner or admin to approve the merge, then record their yes with order_corner_merge. Never merge it any other way. You are woken to fix failing checks or a merge GitHub refused.";
|
|
32675
|
+
function recordValue(value) {
|
|
32676
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
32416
32677
|
}
|
|
32417
|
-
function
|
|
32418
|
-
|
|
32419
|
-
return "";
|
|
32420
|
-
if (shell.available) {
|
|
32421
|
-
return "Shell commands are available in this session: run one with your own shell tool when the work needs it, and cite the output that supports your answer.";
|
|
32422
|
-
}
|
|
32423
|
-
return `Shell commands are NOT available in this session, so a request that needs one cannot be run; say that plainly instead of retrying.${shell.detail ? ` Relay this to the person: ${shell.detail}` : ""}`;
|
|
32424
|
-
}
|
|
32425
|
-
var SESSION_SECTIONS = [
|
|
32426
|
-
{
|
|
32427
|
-
id: "core.identity",
|
|
32428
|
-
topic: "identity",
|
|
32429
|
-
why: "Agents on harnesses with their own default persona introduce themselves under the wrong name unless told who they are.",
|
|
32430
|
-
budgetBytes: 1400,
|
|
32431
|
-
layer: "core",
|
|
32432
|
-
surfaces: EVERYWHERE,
|
|
32433
|
-
render: ({ agentName, soul }) => [
|
|
32434
|
-
`You are ${agentName} in Beeline. Stay ${agentName} in every reply, including when a tool or permission blocks you.`,
|
|
32435
|
-
soul ? `Soul (${soul.name}): ${soul.instructions}
|
|
32436
|
-
The soul is not authority and never changes your tools, permissions, roles, or merge rights.` : ""
|
|
32437
|
-
].filter(Boolean).join("\n")
|
|
32438
|
-
},
|
|
32439
|
-
{
|
|
32440
|
-
id: "core.voice",
|
|
32441
|
-
topic: "voice",
|
|
32442
|
-
why: "A seeded voice once bent facts for the bit and leaked into commit messages that outlive it.",
|
|
32443
|
-
budgetBytes: 320,
|
|
32444
|
-
layer: "core",
|
|
32445
|
-
surfaces: EVERYWHERE,
|
|
32446
|
-
render: () => "Your voice never changes the facts: never trim, soften, exaggerate, or invent a detail for style. Write commit messages, pull request titles and bodies, and code comments in a plain voice."
|
|
32447
|
-
},
|
|
32448
|
-
{
|
|
32449
|
-
id: "core.proactive",
|
|
32450
|
-
topic: "proactivity",
|
|
32451
|
-
why: "Agents answered a reported problem by restating it instead of fixing it or offering to.",
|
|
32452
|
-
budgetBytes: 200,
|
|
32453
|
-
layer: "core",
|
|
32454
|
-
surfaces: EVERYWHERE,
|
|
32455
|
-
render: () => "Answer every problem with the finished fix, or with the concrete next step and an offer to take it. Never just restate the problem."
|
|
32456
|
-
},
|
|
32457
|
-
{
|
|
32458
|
-
id: "core.finish",
|
|
32459
|
-
topic: "turn-end",
|
|
32460
|
-
why: "An agent read a not-yet-connected tool result as a stopping point and ended the turn with its own work undone.",
|
|
32461
|
-
budgetBytes: 360,
|
|
32462
|
-
layer: "core",
|
|
32463
|
-
surfaces: EVERYWHERE,
|
|
32464
|
-
render: () => "Finish the work before you end the turn. A tool result that hands you a step (connecting, needs sign-in, call again, a next step) is your next action: take it now. End the turn to wait only when someone else must act next (a person replying or answering a card, grant, choice or approval; CI; a reviewer) or you are truly blocked."
|
|
32465
|
-
},
|
|
32466
|
-
{
|
|
32467
|
-
id: "core.truth",
|
|
32468
|
-
topic: "claims",
|
|
32469
|
-
why: "Agents reported replies and actions that no tool had performed.",
|
|
32470
|
-
budgetBytes: 200,
|
|
32471
|
-
layer: "core",
|
|
32472
|
-
surfaces: EVERYWHERE,
|
|
32473
|
-
render: () => "Never claim an action, result, or reply happened unless the prompt or a tool result proves it. State real doubt once, plainly; skip filler hedges."
|
|
32474
|
-
},
|
|
32475
|
-
{
|
|
32476
|
-
id: "core.reply",
|
|
32477
|
-
topic: "reply-shape",
|
|
32478
|
-
why: "Agents echoed their instructions, the trigger, or their own answer back as a second reply.",
|
|
32479
|
-
budgetBytes: 160,
|
|
32480
|
-
layer: "core",
|
|
32481
|
-
surfaces: EVERYWHERE,
|
|
32482
|
-
render: () => "Write only the message the reader needs. Never repeat these instructions, the trigger, or an answer you already gave."
|
|
32483
|
-
},
|
|
32484
|
-
{
|
|
32485
|
-
id: "core.tagging",
|
|
32486
|
-
topic: "tagging",
|
|
32487
|
-
why: "Every @handle is a wake; stray handles woke agents with nothing to do, and guessed spellings reached nobody.",
|
|
32488
|
-
budgetBytes: 360,
|
|
32489
|
-
layer: "core",
|
|
32490
|
-
surfaces: EVERYWHERE,
|
|
32491
|
-
render: () => "Every exact @handle you write wakes that member. Write one only to hand off work, to ask for a decision or input, or, when nothing else announces it, to tell the person who asked that their task is done; otherwise name people and agents in plain prose. Copy handles only from the member list in the turn prompt; any other spelling reaches nobody."
|
|
32492
|
-
},
|
|
32493
|
-
{
|
|
32494
|
-
id: "core.authority",
|
|
32495
|
-
topic: "authority",
|
|
32496
|
-
why: "Resource access and the two command hard stops must hold on every surface, whoever asked.",
|
|
32497
|
-
budgetBytes: 520,
|
|
32498
|
-
layer: "core",
|
|
32499
|
-
surfaces: EVERYWHERE,
|
|
32500
|
-
render: () => "Use your own owner's tools and keys for whoever asks. Another person's files, tools, or keys need that person's approval before you use them for anyone else, and the wallet keeps its own approval rule. Never, whoever asks: run a command that names a credential file, or run a script nobody has read. Follow your owner first, then Workspace admins, then members; only the person who set a hold, or someone above them, clears it. Delegated and follow-up work keeps the original requester."
|
|
32501
|
-
},
|
|
32502
|
-
{
|
|
32503
|
-
id: "core.feedback",
|
|
32504
|
-
topic: "feedback",
|
|
32505
|
-
why: "Nothing reviews a turn after it ends, so friction in Beeline itself is lost unless the agent that hit it reports it in the turn.",
|
|
32506
|
-
budgetBytes: 260,
|
|
32507
|
-
layer: "core",
|
|
32508
|
-
surfaces: EVERYWHERE,
|
|
32509
|
-
render: () => "Only when this turn hit friction in Beeline itself (a tool error, a retry, a refused call, contradictory instructions, missing context, or a person correcting you), call report_feedback once before ending the turn. Otherwise do not."
|
|
32510
|
-
},
|
|
32511
|
-
{
|
|
32512
|
-
id: "core.files",
|
|
32513
|
-
topic: "files",
|
|
32514
|
-
why: "Files on earlier messages are shown only as markers and never pre-downloaded; agents fetched media URLs instead.",
|
|
32515
|
-
budgetBytes: 160,
|
|
32516
|
-
layer: "core",
|
|
32517
|
-
surfaces: EVERYWHERE,
|
|
32518
|
-
render: () => "For a \u{1F4CE} file, call download_attachment and read its path. Never fetch media URLs; on failure, say you couldn't open it."
|
|
32519
|
-
},
|
|
32520
|
-
{
|
|
32521
|
-
id: "surface.tools",
|
|
32522
|
-
topic: "tools",
|
|
32523
|
-
why: "Agents need app discovery before use in every turn; connector offers remain in Rooms and DMs.",
|
|
32524
|
-
budgetBytes: 240,
|
|
32525
|
-
layer: "surface",
|
|
32526
|
-
surfaces: EVERYWHERE,
|
|
32527
|
-
render: ({ surface }) => surface === "room" || surface === "dm" ? "When you need a tool you do not have, call workbench_status; offer_connector the tool it lists, otherwise call connect_app for the app." : "In corners, workbench_status discovers apps; connect_app starts sign-in here. Connector offers stay in Rooms and DMs."
|
|
32528
|
-
},
|
|
32529
|
-
// --- Room ----------------------------------------------------------------
|
|
32530
|
-
{
|
|
32531
|
-
id: "room.place",
|
|
32532
|
-
topic: "place",
|
|
32533
|
-
why: "The agent must know it cannot write here, where the checkout is, and whether it has a shell.",
|
|
32534
|
-
budgetBytes: 700,
|
|
32535
|
-
layer: "surface",
|
|
32536
|
-
surfaces: ["room"],
|
|
32537
|
-
render: ({ repository, shell }) => [
|
|
32538
|
-
`This is a Beeline Room. The repository filesystem is read-only here.${repository ? ` The Room is bound to ${repository.name} (branch ${repository.branch}); the read-only checkout is at the session root.` : ""} Web search is available.`,
|
|
32539
|
-
shellLine(shell),
|
|
32540
|
-
shell?.available === false ? "Read code with CodeGraph when available, then beeline-readonly-mcp search_text and read_file." : "If a shell command is refused, say so plainly rather than retrying, and read code with CodeGraph when available, then beeline-readonly-mcp search_text and read_file."
|
|
32541
|
-
].filter(Boolean).join(" ")
|
|
32542
|
-
},
|
|
32543
|
-
{
|
|
32544
|
-
id: "room.corners",
|
|
32545
|
-
topic: "repository-work",
|
|
32546
|
-
why: "Repository work must start through the host-governed corner, with a brief as its authority.",
|
|
32547
|
-
budgetBytes: 1100,
|
|
32548
|
-
layer: "surface",
|
|
32549
|
-
surfaces: ["room"],
|
|
32550
|
-
render: () => [
|
|
32551
|
-
"Repository changes happen only in a corner. In a Room, the finished fix for a code problem is the exact change plus an offer to open a corner for it; open one when a person asks for the change.",
|
|
32552
|
-
"Before opening one, consult beeline-triage and beeline-spec, then call open_corner with a name of at most three words joined by hyphens (no whitespace), an objective of at most 24 words, and the brief (a spec plus the approving message). The objective only titles the work; the brief is its authority. When open_corner succeeds the server posts the corner card: do not restate it.",
|
|
32553
|
-
"For corners you belong to, use inspect_corner for status, steer_corner to pass Room input down, and ask_corner for one answer. Never post into a corner without a Room command.",
|
|
32554
|
-
"A corner ends when its pull request merges; since a person may merge minutes after it opens, anything still needed must already live in a different corner before that pull request opens, not after. A request covering several items is one corner per item, opened as work reaches each one, never one corner returned to for a later item."
|
|
32555
|
-
].join(" ")
|
|
32556
|
-
},
|
|
32557
|
-
{
|
|
32558
|
-
id: "room.replying",
|
|
32559
|
-
topic: "room-replies",
|
|
32560
|
-
why: "Several agents share a Room; replies with nothing to act on, and echoed nudges, are noise.",
|
|
32561
|
-
budgetBytes: 520,
|
|
32562
|
-
layer: "surface",
|
|
32563
|
-
surfaces: ["room"],
|
|
32564
|
-
render: () => "If nothing is actionable for you, do not reply. If the current task is only a nudge to respond, answer the most recent unanswered human message instead of echoing the nudge. A poll plurality is a fact, never permission to deploy, delete, merge, or spend. Consult the using-beeline skill for Room mechanics (files, artifacts, schedules, reactions, events, mocks)."
|
|
32565
|
-
},
|
|
32566
|
-
// --- DM ------------------------------------------------------------------
|
|
32567
|
-
{
|
|
32568
|
-
id: "dm.place",
|
|
32569
|
-
topic: "place",
|
|
32570
|
-
why: "A DM has one person, no repository work, and no corners; the agent must not tag or try to open one.",
|
|
32571
|
-
budgetBytes: 700,
|
|
32572
|
-
layer: "surface",
|
|
32573
|
-
surfaces: ["dm"],
|
|
32574
|
-
render: ({ shell }) => [
|
|
32575
|
-
"This is a private direct message with one person: everything they send is for you, so reply without tagging. It has no repository work and no corners, and the filesystem is read-only. Web search is available.",
|
|
32576
|
-
shellLine(shell)
|
|
32577
|
-
].filter(Boolean).join(" ")
|
|
32578
|
-
},
|
|
32579
|
-
{
|
|
32580
|
-
id: "dm.choices",
|
|
32581
|
-
topic: "choices",
|
|
32582
|
-
why: "A pick in a DM is a preference, never authority, and polls need an electorate.",
|
|
32583
|
-
budgetBytes: 300,
|
|
32584
|
-
layer: "surface",
|
|
32585
|
-
surfaces: ["dm"],
|
|
32586
|
-
render: () => "ask_choice offers options; a pick is a preference, never sandbox, spend, or merge authority. Consult the using-beeline skill for files, artifacts, and reactions."
|
|
32587
|
-
},
|
|
32588
|
-
// --- Corners -------------------------------------------------------------
|
|
32589
|
-
{
|
|
32590
|
-
id: "corner.steering",
|
|
32591
|
-
topic: "corner-steering",
|
|
32592
|
-
why: "Active corner turns can steer member siblings without borrowing parent Room authority.",
|
|
32593
|
-
budgetBytes: 160,
|
|
32594
|
-
layer: "surface",
|
|
32595
|
-
surfaces: ["code-corner", "no-code-corner", "review-corner"],
|
|
32596
|
-
render: () => "Use steer_corner during a turn to send input to a member sibling under this parent. Membership alone grants no turn."
|
|
32597
|
-
},
|
|
32598
|
-
{
|
|
32599
|
-
id: "corner.handover",
|
|
32600
|
-
topic: "corner-handover",
|
|
32601
|
-
why: "An agent asked to hand the corner on opened a duplicate sibling corner instead of tagging the other agent.",
|
|
32602
|
-
budgetBytes: 360,
|
|
32603
|
-
layer: "surface",
|
|
32604
|
-
surfaces: ["code-corner", "no-code-corner"],
|
|
32605
|
-
render: () => "When a person asks you to hand this corner to another agent, tag in your reply only an agent that person named by handle or display name: it moves the implementer role here. A tag for any other reason does not move the role. Do not open a second corner for a hand-over."
|
|
32606
|
-
},
|
|
32607
|
-
{
|
|
32608
|
-
id: "corner.worktree",
|
|
32609
|
-
topic: "place",
|
|
32610
|
-
why: "The agent must know its branch and target; only a code-corner author is told to push and open the pull request.",
|
|
32611
|
-
budgetBytes: 900,
|
|
32612
|
-
layer: "surface",
|
|
32613
|
-
surfaces: ["code-corner", "review-corner"],
|
|
32614
|
-
render: ({ worktree, surface, githubCli, brief }) => [
|
|
32615
|
-
`You are in an isolated git worktree on ${worktree?.featureBranch ?? "the feature branch"}, targeting ${worktree?.targetBranch ?? "the target branch"}.`,
|
|
32616
|
-
surface !== "code-corner" || !cornerHasRepositoryWork(brief) ? "" : githubCli === "rest" ? `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, run \`git ls-remote --exit-code origin ${worktree?.featureBranch}\`. Exit code 0: rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Exit code 2: the branch is absent; skip rebase for the first push. Any other non-zero exit is a lookup failure: stop and retry; do not skip rebase or push. Open it with \`gh pr create\` and read it with \`gh pr view\`; this host has no gh, so the Beeline launcher answers both over the GitHub REST API with the app token.` : `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, run \`git ls-remote --exit-code origin ${worktree?.featureBranch}\`. Exit code 0: rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Exit code 2: the branch is absent; skip rebase for the first push. Any other non-zero exit is a lookup failure: stop and retry; do not skip rebase or push. Open the pull request with gh.`
|
|
32617
|
-
].filter(Boolean).join("\n")
|
|
32618
|
-
},
|
|
32619
|
-
{
|
|
32620
|
-
id: "corner.android",
|
|
32621
|
-
topic: "android",
|
|
32622
|
-
why: "An agent under its isolated $HOME concluded the host had no emulator, and agents on the shared adb server drove each other's emulators.",
|
|
32623
|
-
budgetBytes: 380,
|
|
32624
|
-
layer: "surface",
|
|
32625
|
-
surfaces: ["code-corner"],
|
|
32626
|
-
when: ({ android }) => Boolean(android),
|
|
32627
|
-
render: ({ android }) => `Android: adb and emulator are on PATH; the SDK is $ANDROID_HOME, AVDs are in $ANDROID_AVD_HOME, and /dev/kvm works. Your adb server sees only your own emulator. Boot it with \`emulator -avd <name> -port ${android?.emulatorPort} -read-only -no-window -no-snapshot -gpu swiftshader_indirect\`; it reaches host services at 10.0.2.2.`
|
|
32628
|
-
},
|
|
32629
|
-
{
|
|
32630
|
-
id: "corner.prepare",
|
|
32631
|
-
topic: "corner-prepare",
|
|
32632
|
-
why: "Only a checkout that defines the command can run it; another repository fails on its first step.",
|
|
32633
|
-
budgetBytes: 60,
|
|
32634
|
-
layer: "surface",
|
|
32635
|
-
surfaces: ["code-corner"],
|
|
32636
|
-
when: ({ brief, cornerPrepareScript }) => cornerHasRepositoryWork(brief) && cornerPrepareScript === true,
|
|
32637
|
-
render: () => "Run npm run corner:prepare first."
|
|
32638
|
-
},
|
|
32639
|
-
{
|
|
32640
|
-
id: "corner.contract",
|
|
32641
|
-
topic: "author-contract",
|
|
32642
|
-
why: "The reviewer enforces this contract; an author that never saw it fails review.",
|
|
32643
|
-
budgetBytes: 2600,
|
|
32644
|
-
layer: "surface",
|
|
32645
|
-
surfaces: ["code-corner"],
|
|
32646
|
-
render: ({ brief }) => cornerHasRepositoryWork(brief) ? CORNER_AUTHOR_CONTRACT : CORNER_RUNTIME_AUTHOR_CONTRACT
|
|
32647
|
-
},
|
|
32648
|
-
{
|
|
32649
|
-
id: "corner.merge",
|
|
32650
|
-
topic: "merge",
|
|
32651
|
-
why: 'Exactly one rule may say who merges and when; two rules here once said both "merge" and "never merge".',
|
|
32652
|
-
budgetBytes: 520,
|
|
32653
|
-
layer: "surface",
|
|
32654
|
-
surfaces: ["code-corner"],
|
|
32655
|
-
when: ({ brief }) => cornerHasRepositoryWork(brief),
|
|
32656
|
-
render: ({ yoloMode, reviewerHandle, selfReviewer }) => (selfReviewer ? cornerSelfReviewerInstruction({ isReviewer: true, openedByAgent: true }) : cornerMergeInstruction(Boolean(yoloMode), reviewerHandle)) + " Never merge any other pull request."
|
|
32657
|
-
},
|
|
32658
|
-
{
|
|
32659
|
-
id: "corner.checks",
|
|
32660
|
-
topic: "checks-turns",
|
|
32661
|
-
why: "Server check notes were restated as replies, and agents scheduled polls of a gate that wakes them itself.",
|
|
32662
|
-
budgetBytes: 900,
|
|
32663
|
-
layer: "surface",
|
|
32664
|
-
surfaces: ["code-corner"],
|
|
32665
|
-
when: ({ brief }) => cornerHasRepositoryWork(brief),
|
|
32666
|
-
render: () => "When asked whether the reviewer was woken, call pr_checks_status and report reviewerWake; do not invent a cause. Never schedule polls of pr_checks_status or the merge gate; the server wakes you when it changes."
|
|
32667
|
-
},
|
|
32668
|
-
{
|
|
32669
|
-
id: "corner.review",
|
|
32670
|
-
topic: "merge",
|
|
32671
|
-
why: "The reviewer approves one exact green head and never merges.",
|
|
32672
|
-
budgetBytes: 400,
|
|
32673
|
-
layer: "surface",
|
|
32674
|
-
surfaces: ["review-corner"],
|
|
32675
|
-
render: () => `${CORNER_REVIEWER_SESSION_INSTRUCTION} Report findings to the author; never edit or push the author's branch.`
|
|
32676
|
-
},
|
|
32677
|
-
{
|
|
32678
|
-
id: "corner.no-code",
|
|
32679
|
-
topic: "place",
|
|
32680
|
-
why: "A no-code corner has no repository; the agent delivers files, not commits.",
|
|
32681
|
-
budgetBytes: 1e3,
|
|
32682
|
-
layer: "surface",
|
|
32683
|
-
surfaces: ["no-code-corner"],
|
|
32684
|
-
render: ({ agentMayUpgradeCorner }) => [
|
|
32685
|
-
"This is a no-code corner with no repository checkout and no GitHub workflow. Work in this corner's writable workspace: create files with write_scratch_file or your own tools, then send them with post_artifact.",
|
|
32686
|
-
agentMayUpgradeCorner ? `Do not initialize a repository, create a branch, commit, push, open a pull request, or wait for GitHub checks. The one exception is beeline-agent upgrade_corner_to_code. ${UPGRADE_INTENT_RULE} That one-way upgrade restarts this same corner with a feature branch and writable checkout, keeps its discussion, and re-delivers that same request in the code session, so end this turn immediately once it succeeds and do not edit this workspace.` : "Do not initialize a repository, create a branch, commit, push, open a pull request, or wait for GitHub checks."
|
|
32687
|
-
].join("\n")
|
|
32688
|
-
},
|
|
32689
|
-
{
|
|
32690
|
-
id: "corner.no-code-reply",
|
|
32691
|
-
topic: "reply-target",
|
|
32692
|
-
why: "Every turn was told to end with a one-line summary of files it posted, so answers were followed by a restatement of themselves.",
|
|
32693
|
-
budgetBytes: 260,
|
|
32694
|
-
layer: "surface",
|
|
32695
|
-
surfaces: ["no-code-corner"],
|
|
32696
|
-
render: ({ requesterHandle }) => `${requesterHandle ? `Tag @${handle(requesterHandle)} once, when the deliverable is posted or you need their input. ` : ""}When you posted files, name them in one line; never restate your answer. Only a human closes this corner.`
|
|
32697
|
-
}
|
|
32698
|
-
];
|
|
32699
|
-
function applicable(sections, context) {
|
|
32700
|
-
return sections.filter((section) => section.surfaces.includes(context.surface) && (section.when?.(context) ?? true)).map((section) => ({ section, text: section.render(context).trim() })).filter((entry) => entry.text);
|
|
32701
|
-
}
|
|
32702
|
-
function sessionSections(context) {
|
|
32703
|
-
return applicable(SESSION_SECTIONS, context);
|
|
32704
|
-
}
|
|
32705
|
-
function assembleSessionPrompt(context) {
|
|
32706
|
-
const entries = sessionSections(context);
|
|
32707
|
-
const systemPrompt = entries.map((entry) => entry.text).join("\n\n");
|
|
32708
|
-
return {
|
|
32709
|
-
systemPrompt,
|
|
32710
|
-
turnPrefix: harnessHonorsSessionSystemPrompt(context.agentCommand) ? "" : systemPrompt,
|
|
32711
|
-
report: entries.map(({ section, text: text2 }) => ({
|
|
32712
|
-
id: section.id,
|
|
32713
|
-
bytes: Buffer.byteLength(text2)
|
|
32714
|
-
}))
|
|
32715
|
-
};
|
|
32716
|
-
}
|
|
32717
|
-
function renderAssignedCornerBrief(brief) {
|
|
32718
|
-
const approval = brief.approval ? `Approved by ${brief.approval.approverName}, message ${brief.approval.sourceMessageId}: ${brief.approval.text}` : "This revision predates recorded approvals.";
|
|
32719
|
-
return `Brief revision ${brief.revision}:
|
|
32720
|
-
${brief.spec}
|
|
32721
|
-
|
|
32722
|
-
${approval}`;
|
|
32723
|
-
}
|
|
32724
|
-
function roomMentionDirectory(roster, selfId) {
|
|
32725
|
-
const rows = [];
|
|
32726
|
-
let selfHandle;
|
|
32727
|
-
for (const member of roster.members) {
|
|
32728
|
-
if (member.identityId === selfId) {
|
|
32729
|
-
selfHandle = member.handle?.trim().replace(/^@/, "");
|
|
32730
|
-
continue;
|
|
32731
|
-
}
|
|
32732
|
-
const memberHandle = member.handle?.trim().replace(/^@/, "");
|
|
32733
|
-
if (!memberHandle)
|
|
32734
|
-
continue;
|
|
32735
|
-
const name = member.name?.trim() ?? "";
|
|
32736
|
-
const kind = member.kind === "agent" ? "agent" : "person";
|
|
32737
|
-
rows.push(`- @${memberHandle}${name && name !== memberHandle ? ` \u2014 ${name}` : ""} (${kind})`);
|
|
32738
|
-
}
|
|
32739
|
-
if (selfHandle)
|
|
32740
|
-
rows.push(`You are @${selfHandle}, a member of this Room; you are not listed above.`);
|
|
32741
|
-
if (!rows.length)
|
|
32742
|
-
return "";
|
|
32743
|
-
return ["Members (exact tag spellings):", ...rows].join("\n");
|
|
32744
|
-
}
|
|
32745
|
-
var ROOMS = ["room", "dm"];
|
|
32746
|
-
var TURN_SECTIONS = [
|
|
32747
|
-
{
|
|
32748
|
-
id: "turn.session",
|
|
32749
|
-
topic: "session-delivery",
|
|
32750
|
-
why: "Codex, Pi, and Grok drop the ACP session prompt; without this they never saw the corner rules.",
|
|
32751
|
-
budgetBytes: 16e3,
|
|
32752
|
-
layer: "turn",
|
|
32753
|
-
surfaces: EVERYWHERE,
|
|
32754
|
-
render: ({ sessionPrefix }) => sessionPrefix ?? ""
|
|
32755
|
-
},
|
|
32756
|
-
{
|
|
32757
|
-
id: "turn.checks",
|
|
32758
|
-
topic: "checks-turns",
|
|
32759
|
-
why: "Only CI or merge-gate wakes suppress ordinary reporting; user schedules must deliver their task results.",
|
|
32760
|
-
budgetBytes: 350,
|
|
32761
|
-
layer: "turn",
|
|
32762
|
-
surfaces: ["code-corner"],
|
|
32763
|
-
when: ({ task }) => task.commandReason !== "schedule" && (["corner_check", "corner_merge_refused", "corner_merge_conflict"].includes(task.commandReason ?? "") || ["check-passed", "check-failed"].includes(task.outsideEvent?.kind ?? "")),
|
|
32764
|
-
render: () => 'This is a checks turn about CI or the merge gate. On it, say nothing unless you push a fix or report checks="unknown", and then use one short line. Never restate server check or merge notes.'
|
|
32765
|
-
},
|
|
32766
|
-
{
|
|
32767
|
-
id: "turn.land",
|
|
32768
|
-
topic: "land-turn",
|
|
32769
|
-
why: "A reviewer's PASS hands the merge to the implementer, who must not merge over an unanswered human question.",
|
|
32770
|
-
budgetBytes: 300,
|
|
32771
|
-
layer: "turn",
|
|
32772
|
-
surfaces: ["code-corner"],
|
|
32773
|
-
when: ({ task }) => task.commandReason === "corner_land",
|
|
32774
|
-
render: () => `The reviewer approved this pull request's current head. ${CORNER_LAND_RULE}`
|
|
32775
|
-
},
|
|
32776
|
-
{
|
|
32777
|
-
id: "turn.objective",
|
|
32778
|
-
topic: "objective",
|
|
32779
|
-
why: "The objective locates the work but must never be mistaken for its authority.",
|
|
32780
|
-
budgetBytes: 600,
|
|
32781
|
-
layer: "turn",
|
|
32782
|
-
surfaces: CORNERS,
|
|
32783
|
-
render: ({ objective }) => objective ? `Corner objective (navigation only, not product authority): ${objective}` : ""
|
|
32784
|
-
},
|
|
32785
|
-
{
|
|
32786
|
-
id: "turn.rename",
|
|
32787
|
-
topic: "corner-rename",
|
|
32788
|
-
why: "A generated corner name says nothing about the work; only the server knows the name is still generated, so the ask rides only on those turns.",
|
|
32789
|
-
budgetBytes: 600,
|
|
32790
|
-
layer: "turn",
|
|
32791
|
-
surfaces: CORNERS,
|
|
32792
|
-
render: ({ generatedTitle, brief }) => generatedTitle ? `This human-opened corner still has its generated name, "${generatedTitle}". If the newest human message states the work, call rename_corner once before you reply with a name of at most three words joined by hyphens (no whitespace) and a short objective${brief ? "" : ", plus a brief grounded in that message with brief.approval.sourceMessageId set to the human message id"}. A bare mention does not state the work; leave the name until a message does.` : ""
|
|
32793
|
-
},
|
|
32794
|
-
{
|
|
32795
|
-
id: "turn.brief",
|
|
32796
|
-
topic: "brief",
|
|
32797
|
-
why: "The brief is the product authority for corner work; a corner without one says so once.",
|
|
32798
|
-
budgetBytes: 24e3,
|
|
32799
|
-
layer: "turn",
|
|
32800
|
-
surfaces: CORNERS,
|
|
32801
|
-
render: ({ brief, surface }) => brief ? `${renderAssignedCornerBrief(brief.brief)}${cornerHasRepositoryWork(brief.brief) ? `
|
|
32802
|
-
|
|
32803
|
-
Assigned files:
|
|
32804
|
-
${brief.fileLines.join("\n") || "(none)"}` : brief.fileLines.length ? `
|
|
32805
|
-
|
|
32806
|
-
Brief attachments:
|
|
32807
|
-
${brief.fileLines.join("\n")}` : ""}${brief.missingRequiredFile ? "\nRequired assignment files are unavailable. Pause work that depends on them and report the missing file precisely." : ""}${surface === "code-corner" && isPlaceholderCornerBrief(brief.brief) ? `
|
|
32808
|
-
|
|
32809
|
-
${CORNER_PLACEHOLDER_BRIEF_RULE}` : ""}` : "No assigned brief: the human messages in this corner are the authority; skip steps that need a brief revision."
|
|
32810
|
-
},
|
|
32811
|
-
{
|
|
32812
|
-
id: "turn.checkout",
|
|
32813
|
-
topic: "checkout",
|
|
32814
|
-
why: "A Room checkout is refreshed per turn; the agent must know which commit it reads.",
|
|
32815
|
-
budgetBytes: 200,
|
|
32816
|
-
layer: "turn",
|
|
32817
|
-
surfaces: ROOMS,
|
|
32818
|
-
render: ({ checkout }) => checkout ? `Repository checkout: origin/${checkout.branch} at commit ${checkout.commit}, refreshed for this turn.` : ""
|
|
32819
|
-
},
|
|
32820
|
-
{
|
|
32821
|
-
id: "turn.transcript",
|
|
32822
|
-
topic: "transcript",
|
|
32823
|
-
why: "The conversation so far, with rows this session already saw left out.",
|
|
32824
|
-
budgetBytes: 2e5,
|
|
32825
|
-
layer: "turn",
|
|
32826
|
-
surfaces: EVERYWHERE,
|
|
32827
|
-
render: ({ transcript, surface }) => {
|
|
32828
|
-
if (!transcript?.lines.length)
|
|
32829
|
-
return "";
|
|
32830
|
-
const place = ROOMS.includes(surface) ? "Room" : "corner";
|
|
32831
|
-
return `${transcript.sinceLastTurn ? `New in the ${place} since your last turn:` : `${place === "Room" ? "Room conversation" : "Corner transcript"}:`}
|
|
32832
|
-
${transcript.lines.join("\n")}`;
|
|
32833
|
-
}
|
|
32834
|
-
},
|
|
32835
|
-
{
|
|
32836
|
-
id: "turn.resume",
|
|
32837
|
-
topic: "resume",
|
|
32838
|
-
why: "A card answer resumes a paused turn; the agent must know which question was answered.",
|
|
32839
|
-
budgetBytes: 600,
|
|
32840
|
-
layer: "turn",
|
|
32841
|
-
surfaces: ROOMS,
|
|
32842
|
-
render: ({ resume }) => resume ?? ""
|
|
32843
|
-
},
|
|
32844
|
-
{
|
|
32845
|
-
id: "turn.members",
|
|
32846
|
-
topic: "members",
|
|
32847
|
-
why: "Handle spellings change; only the live roster is a safe source.",
|
|
32848
|
-
budgetBytes: 4e3,
|
|
32849
|
-
layer: "turn",
|
|
32850
|
-
surfaces: EVERYWHERE,
|
|
32851
|
-
render: ({ members }) => members ?? ""
|
|
32852
|
-
},
|
|
32853
|
-
{
|
|
32854
|
-
id: "turn.memory",
|
|
32855
|
-
topic: "memory",
|
|
32856
|
-
why: "Keyword-matched institutional memory, compiled and capped by the server.",
|
|
32857
|
-
budgetBytes: 1e3,
|
|
32858
|
-
layer: "turn",
|
|
32859
|
-
surfaces: EVERYWHERE,
|
|
32860
|
-
render: ({ memory }) => memory ?? ""
|
|
32861
|
-
},
|
|
32862
|
-
{
|
|
32863
|
-
id: "turn.corners",
|
|
32864
|
-
topic: "corners",
|
|
32865
|
-
why: "Room and corner agents need exact member corner ids for supported relay actions.",
|
|
32866
|
-
budgetBytes: 8e3,
|
|
32867
|
-
layer: "turn",
|
|
32868
|
-
surfaces: ["room", "code-corner", "no-code-corner", "review-corner"],
|
|
32869
|
-
render: ({ surface, corners, closedCorners }) => [
|
|
32870
|
-
corners?.length ? `Current corners you belong to (use the exact cornerId with ${surface === "room" ? "inspect_corner, steer_corner, or ask_corner" : "steer_corner for a sibling under this parent Room"}):
|
|
32871
|
-
${JSON.stringify(corners)}` : "",
|
|
32872
|
-
surface === "room" && closedCorners?.length ? `Corners you belong to closed in the last 24 hours (merge commit is unavailable when absent):
|
|
32873
|
-
${closedCorners.map((corner) => `- ${corner.name ?? corner.cornerId} (${corner.cornerId}): PR ${corner.pullRequestNumber ? `#${corner.pullRequestNumber}` : "unavailable"}, merge commit ${corner.mergeCommitSha ?? "unavailable"}`).join("\n")}` : ""
|
|
32874
|
-
].filter(Boolean).join("\n\n")
|
|
32875
|
-
},
|
|
32876
|
-
{
|
|
32877
|
-
id: "turn.reviewer-target",
|
|
32878
|
-
topic: "reviewer-target",
|
|
32879
|
-
why: "The reviewer must review the head that is green now, not one named in an older message.",
|
|
32880
|
-
budgetBytes: 1200,
|
|
32881
|
-
layer: "turn",
|
|
32882
|
-
surfaces: ["review-corner"],
|
|
32883
|
-
render: ({ reviewerTarget }) => reviewerTarget ?? ""
|
|
32884
|
-
},
|
|
32885
|
-
{
|
|
32886
|
-
id: "turn.task",
|
|
32887
|
-
topic: "task",
|
|
32888
|
-
why: "The message this turn answers, said once and last.",
|
|
32889
|
-
budgetBytes: 64e3,
|
|
32890
|
-
layer: "turn",
|
|
32891
|
-
surfaces: EVERYWHERE,
|
|
32892
|
-
render: ({ task, surface }) => ROOMS.includes(surface) ? [
|
|
32893
|
-
`Current task from ${task.fromName ?? "a Room member"}:`,
|
|
32894
|
-
task.cornerAskId ? `Corner ask id: ${task.cornerAskId}. Use get_corner_ask to retrieve its status and answer.` : "",
|
|
32895
|
-
task.body,
|
|
32896
|
-
renderReplyContext(task.reply ?? {})
|
|
32897
|
-
].filter(Boolean).join("\n\n") : [
|
|
32898
|
-
...task.reactionTargetId ? [`Reaction target message id: ${task.reactionTargetId}`] : [],
|
|
32899
|
-
`Newest message:
|
|
32900
|
-
${task.body}`,
|
|
32901
|
-
...task.reply?.replyToMessageId ? [renderReplyContext(task.reply)] : [],
|
|
32902
|
-
...task.attachmentLines ?? []
|
|
32903
|
-
].join("\n")
|
|
32904
|
-
}
|
|
32905
|
-
];
|
|
32906
|
-
function assembleTurnPrompt(context) {
|
|
32907
|
-
if (context.task.outsideEvent?.kind?.startsWith("webhook:")) {
|
|
32908
|
-
context = { ...context, task: { ...context.task, body: quoteOutsideData(context.task.outsideEvent.kind, context.task.outsideEvent.payload) } };
|
|
32909
|
-
}
|
|
32910
|
-
const boundedContext = ROOMS.includes(context.surface) ? {
|
|
32911
|
-
...context,
|
|
32912
|
-
task: {
|
|
32913
|
-
...context.task,
|
|
32914
|
-
body: boundRoomTaskBody(context.task.body, context.modelContextTokens)
|
|
32915
|
-
}
|
|
32916
|
-
} : context;
|
|
32917
|
-
const withoutTranscript = applicable(TURN_SECTIONS, {
|
|
32918
|
-
...boundedContext,
|
|
32919
|
-
transcript: void 0
|
|
32920
|
-
});
|
|
32921
|
-
const otherBytes = withoutTranscript.reduce((sum, entry) => sum + Buffer.byteLength(entry.text) + 2, 0);
|
|
32922
|
-
const lines = budgetTranscript(boundedContext.transcript?.lines ?? [], boundedContext.modelContextTokens, otherBytes);
|
|
32923
|
-
const entries = applicable(TURN_SECTIONS, {
|
|
32924
|
-
...boundedContext,
|
|
32925
|
-
transcript: boundedContext.transcript ? { ...boundedContext.transcript, lines } : void 0
|
|
32926
|
-
});
|
|
32927
|
-
return {
|
|
32928
|
-
text: entries.map((entry) => entry.text).join("\n\n"),
|
|
32929
|
-
report: entries.map(({ section, text: text2 }) => ({
|
|
32930
|
-
id: section.id,
|
|
32931
|
-
bytes: Buffer.byteLength(text2)
|
|
32932
|
-
}))
|
|
32933
|
-
};
|
|
32934
|
-
}
|
|
32935
|
-
|
|
32936
|
-
// apps/body/dist/beeline-skill.js
|
|
32937
|
-
var USING_BEELINE_SKILL_NAME = "using-beeline";
|
|
32938
|
-
var BEELINE_TRIAGE_SKILL_NAME = "beeline-triage";
|
|
32939
|
-
var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
|
|
32940
|
-
var BEELINE_SPEC_SKILL_NAME = "beeline-spec";
|
|
32941
|
-
var BEELINE_ROOM_MECHANICS = [
|
|
32942
|
-
"Files and photos on the message you are answering are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline). A file on an earlier message shows only as a \u{1F4CE} marker with an id; call beeline-agent download_attachment with that message id and attachment id, then read the returned path. Never fetch a media URL; if the download fails, tell the requester you could not open the file.",
|
|
32943
|
-
"To create a file you can send, call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture.",
|
|
32944
|
-
"To run something later or repeatedly, call beeline-agent create_schedule (interval in minutes or a 5-field cron, optional maxRuns); list_schedules / update_schedule / delete_schedule manage them, including schedules other agents created for themselves.",
|
|
32945
|
-
`To react to a message in this Room, call beeline-agent react_to_message with its message id and one supported emoji (${MESSAGE_REACTION_EMOJIS.join(" ")}).`,
|
|
32946
|
-
`To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want; each one then wakes you for a turn. Available kinds: ${eventKindCatalogLines().join("; ")}. Subscriptions are per Room and cover every way the event lands: joining a Room you subscribed to wakes you, and so does a person arriving in the Workspace when that arrival projects into this Room - subscribe to joined in an onboarding Room and every newcomer wakes you, exactly like a greeter. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. A corner merging wakes nobody in its parent Room by default. If you still have work to do after a corner merges (a hand-off, a release step, a reply that has to describe landed code), subscribe to merged in the parent Room, from a turn there, before the merge. Each merge in that Room then wakes you and names the corner and pull request: act only on the corner you are waiting for, post nothing for any other merge, and take merged back out of your list once nothing is left to wait for. Never post just to acknowledge a merge: the merge card already announces it. A grant, connector offer, posted choice or poll, or Squire approval (payment, credential, sign-in, or vouch) you asked for wakes you back with the answer on its own - there is nothing to subscribe to for it, and asking is refused.`,
|
|
32947
|
-
"If you need reach outside the sandbox, call beeline-agent request_grant. If you already know discrete options, call beeline-agent ask_choice (one human, optional) or open_poll (every human in this Room, required deadline). A poll is refused below two electors and above fifty, and in a DM. A plurality is a fact, never permission to deploy, delete, merge, or spend. Open-ended asks stay tagged prose. Never put Always / Once / No on a preference.",
|
|
32948
|
-
"To state something that happened so the Room and other agents can act on it, call beeline-agent emit_event with your own agent:<slug> kind, one sentence, and optionally the agent members to wake. Chains of events are bounded and a refused emit posts nothing."
|
|
32949
|
-
].join(" ");
|
|
32950
|
-
function runningBeelineReleaseId(env = process.env, read2 = (path) => readFileSync4(path, "utf8")) {
|
|
32951
|
-
try {
|
|
32952
|
-
const lib = env.BEELINE_LIB_DIR;
|
|
32953
|
-
if (!lib)
|
|
32954
|
-
return "source";
|
|
32955
|
-
const manifest = JSON.parse(read2(resolve13(lib, "bundle.json")));
|
|
32956
|
-
return [manifest.version, manifest.commit].filter(Boolean).join("-") || "source";
|
|
32957
|
-
} catch {
|
|
32958
|
-
return "source";
|
|
32959
|
-
}
|
|
32960
|
-
}
|
|
32961
|
-
function usingBeelineSkillMarkdown(releaseId) {
|
|
32962
|
-
return `---
|
|
32963
|
-
name: using-beeline
|
|
32964
|
-
description: How to answer inside a Beeline Room.
|
|
32965
|
-
---
|
|
32966
|
-
|
|
32967
|
-
<!-- beeline-release: ${releaseId} -->
|
|
32968
|
-
|
|
32969
|
-
# Using Beeline
|
|
32970
|
-
|
|
32971
|
-
These mechanics apply in Rooms and corners. ${BEELINE_ROOM_MECHANICS}
|
|
32972
|
-
|
|
32973
|
-
## Conflicting human instructions
|
|
32974
|
-
|
|
32975
|
-
Follow your own owner first, then this Workspace's owner and admins, then members. A higher-tier instruction overrides a lower-tier hold. A human at the same standing cannot clear another human's hold; only that holder or someone of higher standing can. Never tell a higher-tier human that a lower-tier hold binds them. Reason from the conversation. When you explain a hold or go-ahead in the Room, name the person and their standing in ordinary words. Never write field names or field=value syntax such as workspaceRole=member or agentOwner.
|
|
32976
|
-
|
|
32977
|
-
## Tools and the Workbench
|
|
32978
|
-
|
|
32979
|
-
A **tool** is something you can use once a human adds it; a **key** is the credential that tool holds for that human. You spend a key through the mounted connector and never see the credential itself.
|
|
32980
|
-
|
|
32981
|
-
The tools this build knows, what each is for, and which the owner of this machine already has are one call away: beeline-agent workbench_status. That read is the Workbench of the machine and owner you actually run on, not the person who asked. Connected apps are listed once per app with a stable ID. Their sign-in and credentials stay with the server. Trusty Squire is vaulted credentials plus a browser for the separate key and browser routes. Drive a multi-step browser flow such as checkout or signup with operate_drive rather than one operate_act call per step. Tailscale installs its CLI on the selected helper and opens Tailscale's own browser sign-in; once connected, use the CLI for tailnet resources and tailscale file cp or tailscale file get for Taildrop. The wallet is created only from the Workbench page. When you cannot reach a tool, say: I can't reach X on this machine because Y; to fix it, Z. Give one explanation, then take or offer Z.
|
|
32982
|
-
|
|
32983
|
-
**Inspect your own host before answering.** When a question is about a system on the machine you run on, inspect its live state with the tools you have: processes, logs, local endpoints or service status. Say what you checked. Memory and Room history are not evidence of current state.
|
|
32984
|
-
|
|
32985
|
-
**Use available tools before asking the person.** Before asking a person for a code, a status or a file, call workbench_status and check whether a tool or connected app you already have can get it, then use it. This includes an emailed verification code in an already-connected inbox. Ask only when none can, and say why. The key-ownership and grant rules still apply.
|
|
32986
|
-
|
|
32987
|
-
**Offer the tool at the moment you need it.** When the work in front of you needs a tool the person does not have, do not send them to a settings page and do not stop at naming it. Call workbench_status first - a tool they already have is used, not offered. In a Room or DM, call offer_connector with the connectorType and one short reason: a card appears there, spoken by you, addressed to the person you are answering, with one action. Connector offers are unavailable in corners; use a Room or DM for those. Your turn pauses on the card - say in prose what you are waiting for and end the turn; you are woken when it is added, and then you carry on.
|
|
32988
|
-
|
|
32989
|
-
**Research before you offer, and say so.** Never offer a tool you cannot describe. If someone asks you to install or add something you do not already know - by name, by purpose, or by what it will hold - say plainly that you are looking it up first, find out what it is and what it does with credentials, and state what you learned in your reply BEFORE the card appears. A person reading "Add X?" must already have read, in your own words, what X is. Refusing to act blind is part of being trusted with keys.
|
|
32990
|
-
|
|
32991
|
-
An offer is setup, never authority: it does not replace a grant, write permission, target-branch confirmation, or the merge gate, and it never needs a raw credential in chat. You never pair a tool yourself and never ask anyone for a key value; once a tool is added, provisioning happens inside it and receipts reach the person through the tool's own status message.
|
|
32992
|
-
|
|
32993
|
-
Keys belong to the human who provisioned them. Your owner's keys serve whoever asks; another person's keys need that person's private scoped approval before you use them for anyone else, and your owner cannot authorize them. Use the connector without exposing or sharing raw keys. The Workbench page remains the place a person manages tools and keys by hand (Settings \u2192 Workbench); you point there to MANAGE what exists, not to add what you need.
|
|
32994
|
-
|
|
32995
|
-
## Connect an app
|
|
32996
|
-
|
|
32997
|
-
When a request needs an app or service, call beeline-agent \`workbench_status\` first. For a connected app via composio, use its ID with \`list_app_tools\`, then call \`execute_app_tool\` with the selected tool slug and arguments. For an app via squire-api, call its API with \`use_credential\` (service = the app key); via squire-browser, use the \`operate_*\` tools; via registry-mcp, use its mounted MCP tools. The server executes against that person\u2019s account and decides permission on every call. If tool discovery reports an error, follow its cause; \`needs_connection\` means the account must be reconnected, and \`needs_permission\` means wait for the connected person's decision. Do not switch paths after denial or provider unavailability. No app token, provider key, or sign-in URL belongs in chat or a model prompt.
|
|
32998
|
-
|
|
32999
|
-
For an app that is not connected, call \`connect_app\` with its name, one short reason, and a request-specific \`continuation\`: one sentence stating what you will do right after sign-in. Do not include a link, credential, account identifier, or private data in that sentence. The server chooses the route and returns \`next\`. In a corner, call it from that corner so the sign-in card and connection wake return there. A managed OAuth app presents a sign-in card to its owner; end the turn and wait for the connection wake. If the provider reports managed OAuth unsupported, follow the server's Squire route and its instructions. A provider outage is unavailable, not permission to change routes. After the person connects, read Workbench again and use the app ID. Every use is authorized as \`app:<key>\` and recorded once.
|
|
33000
|
-
|
|
33001
|
-
## Showing a mock
|
|
33002
|
-
|
|
33003
|
-
When a design decision needs eyes, show it instead of describing it. Build ONE self-contained HTML page and post it with beeline-agent post_artifact (mime "text/html", pass the document as html). Everything is inline: a single <style> element for all CSS and data: URLs for any image - no script, no external dependencies, no network references. The validator refuses every <script>, <link>, <iframe>, <object>, <embed>, <form>, inline event handler, and http(s) URL, so a page that reaches for the network never posts. Use the Obsidian Refined tokens: grayscale surfaces, one brass accent #d7af5f, 3px radii, IBM Plex Sans/Mono type. Lay the user stories out as frames - one bordered, labelled block per story, so each story can be judged on its own. After posting, ask for feedback here in the corner: the artifact is the thing people react to, not your prose.
|
|
33004
|
-
|
|
33005
|
-
## Showing a photograph
|
|
33006
|
-
|
|
33007
|
-
When the mock needs a real product photo, do not draw an SVG stand-in. Call beeline-agent fetch_image with the photo's http(s) URL. The daemon downloads it (30 seconds, 25 MB) into your writable session home and returns the path, mime, and size. Read those bytes, base64-encode them, and put them in the HTML as a data: URL (\`<img src="data:image/jpeg;base64,\u2026">\`). Then post_artifact as usual. The validator still refuses every http(s) image reference \u2014 the photograph has to be inline. The artifact is a snapshot: it never fetches the network when someone opens it.
|
|
33008
|
-
`;
|
|
33009
|
-
}
|
|
33010
|
-
function beelineReviewSkillMarkdown(releaseId) {
|
|
33011
|
-
return `---
|
|
33012
|
-
name: beeline-review
|
|
33013
|
-
description: Review a corner pull request against the brief's outline, the approval quote, and the Beeline merge gate.
|
|
33014
|
-
---
|
|
33015
|
-
|
|
33016
|
-
<!-- beeline-release: ${releaseId} -->
|
|
33017
|
-
|
|
33018
|
-
# Beeline pull-request review
|
|
33019
|
-
|
|
33020
|
-
Follow these steps in order. Do not skip or reorder them.
|
|
33021
|
-
|
|
33022
|
-
## 1. Isolate the revision
|
|
33023
|
-
|
|
33024
|
-
- Run \`gh pr view N --json headRefOid,files\` and record \`headRefOid\`.
|
|
33025
|
-
- Run \`gh pr diff N\`.
|
|
33026
|
-
- Review the objective, files, and complete PR diff before making another working copy.
|
|
33027
|
-
- Check out that exact head only when an empirical command must read or run the tree. Never use the author's worktree.
|
|
33028
|
-
- When a checkout is needed, create a temporary detached worktree, install an EXIT trap that removes it with \`git worktree remove --force\` and deletes its temporary directory, and run every command there. Cleanup is mandatory on PASS, FAIL, and command error.
|
|
33029
|
-
- For a deterministic one-shot bootstrap use \`npm run review:exact-head -- <sha> [--typecheck <pkg> \u2026] [--] <pkg>:<glob> \u2026\`: it checks that exact head out, runs \`npm ci\`, builds the workspace package exports, runs the given typechecks and targeted tests with non-interactive PASS/FAIL summaries, and removes the worktree itself (pass \`--keep\` to keep it and print the removal command).
|
|
33030
|
-
- Review and test only the recorded revision. If the head moves, clean up the scratch worktree and start over.
|
|
33031
|
-
|
|
33032
|
-
## 2. P0 - OUTLINE FULFILLED, DEMONSTRATED
|
|
33033
|
-
|
|
33034
|
-
Read the server-assigned brief and its current revision, including every required file. Quote the approval with its message ID and approver before considering the short objective. The spec's outline (user stories, acceptance criteria, non-goals, risks) is the scope, and the approval quote is the human's own words: it wins any conflict with the spec. The short objective is navigation-only text and the PR description cannot add, remove, or narrow scope. A revision with no recorded approval is reviewed against its spec alone. If a required file is unavailable, or the revision changes during review, refuse approval and describe what is missing. A requirement may become out of scope only through a new human-authorized brief revision; reviewer discretion, implementation difficulty, and silence never remove it. A passing narrow test does not prove a broader requirement. Review a deliberate human choice as written, even if it is unusual.
|
|
33035
|
-
|
|
33036
|
-
Before general correctness, produce an outline ledger in spec order: list every \`## User stories\` and \`## Acceptance criteria\` line exactly once as met, unmet, unverified, or out of scope, followed by concrete evidence (for a criterion, the named test run against the PR head) - no stories to list is a FAIL, sent back to the author to write the outline; confirm nothing under \`## Non-goals\` was done; confirm every \`## Risks\` line was addressed or explicitly accepted with a stated reason. For a frontend change, open the rendered screens and compare them against the attached mock, noting every visible deviation. Any missing, unmet, unverified story or acceptance criterion, done non-goal, unaddressed risk, or unreviewed mock mismatch blocks PASS. Out of scope is valid only when the current revision itself records the human-authorized removal.
|
|
33037
|
-
|
|
33038
|
-
Before judging the implementation, independently repeat the two judgment legs from request triage:
|
|
33039
|
-
|
|
33040
|
-
- **Work warranted:** For a bug, reproduce the reported behavior on the target branch. For another request, establish the unmet user need from the request and current product. Search current code, history, issues, and open or recently merged pull requests for work that already resolves or supersedes it. Treat title similarity only as a candidate, not proof of duplication. FAIL confirmed duplicate or obsolete work.
|
|
33041
|
-
- **Desirable:** Check repository-owned goals, invariants, architecture, and established product behavior. Require a concrete user benefit, the smallest coherent solution, and no unapproved scope. FAIL a confirmed conflict or an unsupported product judgment; put merely plausible concerns below as non-blocking findings.
|
|
33042
|
-
|
|
33043
|
-
- Quote the approval text, with its message ID, exactly as stored. Do not quote the short objective as product truth.
|
|
33044
|
-
- Derive the end-user story from the spec and the approval quote in one sentence: \`a user who does X sees Y\`.
|
|
33045
|
-
- Make Y happen against the built PR head: run the app or affected service and perform X.
|
|
33046
|
-
- If no interactive surface is reachable, run the narrowest test or script that exercises the exact user path and prints the observable Y.
|
|
33047
|
-
- Record the command and the observed Y.
|
|
33048
|
-
- A unit test of an inner function, a log line, \`the code looks right\`, or any other proxy does not count.
|
|
33049
|
-
- If the user-visible Y cannot be produced, FAIL now. Nothing below can rescue the review.
|
|
33050
|
-
- For a bug, if a \`Reproduction <id>\` was recorded, quote it, re-run that exact user path on the PR head, and record that the wrong result is gone. FAIL if that proof does not name the identifier, even when other tests pass. If none was obtained, require the proof to say so plainly and show the regression instead; do not fail the review for a missing identifier.
|
|
33051
|
-
- State whether the diff meets every user story and the approval quote without unapproved scope.
|
|
33052
|
-
|
|
33053
|
-
## 3. Empirical pass second
|
|
33054
|
-
|
|
33055
|
-
Build one visible validation record for the brief revision and code head. Assess intent, base synchronization, independent review, tests, documentation, lint and types, publication, CI, and final Beeline authorization. Each applicable stage is pending, running, passed, failed, skipped, or not applicable, with a reason for the last two. A required skipped, failed, or unverified stage blocks PASS. An empty rollup alone is not proof of passing checks. Use pr_checks_status for the exact current head: checks="passed" with checkCount=0 is the server-confirmed no-checks path and satisfies the CI prerequisite for review. Do not refuse review because no literal green check exists, and do not require mergeAllowed=true before reviewing: the missing reviewer PASS itself keeps that gate closed. Pending, failed, or unknown checks still block approval. Reuse valid author evidence; run targeted independent checks where needed. A screenshot or rendered app is needed for visual claims, and server-boundary behavior for authorization claims.
|
|
33056
|
-
Use record_validation_stage for each assessed stage, naming the current brief revision and exact PR head.
|
|
33057
|
-
${VALIDATION_STAGE_OWNERSHIP}
|
|
33058
|
-
The record informs the verdict but never replaces approve_merge or pr_checks_status.
|
|
33059
|
-
|
|
33060
|
-
- Run the repository typecheck and tests touched by the diff.
|
|
33061
|
-
- If the spec or the approval quote names a user path, exercise that path.
|
|
33062
|
-
- Record every command and exit code.
|
|
33063
|
-
- A review with no executed command is invalid and must FAIL.
|
|
33064
|
-
|
|
33065
|
-
### Design rules
|
|
33066
|
-
|
|
33067
|
-
When the diff touches UI and the repository has a DESIGN.md, check every changed style against its rules: colours from theme tokens only, the type roles, the spacing scale, the allowed radii, elevation, the shared components, and touch targets.
|
|
33068
|
-
|
|
33069
|
-
- Run the repository's design lints. In Beeline, from \`apps/mobile\`: \`npx vitest run sources/buzz/calm-lint sources/buzz/design-lint\`.
|
|
33070
|
-
- FAIL when a design baseline count grows, or when an allowlist entry is added without naming the design decision behind it.
|
|
33071
|
-
- Require a screenshot or rendered proof of every visual change in each theme the app ships (in Beeline, Obsidian and Bone).
|
|
33072
|
-
- A design-rule violation is an engineering finding and blocks PASS.
|
|
33073
|
-
|
|
33074
|
-
## 4. Adversarial pass
|
|
33075
|
-
|
|
33076
|
-
- For every changed function, name one concrete input or sequence that breaks it.
|
|
33077
|
-
- If none is found, write \`none found\` for that function.
|
|
33078
|
-
|
|
33079
|
-
## 5. Verify before reporting
|
|
33080
|
-
|
|
33081
|
-
- Confirm every finding by reading the exact line or by running a command.
|
|
33082
|
-
- Put unconfirmed concerns under plausible findings. They never block.
|
|
33083
|
-
|
|
33084
|
-
## 6. Bloat guard
|
|
33085
|
-
|
|
33086
|
-
- Compare net lines with the spec, its user stories and non-goals, and the approval quote.
|
|
33087
|
-
- FAIL backwards-compatibility shims, dual paths, feature flags, or abstractions with one caller.
|
|
33088
|
-
- FAIL machinery the spec and the approval quote did not ask for.
|
|
33089
|
-
|
|
33090
|
-
## 7. Security and data
|
|
33091
|
-
|
|
33092
|
-
- Check credentials, authorization boundaries, and destructive migrations.
|
|
33093
|
-
|
|
33094
|
-
## 8. Gate and verdict
|
|
33095
|
-
|
|
33096
|
-
- Review the exact green head named in your reviewer instruction; a server-confirmed zero-check passed head counts as green. If the head moved, do not approve it.
|
|
33097
|
-
- Re-read the assigned brief revision before the verdict. A repair changes the head and invalidates affected evidence; a requirement correction invalidates the relevant verdict even if code did not change. Keep product-completeness findings (a missing or contradicted user story, done non-goal, unaddressed risk, mock mismatch, or approval quote) separate from engineering findings (correctness, security, maintainability, tests). Give every confirmed finding a stable ID that survives rereview, the affected outline line or \`engineering\`, location, severity, evidence, and repair disposition. Reuse the same ID until that finding is resolved. Mechanical repairs return to the author; ask a human only for a genuinely unresolved product choice. Do not run a nested validation pipeline or push the author's branch.
|
|
33098
|
-
- Always use this exact verdict shape:
|
|
33099
|
-
|
|
33100
|
-
\`approval quoted with its message ID:\`
|
|
33101
|
-
\`user story:\`
|
|
33102
|
-
\`work warranted evidence:\`
|
|
33103
|
-
\`desirability evidence:\`
|
|
33104
|
-
\`reproduction id (or none obtained):\`
|
|
33105
|
-
\`proof of that reproduction (or none obtained + regression):\`
|
|
33106
|
-
\`how Y was demonstrated (or FAIL):\`
|
|
33107
|
-
\`commands run + results:\`
|
|
33108
|
-
\`brief revision:\`
|
|
33109
|
-
\`outline ledger (every user story, acceptance criterion, non-goal and risk + status + evidence; mock-vs-screen notes for frontend work):\`
|
|
33110
|
-
\`validation stages and evidence:\`
|
|
33111
|
-
\`product-completeness findings (block):\`
|
|
33112
|
-
\`engineering findings (block):\`
|
|
33113
|
-
\`plausible findings (do not block):\`
|
|
33114
|
-
\`net lines:\`
|
|
33115
|
-
\`decision: PASS|FAIL\`
|
|
33116
|
-
|
|
33117
|
-
Then take exactly one action:
|
|
33118
|
-
|
|
33119
|
-
- FAIL: call \`record_validation_stage\` with stage \`review\`, status \`failed\`, and the reviewed head SHA, then reply \`@author\` with the confirmed findings and that reviewed head SHA to fix. The recorded review stage is how an implementer who has already fixed the findings can tell whether the review is stale.
|
|
33120
|
-
- PASS: call \`approve_merge\` with the reviewed head SHA and assigned briefRevision (omit the revision only for a legacy corner without a brief), then reply \`approved <reviewed sha>\` without tagging the author. Do not tell the author to merge.
|
|
33121
|
-
- A human's question or proposal in the corner discussion with no answer is a blocking finding: FAIL and name it.
|
|
33122
|
-
- Approving is your last step as reviewer. Your PASS wakes the author, who merges that exact head with \`merge_corner\` once checks are green, the worker's yolo is on, and no human hold stands. You never merge it yourself.
|
|
33123
|
-
`;
|
|
33124
|
-
}
|
|
33125
|
-
function beelineSpecSkillMarkdown(releaseId) {
|
|
33126
|
-
return `---
|
|
33127
|
-
name: beeline-spec
|
|
33128
|
-
description: Prepare or revise a durable corner assignment from Room decisions before repository work. Do not use for ordinary conversation or unrelated artifact delivery.
|
|
33129
|
-
---
|
|
33130
|
-
|
|
33131
|
-
<!-- beeline-release: ${releaseId} -->
|
|
33132
|
-
|
|
33133
|
-
# Durable corner brief
|
|
33134
|
-
|
|
33135
|
-
Create an assignment a fresh session can execute without the parent transcript. A brief has three parts:
|
|
33136
|
-
|
|
33137
|
-
- \`spec\`: one Markdown doc you write, with these headings:
|
|
33138
|
-
- \`## Intent\`: the human's own words, quoted exactly, each with its Room message ID. Never substitute a summary.
|
|
33139
|
-
- \`## User stories\`: what this enables, one per line as "a person who does X sees Y". Only stories traceable to the quoted Intent; never import a plan, a teammate's suggestion, or your own idea as a story. Keep unaffected lines as they are across revisions.
|
|
33140
|
-
- \`## Acceptance criteria\`: the requester's own rules from \`## Intent\`, quoted verbatim and numbered, each naming the test (existing or to be written) that fails if the rule is broken anywhere. Criteria are the invariant; stories are examples of it. Write "None beyond the stories." when the request states no rule.
|
|
33141
|
-
- \`## Non-goals\`: explicit exclusions.
|
|
33142
|
-
- \`## Risks\`: what could go wrong, and for whom.
|
|
33143
|
-
- \`## References\`: each reference or mock with what it is and whose call it is; the mock posted here (DESIGN.md, else existing app styles) is the reviewer's acceptance reference, not any other optional or agent-generated mock.
|
|
33144
|
-
Add implementation guidance under further headings when it helps.
|
|
33145
|
-
- files (\`attachments\`): the Room files the worker needs, each with its purpose and whether it is required.
|
|
33146
|
-
- \`approval\`: one human Room message ID. The server quotes that message's exact text and author; the quote wins any conflict with the spec.
|
|
33147
|
-
|
|
33148
|
-
Do not infer approval from silence, assent to different prose, or the mere existence of a plan. Keep agent recommendations labelled as recommendations. Do not promote a plausible UX detail, algorithm, failure message, or extra test into a user story merely because it sounds helpful.
|
|
33149
|
-
|
|
33150
|
-
## Compact path for a settled small fix
|
|
33151
|
-
|
|
33152
|
-
Use this when the requested behavior, touched surface, exclusions, and proof are already obvious. Inspect the narrow current behavior, write a short spec (quoted intent, observable user stories, non-goals, risks, references), and use the initiating command as the approval. Always compose the full outline and post it to the Room before calling open_corner, even when the initiating command already settles scope and stands as the approval: posting is not optional merely because approval is inferred, since the human must be able to see what you are about to build. \u201CChange the message action label from Remove to Delete; leave confirmation and accessibility wording unchanged\u201D needs the visible-label user story and both exclusions, not another confirmation question.
|
|
33153
|
-
|
|
33154
|
-
## Complex-work planning loop
|
|
33155
|
-
|
|
33156
|
-
Use this default-on loop when work crosses components, changes architecture or authorization, needs a visual choice, has multiple user stories, or still contains a material unknown.
|
|
33157
|
-
|
|
33158
|
-
### 1. Scope and current state
|
|
33159
|
-
|
|
33160
|
-
- Collect every relevant human message, including corrections; later corrections supersede only what they explicitly change. Quote their exact text and IDs under \`## Intent\`.
|
|
33161
|
-
- Inspect current code, data contracts, tests, recent related work, repository invariants, and existing behavior. State what exists, what is missing, and what is deliberately out of scope.
|
|
33162
|
-
- Separate facts, human decisions, agent recommendations, and unresolved choices.
|
|
33163
|
-
|
|
33164
|
-
### 2. User stories and product boundary
|
|
33165
|
-
|
|
33166
|
-
- Write the actor/action/observable-result stories.
|
|
33167
|
-
- Map each story to its own observable line and explicit non-goals.
|
|
33168
|
-
- If one unresolved choice would materially change behavior, scope, irreversible effects, or the intended result, present only that choice or the affected brief slice to the human. Use the answering message as the \`approval\`. Never treat no answer as approval.
|
|
33169
|
-
|
|
33170
|
-
### 3. Architecture and data flow
|
|
33171
|
-
|
|
33172
|
-
- Trace inputs, authorities, state transitions, persistence, outputs, and trust boundaries.
|
|
33173
|
-
- Name the existing components to reuse and the smallest coherent changes.
|
|
33174
|
-
- Describe migration and compatibility behavior only where the human intent or existing data requires it.
|
|
33175
|
-
|
|
33176
|
-
### 4. Failure modes and test map
|
|
33177
|
-
|
|
33178
|
-
- Enumerate missing/stale data, retries, concurrency, partial failure, authorization failure, rollback, and boundary-specific hazards that apply.
|
|
33179
|
-
- Map every user story to its proof boundary: unit, integration, device/browser, server authorization, migration, or live harness. No story may be left without planned evidence.
|
|
33180
|
-
|
|
33181
|
-
### 5. Mocks and references
|
|
33182
|
-
|
|
33183
|
-
- When a visual or interaction decision needs eyes, create and post one self-contained mock using the using-beeline mock procedure.
|
|
33184
|
-
- A mock/file posted with \`post_artifact\` in this planning turn is added automatically to the brief attachment manifest; do not manually copy its object ID. Still list it under \`## References\` with whose call it is. Existing human-posted files must be named explicitly in attachments.
|
|
33185
|
-
|
|
33186
|
-
### 6. Implementation tasks
|
|
33187
|
-
|
|
33188
|
-
- Synthesize ordered, independently verifiable tasks. For each task name the affected boundary, user stories, expected files/components, failure handling, and proof.
|
|
33189
|
-
- Keep the spec actionable, not a transcript summary.
|
|
33190
|
-
|
|
33191
|
-
### 7. Bounded adversarial second read (default on)
|
|
33192
|
-
|
|
33193
|
-
- Spend one pass, bounded to the drafted scope, trying to disprove completeness: find a missing story, authority mismatch, unhandled failure, user story without proof, mock ambiguity, accidental scope expansion, or task-order hazard.
|
|
33194
|
-
- Return findings to the planning agent and repair the draft. Escalate only a genuinely material product choice to the human; engineering choices and mechanical gaps are resolved in the plan.
|
|
33195
|
-
- End after one adversarial pass unless its repair exposes one new material product choice. Do not recursively review the review.
|
|
33196
|
-
|
|
33197
|
-
## Dispatch and revision
|
|
33198
|
-
|
|
33199
|
-
Existing authorization permits dispatch when the initiating command already settles the exact material scope; that command is the approval, but the outline is still posted to the Room first so the human can see it before open_corner runs. Drafting a brief does not create approval. When the scope was not already explicit, post the spec and ask the corner's opener to approve it, then use their reply as the \`approval\`. Honor an explicit request to review first.
|
|
33200
|
-
|
|
33201
|
-
Pass the brief as \`open_corner.brief\`. Read the current revision before revising and use \`revise_corner_brief\` with the complete replacement spec, the approving message, and a concise change description. Preserve unaffected intent quotes, user stories, and references. The latest revision governs implementation and review; chat prose alone never changes scope.
|
|
33202
|
-
`;
|
|
33203
|
-
}
|
|
33204
|
-
function beelineTriageSkillMarkdown(releaseId) {
|
|
33205
|
-
return `---
|
|
33206
|
-
name: beeline-triage
|
|
33207
|
-
description: Clarify and assess a user request before opening a Beeline corner, then bind a bugfix to its recorded reproduction. Use before open_corner and while implementing a bug in a corner.
|
|
33208
|
-
---
|
|
33209
|
-
|
|
33210
|
-
<!-- beeline-release: ${releaseId} -->
|
|
33211
|
-
|
|
33212
|
-
# Beeline request triage
|
|
33213
|
-
|
|
33214
|
-
Run these checks before opening a corner.
|
|
33215
|
-
|
|
33216
|
-
## 1. Is it clear?
|
|
33217
|
-
|
|
33218
|
-
- Rewrite the request as a concrete outcome and the user stories it enables.
|
|
33219
|
-
- State material exclusions (non-goals) and the risks needed to prevent unrequested work or surprise.
|
|
33220
|
-
- Keep the corner objective complete and within 24 words.
|
|
33221
|
-
- First ask whether the request has any material ambiguity - one that would change scope, behavior, or risk - and resolve it with the human before doing anything else; never resolve it by adopting a teammate's plan, a prior document, or your own assumption.
|
|
33222
|
-
- Once it's resolved, say plainly if the request itself is suboptimal and give your own recommendation - you are expected to disagree, not just permitted to.
|
|
33223
|
-
- On a judgment call you're still not sure of, tag a Room colleague for an adversarial second opinion and ask them to reply tagging you back; skip this only when you are the only agent in the Room.
|
|
33224
|
-
|
|
33225
|
-
## 2. Is work warranted?
|
|
33226
|
-
|
|
33227
|
-
- For a bug, try to reproduce the exact user-visible behavior and record the evidence.
|
|
33228
|
-
- For another request, identify the unmet user need and the current behavior that does not meet it.
|
|
33229
|
-
- Search current code, history, issues, and open or recently merged pull requests for released or unreleased work that resolves or supersedes the request.
|
|
33230
|
-
- Treat similar titles as candidates. Confirm behavior and scope before calling work duplicate.
|
|
33231
|
-
- If the need cannot be established, reproduction fails, or other work may obviate it, warn; do not block.
|
|
33232
|
-
|
|
33233
|
-
## 3. Is it desirable?
|
|
33234
|
-
|
|
33235
|
-
- Compare the request with repository-owned goals, invariants, architecture, and established product behavior.
|
|
33236
|
-
- Look for concrete user benefit, the smallest coherent solution, and ongoing maintenance cost.
|
|
33237
|
-
- Do not invent product strategy. If the evidence is absent or conflicting, warn; do not block.
|
|
33238
|
-
|
|
33239
|
-
## Output
|
|
33240
|
-
|
|
33241
|
-
When dispatching work, pass the complete brief to open_corner under existing authorization, after posting the outline as beeline-spec requires. Add applicable warnings to the brief:
|
|
33242
|
-
|
|
33243
|
-
\`Triage warning \u2014 warranted: <evidence-backed reason>\`
|
|
33244
|
-
\`Triage warning \u2014 desirable: <evidence-backed reason>\`
|
|
33245
|
-
|
|
33246
|
-
When a bug reproduction succeeds, also emit:
|
|
33247
|
-
|
|
33248
|
-
\`Reproduction <id>: <user path> \u2192 <observable wrong result>\`
|
|
33249
|
-
|
|
33250
|
-
Do not emit a warning merely because evidence is incomplete when the repository offers no practical way to obtain it. Never describe a warning as approval or rejection. Warnings inform the user and implementer; they do not block work.
|
|
33251
|
-
|
|
33252
|
-
## Bugfix execution
|
|
33253
|
-
|
|
33254
|
-
Once a corner is open for a reported bug, follow these steps in order. They are instruction, not a server gate. They do not condition the fix on reproduction. Warranted-work and desirability warnings still do not block.
|
|
33255
|
-
|
|
33256
|
-
### 1. Attempt to reproduce
|
|
33257
|
-
|
|
33258
|
-
- Attempt to reproduce the bug as triage isolated it, using every tool the host offers: emulator, Playwright, browser, test runner.
|
|
33259
|
-
- Record what was tried and what was observed.
|
|
33260
|
-
- If a reproduction is obtained, record the exact user path and the observable wrong result under \`Reproduction <id>\`. Reuse the identifier triage emitted when it recorded one.
|
|
33261
|
-
- If reproduction fails, warn and continue exactly as triage already does. Never stop. Never condition the fix on reproduction.
|
|
33262
|
-
|
|
33263
|
-
### 2. Narrow fix
|
|
33264
|
-
|
|
33265
|
-
- Narrow the fix to the reported behavior.
|
|
33266
|
-
- When a reproduction exists, change only what removes that recorded reproduction.
|
|
33267
|
-
- Nearby improvements stay out of scope.
|
|
33268
|
-
|
|
33269
|
-
### 3. Proof matching triage
|
|
33270
|
-
|
|
33271
|
-
- When a reproduction exists, re-run it, cite the same identifier, and show it now passing, plus the regression that would fail if the bug returned.
|
|
33272
|
-
- When none was obtained, state that plainly and show the regression instead.
|
|
33273
|
-
- Name the identifier in the pull request when one exists so the reviewer can check that proof, not merely that some test exists.
|
|
33274
|
-
`;
|
|
33275
|
-
}
|
|
33276
|
-
|
|
33277
|
-
// apps/body/dist/external-mcp-capabilities.js
|
|
33278
|
-
var SQUIRE_MCP_VERSION = "latest";
|
|
33279
|
-
var SQUIRE_MCP_PACKAGE = `@trusty-squire/mcp@${SQUIRE_MCP_VERSION}`;
|
|
33280
|
-
function isTrustySquireMcpLaunch(command, args = []) {
|
|
33281
|
-
return [command, ...args].some((value) => {
|
|
33282
|
-
const normalized = value.trim().replace(/\\/g, "/").toLowerCase();
|
|
33283
|
-
return /(^|[\s/="'\[,])@trusty-squire\/mcp(?:@[^\s/"'\],}]+)?(?=$|[\s/"'\],}])/i.test(normalized) || /(^|\/)trusty-squire(?:-mcp)?(?:\.[a-z0-9]+)?$/i.test(normalized);
|
|
33284
|
-
});
|
|
33285
|
-
}
|
|
33286
|
-
var SQUIRE_GOVERNED_TOOLS = [
|
|
33287
|
-
"use_credential",
|
|
33288
|
-
"grant_app_access",
|
|
33289
|
-
"revoke_app_access"
|
|
33290
|
-
];
|
|
33291
|
-
var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
|
|
33292
|
-
|
|
33293
|
-
// apps/body/dist/host-mcp-route.js
|
|
33294
|
-
import { join as join11, resolve as resolve14 } from "node:path";
|
|
33295
|
-
var import_yaml = __toESM(require_dist(), 1);
|
|
33296
|
-
init_squire_host();
|
|
33297
|
-
function grantedMcpServerNames(grants) {
|
|
33298
|
-
const names = /* @__PURE__ */ new Set();
|
|
33299
|
-
for (const grant of grants ?? []) {
|
|
33300
|
-
if (grant.kind !== "mcp")
|
|
33301
|
-
continue;
|
|
33302
|
-
const name = grant.target.trim();
|
|
33303
|
-
if (name)
|
|
33304
|
-
names.add(name);
|
|
33305
|
-
}
|
|
33306
|
-
return [...names];
|
|
33307
|
-
}
|
|
33308
|
-
function ungatedHostServers(hostServers, granted) {
|
|
33309
|
-
const allowed = new Set(granted);
|
|
33310
|
-
return hostServers.filter((name) => !allowed.has(name));
|
|
33311
|
-
}
|
|
33312
|
-
async function claimGrantedHostRoutes(result) {
|
|
33313
|
-
if (!result || typeof result !== "object" || !("grants" in result))
|
|
33314
|
-
return [];
|
|
33315
|
-
const grants = result.grants;
|
|
33316
|
-
if (!Array.isArray(grants))
|
|
33317
|
-
return [];
|
|
33318
|
-
return grantedMcpServerNames(grants.flatMap((entry) => {
|
|
33319
|
-
if (!entry || typeof entry !== "object")
|
|
33320
|
-
return [];
|
|
33321
|
-
const grant = entry;
|
|
33322
|
-
return grant.kind === "mcp" && typeof grant.target === "string" && (grant.status === "once" || grant.status === "approved") ? [{ kind: "mcp", target: grant.target }] : [];
|
|
33323
|
-
}));
|
|
33324
|
-
}
|
|
33325
|
-
function recordValue(value) {
|
|
33326
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
33327
|
-
}
|
|
33328
|
-
function stringArray(value) {
|
|
33329
|
-
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : [];
|
|
32678
|
+
function stringArray(value) {
|
|
32679
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : [];
|
|
33330
32680
|
}
|
|
33331
32681
|
function isSquireDeclaration(name, declaration) {
|
|
33332
32682
|
if (isCodeOwnedHostMcpName(name))
|
|
@@ -34896,7 +34246,7 @@ async function materializedSkillManifest(target) {
|
|
|
34896
34246
|
return lines.sort().join("\n");
|
|
34897
34247
|
}
|
|
34898
34248
|
function sha2563(value) {
|
|
34899
|
-
return
|
|
34249
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
34900
34250
|
}
|
|
34901
34251
|
async function resolveSharedSkillSources(operatorHome, names) {
|
|
34902
34252
|
if (names.length > 0)
|
|
@@ -35076,7 +34426,7 @@ async function writeIsolatedHarnessFile(path, content) {
|
|
|
35076
34426
|
}
|
|
35077
34427
|
}
|
|
35078
34428
|
function roomSessionTmpDir(root, base = "/tmp") {
|
|
35079
|
-
const hash =
|
|
34429
|
+
const hash = createHash7("sha256").update(resolve16(root)).digest("hex").slice(0, 12);
|
|
35080
34430
|
return join12(base, `beeline-${hash}`);
|
|
35081
34431
|
}
|
|
35082
34432
|
function roomAgentHomeEnv(root) {
|
|
@@ -36553,7 +35903,7 @@ var DEFAULT_UPDATE_INTERVAL_MS = 3e4;
|
|
|
36553
35903
|
var UPDATE_DRAIN_DEADLINE_MS = UPDATE_CONVERGENCE_SLO_MS - 15e4;
|
|
36554
35904
|
var MAX_HOST_RESTART_STAGGER_MS = 15e3;
|
|
36555
35905
|
function managedRestartStaggerMs(agentId, desiredRelease, drainDeadlineAt, now2 = Date.now()) {
|
|
36556
|
-
const digest =
|
|
35906
|
+
const digest = createHash8("sha256").update(agentId).update("\0").update(desiredRelease).digest();
|
|
36557
35907
|
const spread = digest.readUInt32BE(0) % (MAX_HOST_RESTART_STAGGER_MS + 1);
|
|
36558
35908
|
return Math.min(spread, Math.max(0, drainDeadlineAt - now2));
|
|
36559
35909
|
}
|
|
@@ -37442,7 +36792,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
|
|
|
37442
36792
|
|
|
37443
36793
|
// apps/body/dist/connect-command.js
|
|
37444
36794
|
import { spawn as spawn13 } from "node:child_process";
|
|
37445
|
-
import { createHash as
|
|
36795
|
+
import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
|
|
37446
36796
|
import { chmod as chmod7, mkdir as mkdir16, readFile as readFile20, unlink as unlink7, writeFile as writeFile13 } from "node:fs/promises";
|
|
37447
36797
|
import { homedir as homedir15, hostname } from "node:os";
|
|
37448
36798
|
import { dirname as dirname19, resolve as resolve26 } from "node:path";
|
|
@@ -38512,7 +37862,7 @@ async function readMachineId(env = process.env) {
|
|
|
38512
37862
|
await writeFile13(machineIdPath, `${machineId}
|
|
38513
37863
|
`, { mode: 384 });
|
|
38514
37864
|
} catch {
|
|
38515
|
-
machineId =
|
|
37865
|
+
machineId = createHash9("sha256").update(machineName).digest("hex").slice(0, 36);
|
|
38516
37866
|
}
|
|
38517
37867
|
}
|
|
38518
37868
|
return { machineId, machineName };
|
|
@@ -38521,7 +37871,7 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl, machine
|
|
|
38521
37871
|
const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
|
|
38522
37872
|
if (!normalizedPairingCode)
|
|
38523
37873
|
throw new Error("invalid pairing code");
|
|
38524
|
-
const avatarSeed =
|
|
37874
|
+
const avatarSeed = createHash9("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
|
|
38525
37875
|
return jsonRequest(`${baseUrl}/auth/agent/connect`, {
|
|
38526
37876
|
pairing_code: normalizedPairingCode,
|
|
38527
37877
|
harness: selection.harness,
|
|
@@ -38992,13 +38342,13 @@ async function applyRuntimeModelPreflight(config, agent, selection, validate = v
|
|
|
38992
38342
|
}
|
|
38993
38343
|
|
|
38994
38344
|
// apps/body/dist/model-catalog-sync.js
|
|
38995
|
-
import { createHash as
|
|
38345
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
38996
38346
|
import { readFile as readFile21, writeFile as writeFile14 } from "node:fs/promises";
|
|
38997
38347
|
import { resolve as resolve27 } from "node:path";
|
|
38998
38348
|
var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
|
|
38999
38349
|
var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
|
|
39000
38350
|
function modelCatalogHash(options, selection, startupUnavailable) {
|
|
39001
|
-
return
|
|
38351
|
+
return createHash10("sha256").update(JSON.stringify({
|
|
39002
38352
|
options,
|
|
39003
38353
|
selection: selection ?? null,
|
|
39004
38354
|
startupUnavailable: startupUnavailable ?? null
|
|
@@ -39629,7 +38979,7 @@ function describe2(error) {
|
|
|
39629
38979
|
|
|
39630
38980
|
// apps/body/dist/room-runtime.js
|
|
39631
38981
|
import { execFile as execFile13 } from "node:child_process";
|
|
39632
|
-
import { createHash as
|
|
38982
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
39633
38983
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
|
|
39634
38984
|
import { mkdir as mkdir27, readdir as readdir9, realpath as realpath2, rm as rm15 } from "node:fs/promises";
|
|
39635
38985
|
import { dirname as dirname27, relative as relative3, resolve as resolve38, sep as sep2 } from "node:path";
|
|
@@ -39637,7 +38987,7 @@ import { promisify as promisify8 } from "node:util";
|
|
|
39637
38987
|
|
|
39638
38988
|
// apps/body/dist/grant-runner.js
|
|
39639
38989
|
import { execFile as execFile8 } from "node:child_process";
|
|
39640
|
-
import { createHash as
|
|
38990
|
+
import { createHash as createHash11, randomBytes as randomBytes9, randomUUID as randomUUID9 } from "node:crypto";
|
|
39641
38991
|
import { chmod as chmod8, mkdir as mkdir17, readFile as readFile22, readdir as readdir6, rename as rename9, rm as rm11, rmdir, stat as stat7, writeFile as writeFile15 } from "node:fs/promises";
|
|
39642
38992
|
import { createServer as createServer3 } from "node:http";
|
|
39643
38993
|
import { dirname as dirname21, resolve as resolve28 } from "node:path";
|
|
@@ -39902,7 +39252,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
|
|
|
39902
39252
|
if (!script || script.path !== argument.path) {
|
|
39903
39253
|
throw new Error(`${argument.path} is a script no human has read: this grant was approved without it. Ask with request_grant kind=command so the card carries what the command runs.`);
|
|
39904
39254
|
}
|
|
39905
|
-
if (
|
|
39255
|
+
if (createHash11("sha256").update(bytes).digest("hex") !== script.sha256) {
|
|
39906
39256
|
throw new Error(`${argument.path} changed after it was approved, so the approval no longer covers it. Ask with request_grant kind=command again so a human reads what runs now.`);
|
|
39907
39257
|
}
|
|
39908
39258
|
}
|
|
@@ -40492,7 +39842,7 @@ async function runServerCommandIntake(options) {
|
|
|
40492
39842
|
|
|
40493
39843
|
// apps/body/dist/monolith-corner-turn.js
|
|
40494
39844
|
import { execFile as execFile12 } from "node:child_process";
|
|
40495
|
-
import { createHash as
|
|
39845
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
40496
39846
|
import { existsSync as existsSync9 } from "node:fs";
|
|
40497
39847
|
import { mkdir as mkdir26, readFile as readFile30, writeFile as writeFile20 } from "node:fs/promises";
|
|
40498
39848
|
import { homedir as homedir18, hostname as hostname3 } from "node:os";
|
|
@@ -41606,361 +40956,1038 @@ function shellPayload(toolCall) {
|
|
|
41606
40956
|
const rawInput = toolCall?.rawInput;
|
|
41607
40957
|
if (typeof rawInput === "string")
|
|
41608
40958
|
return true;
|
|
41609
|
-
if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput))
|
|
40959
|
+
if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput))
|
|
40960
|
+
return false;
|
|
40961
|
+
const record5 = rawInput;
|
|
40962
|
+
return typeof record5.command === "string" || typeof record5.cmd === "string";
|
|
40963
|
+
}
|
|
40964
|
+
var ROOM_MOUNTED_MCP_SERVERS = [
|
|
40965
|
+
READ_ONLY_MCP_SERVER_NAME,
|
|
40966
|
+
BEELINE_AGENT_MCP_SERVER_NAME
|
|
40967
|
+
];
|
|
40968
|
+
var DISPATCHER_TOOL_NAME_KEYS = ["tool_name", "toolName"];
|
|
40969
|
+
var DISPATCHER_TOOL_INPUT_KEYS = ["tool_input", "toolInput"];
|
|
40970
|
+
var TOOL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
40971
|
+
function permissionRecord(rawInput) {
|
|
40972
|
+
return rawInput && typeof rawInput === "object" && !Array.isArray(rawInput) ? rawInput : void 0;
|
|
40973
|
+
}
|
|
40974
|
+
function dispatchedPayloadIsShell(record5) {
|
|
40975
|
+
if (!record5)
|
|
40976
|
+
return false;
|
|
40977
|
+
return DISPATCHER_TOOL_INPUT_KEYS.some((key) => key in record5 && shellPayload({ rawInput: record5[key] }));
|
|
40978
|
+
}
|
|
40979
|
+
function toolIdentityCandidates(toolCall) {
|
|
40980
|
+
const candidates = [];
|
|
40981
|
+
const add = (value) => {
|
|
40982
|
+
if (typeof value === "string" && value.trim())
|
|
40983
|
+
candidates.push(value.trim());
|
|
40984
|
+
};
|
|
40985
|
+
add(toolCall?.title);
|
|
40986
|
+
const record5 = permissionRecord(toolCall?.rawInput);
|
|
40987
|
+
for (const key of DISPATCHER_TOOL_NAME_KEYS)
|
|
40988
|
+
add(record5?.[key]);
|
|
40989
|
+
return candidates;
|
|
40990
|
+
}
|
|
40991
|
+
function normalizedServerName(name) {
|
|
40992
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
40993
|
+
}
|
|
40994
|
+
function resolveMountedMcpToolCall(request, mountedServers) {
|
|
40995
|
+
const toolCall = request.toolCall;
|
|
40996
|
+
if (shellPayload(toolCall))
|
|
40997
|
+
return void 0;
|
|
40998
|
+
const record5 = permissionRecord(toolCall?.rawInput);
|
|
40999
|
+
if (dispatchedPayloadIsShell(record5))
|
|
41000
|
+
return void 0;
|
|
41001
|
+
const candidates = toolIdentityCandidates(toolCall);
|
|
41002
|
+
for (const server of mountedServers) {
|
|
41003
|
+
const spellings = /* @__PURE__ */ new Set([server.toLowerCase(), normalizedServerName(server)]);
|
|
41004
|
+
for (const candidate of candidates) {
|
|
41005
|
+
const lowered = candidate.toLowerCase();
|
|
41006
|
+
for (const spelling of spellings) {
|
|
41007
|
+
for (const separator of TOOL_NAME_SEPARATORS) {
|
|
41008
|
+
const prefix = `${spelling}${separator}`;
|
|
41009
|
+
if (!lowered.startsWith(prefix))
|
|
41010
|
+
continue;
|
|
41011
|
+
const tool = candidate.slice(prefix.length).trim();
|
|
41012
|
+
if (TOOL_NAME_PATTERN.test(tool))
|
|
41013
|
+
return { server, tool };
|
|
41014
|
+
}
|
|
41015
|
+
}
|
|
41016
|
+
}
|
|
41017
|
+
}
|
|
41018
|
+
return void 0;
|
|
41019
|
+
}
|
|
41020
|
+
function isReadOnlyMcpPermissionRequest(request) {
|
|
41021
|
+
const toolCall = request.toolCall;
|
|
41022
|
+
const title = toolCall?.title?.trim() ?? "";
|
|
41023
|
+
const rawInput = toolCall?.rawInput;
|
|
41024
|
+
const mcpCall = Boolean(rawInput) && typeof rawInput === "object" && !Array.isArray(rawInput) ? rawInput : void 0;
|
|
41025
|
+
if (mcpCall?.server === READ_ONLY_MCP_SERVER_NAME && typeof mcpCall.tool === "string" && READ_ONLY_TOOL_SET.has(mcpCall.tool)) {
|
|
41026
|
+
return true;
|
|
41027
|
+
}
|
|
41028
|
+
if (shellPayload(toolCall))
|
|
41029
|
+
return false;
|
|
41030
|
+
if (READ_ONLY_PERMISSION_TITLES.has(title))
|
|
41031
|
+
return true;
|
|
41032
|
+
if (READ_ONLY_SERVER_TITLE_PREFIXES.some((prefix) => title.startsWith(prefix))) {
|
|
41033
|
+
for (const tool of READ_ONLY_TOOL_NAMES) {
|
|
41034
|
+
if (title === tool || title.endsWith(`(${tool})`) || TOOL_NAME_SEPARATORS.some((separator) => title.endsWith(`${separator}${tool}`))) {
|
|
41035
|
+
return true;
|
|
41036
|
+
}
|
|
41037
|
+
}
|
|
41038
|
+
}
|
|
41039
|
+
return false;
|
|
41040
|
+
}
|
|
41041
|
+
function isMountedMcpToolPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_SERVERS) {
|
|
41042
|
+
const toolCall = request.toolCall;
|
|
41043
|
+
const rawInput = toolCall?.rawInput;
|
|
41044
|
+
if (dispatchedPayloadIsShell(permissionRecord(rawInput)))
|
|
41045
|
+
return false;
|
|
41046
|
+
if (rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)) {
|
|
41047
|
+
const call = rawInput;
|
|
41048
|
+
if (typeof call.server === "string" && typeof call.tool === "string")
|
|
41049
|
+
return true;
|
|
41050
|
+
}
|
|
41051
|
+
if (shellPayload(toolCall))
|
|
41052
|
+
return false;
|
|
41053
|
+
const title = toolCall?.title?.trim() ?? "";
|
|
41054
|
+
if (/^mcp__[^_].*__[^_]/.test(title))
|
|
41055
|
+
return true;
|
|
41056
|
+
if (/^mcp\.[^.]+\.[^.]/.test(title))
|
|
41057
|
+
return true;
|
|
41058
|
+
if (resolveMountedMcpToolCall(request, mountedServers))
|
|
41059
|
+
return true;
|
|
41060
|
+
return isReadOnlyMcpPermissionRequest(request) || isBeelineAgentMcpPermissionRequest(request);
|
|
41061
|
+
}
|
|
41062
|
+
var AGENT_SURFACE_TOOL_NAMES = [
|
|
41063
|
+
"get_avatar",
|
|
41064
|
+
"set_avatar",
|
|
41065
|
+
"open_corner",
|
|
41066
|
+
"pr_checks_status",
|
|
41067
|
+
"post_artifact",
|
|
41068
|
+
"write_scratch_file",
|
|
41069
|
+
"fetch_image",
|
|
41070
|
+
"connect_app"
|
|
41071
|
+
];
|
|
41072
|
+
function isHostMcpPermissionRequest(request, hostServers = CODE_OWNED_HOST_MCP_NAMES) {
|
|
41073
|
+
const rawInput = request.toolCall?.rawInput;
|
|
41074
|
+
if (rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)) {
|
|
41075
|
+
const server = rawInput.server;
|
|
41076
|
+
if (typeof server === "string" && isHostMcpIdentity(server, hostServers))
|
|
41077
|
+
return true;
|
|
41078
|
+
}
|
|
41079
|
+
return toolIdentityCandidates(request.toolCall).some((candidate) => isHostMcpIdentity(candidate, hostServers));
|
|
41080
|
+
}
|
|
41081
|
+
function isBeelineAgentMcpPermissionRequest(request) {
|
|
41082
|
+
const toolCall = request.toolCall;
|
|
41083
|
+
const title = toolCall?.title?.trim() ?? "";
|
|
41084
|
+
const rawInput = toolCall?.rawInput;
|
|
41085
|
+
if (rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)) {
|
|
41086
|
+
const call = rawInput;
|
|
41087
|
+
if (call.server === BEELINE_AGENT_MCP_SERVER_NAME && typeof call.tool === "string" && AGENT_SURFACE_TOOL_NAMES.includes(call.tool)) {
|
|
41088
|
+
return true;
|
|
41089
|
+
}
|
|
41090
|
+
}
|
|
41091
|
+
if (shellPayload(toolCall))
|
|
41092
|
+
return false;
|
|
41093
|
+
const normalized = BEELINE_AGENT_MCP_SERVER_NAME.replaceAll("-", "_");
|
|
41094
|
+
return AGENT_SURFACE_TOOL_NAMES.flatMap((tool) => [
|
|
41095
|
+
`mcp__${BEELINE_AGENT_MCP_SERVER_NAME}__${tool}`,
|
|
41096
|
+
`mcp__${normalized}__${tool}`,
|
|
41097
|
+
`mcp.${BEELINE_AGENT_MCP_SERVER_NAME}.${tool}`
|
|
41098
|
+
]).includes(title);
|
|
41099
|
+
}
|
|
41100
|
+
function isMcpShapedPermissionRequest(request, hostServers = CODE_OWNED_HOST_MCP_NAMES) {
|
|
41101
|
+
if (isHostMcpPermissionRequest(request, hostServers))
|
|
41102
|
+
return true;
|
|
41103
|
+
const record5 = permissionRecord(request.toolCall?.rawInput);
|
|
41104
|
+
if (typeof record5?.server === "string")
|
|
41105
|
+
return true;
|
|
41106
|
+
if (DISPATCHER_TOOL_NAME_KEYS.some((key) => typeof record5?.[key] === "string"))
|
|
41107
|
+
return true;
|
|
41108
|
+
const title = request.toolCall?.title?.trim() ?? "";
|
|
41109
|
+
return /^mcp__[^_].*__[^_]/.test(title) || /^mcp\.[^.]+\.[^.]/.test(title);
|
|
41110
|
+
}
|
|
41111
|
+
|
|
41112
|
+
// apps/body/dist/room-session.js
|
|
41113
|
+
var ReadOnlyToolsUnavailableError = class extends Error {
|
|
41114
|
+
name = "ReadOnlyToolsUnavailableError";
|
|
41115
|
+
};
|
|
41116
|
+
function beelineAgentMcpServer(config, api, context) {
|
|
41117
|
+
if (!config.readonlyMcpCommand) {
|
|
41118
|
+
throw new ReadOnlyToolsUnavailableError("agent tools unavailable: the Beeline MCP command is required");
|
|
41119
|
+
}
|
|
41120
|
+
const connection = api.connection();
|
|
41121
|
+
return {
|
|
41122
|
+
name: BEELINE_AGENT_MCP_SERVER_NAME,
|
|
41123
|
+
command: config.readonlyMcpCommand,
|
|
41124
|
+
args: [...config.readonlyMcpArgs ?? []],
|
|
41125
|
+
env: [
|
|
41126
|
+
{ name: "BEELINE_MCP_SURFACE", value: "agent" },
|
|
41127
|
+
...context.turnContextPath ? [{ name: "BEELINE_TURN_CONTEXT_FILE", value: context.turnContextPath }] : [],
|
|
41128
|
+
...context.directMessage ? [{ name: "BEELINE_AGENT_DM", value: "1" }] : [],
|
|
41129
|
+
...context.squireRelay ? [
|
|
41130
|
+
{ name: "BEELINE_SQUIRE_RELAY_URL", value: context.squireRelay.url },
|
|
41131
|
+
{ name: "BEELINE_SQUIRE_RELAY_TOKEN", value: context.squireRelay.token }
|
|
41132
|
+
] : [],
|
|
41133
|
+
{ name: "BEELINE_DAEMON_BASE_URL", value: connection.baseUrl },
|
|
41134
|
+
{ name: "BEELINE_DAEMON_TOKEN", value: connection.daemonToken },
|
|
41135
|
+
{ name: "BEELINE_HELPER_VERSION", value: connection.helperVersion },
|
|
41136
|
+
{ name: "BEELINE_DAEMON_AGENT_ID", value: connection.agentId },
|
|
41137
|
+
{ name: "BEELINE_OPERATOR_HOME", value: config.operatorHome ?? homedir16() },
|
|
41138
|
+
{ name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
|
|
41139
|
+
{ name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
|
|
41140
|
+
...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
|
|
41141
|
+
...context.agentMayCloseCorner ? [{ name: "BEELINE_CORNER_AGENT_CLOSE", value: "1" }] : [],
|
|
41142
|
+
...context.reviewer ? [{ name: "BEELINE_CORNER_REVIEWER", value: "1" }] : [],
|
|
41143
|
+
...context.cornerId && context.repositoryBacked ? [{ name: "BEELINE_CORNER_REPOSITORY", value: "1" }] : [],
|
|
41144
|
+
...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
|
|
41145
|
+
...context.attachScratchRoot ? [{ name: "BEELINE_ATTACH_SCRATCH_ROOT", value: context.attachScratchRoot }] : [],
|
|
41146
|
+
...context.grantRunner ? [
|
|
41147
|
+
{ name: "BEELINE_GRANT_RUNNER_URL", value: context.grantRunner.url },
|
|
41148
|
+
{ name: "BEELINE_GRANT_RUNNER_TOKEN", value: context.grantRunner.token }
|
|
41149
|
+
] : []
|
|
41150
|
+
]
|
|
41151
|
+
};
|
|
41152
|
+
}
|
|
41153
|
+
function readOnlyMcpServer(config, cwd, agentMemoryDir) {
|
|
41154
|
+
if (!config.readonlyMcpCommand) {
|
|
41155
|
+
throw new ReadOnlyToolsUnavailableError("read-only tools unavailable: beeline-readonly-mcp is required for Room sessions");
|
|
41156
|
+
}
|
|
41157
|
+
const skillDir = agentSkillDir(config.agentKind);
|
|
41158
|
+
return {
|
|
41159
|
+
name: READ_ONLY_MCP_SERVER_NAME,
|
|
41160
|
+
command: config.readonlyMcpCommand,
|
|
41161
|
+
args: [...config.readonlyMcpArgs ?? []],
|
|
41162
|
+
env: [
|
|
41163
|
+
{ name: "BEELINE_READONLY_ROOT", value: resolve32(cwd) },
|
|
41164
|
+
...config.agentHomeRoot ? [
|
|
41165
|
+
{
|
|
41166
|
+
name: "BEELINE_READONLY_AGENT_SKILLS_ROOT",
|
|
41167
|
+
value: resolve32(config.agentHomeRoot, skillDir, "skills")
|
|
41168
|
+
}
|
|
41169
|
+
] : [],
|
|
41170
|
+
...agentMemoryDir ? [{ name: "BEELINE_READONLY_AGENT_MEMORY_ROOT", value: resolve32(agentMemoryDir) }] : []
|
|
41171
|
+
]
|
|
41172
|
+
};
|
|
41173
|
+
}
|
|
41174
|
+
|
|
41175
|
+
// apps/body/dist/institutional-context.js
|
|
41176
|
+
import { performance as performance2 } from "node:perf_hooks";
|
|
41177
|
+
var INSTITUTIONAL_CONTEXT_TIMEOUT_MS = 500;
|
|
41178
|
+
var INSTITUTIONAL_MEMORY_LIVE_FLAG = "BEELINE_INSTITUTIONAL_MEMORY_ENABLED";
|
|
41179
|
+
function institutionalMemoryFlagEnabled(env, flag) {
|
|
41180
|
+
return env[flag] !== "false";
|
|
41181
|
+
}
|
|
41182
|
+
var EMPTY_INSTITUTIONAL_CONTEXT = {
|
|
41183
|
+
snapshotRevision: 0,
|
|
41184
|
+
text: "",
|
|
41185
|
+
itemIds: [],
|
|
41186
|
+
totalBytes: 0,
|
|
41187
|
+
omitted: {}
|
|
41188
|
+
};
|
|
41189
|
+
var INSTITUTIONAL_CONTEXT_MISSED_TEXT = "institutional memory did not load this turn; use search_memory before concluding anything is not stored.";
|
|
41190
|
+
function startInstitutionalContextFetch(api, roomId, enabled = institutionalMemoryFlagEnabled(process.env, INSTITUTIONAL_MEMORY_LIVE_FLAG), now2 = () => performance2.now()) {
|
|
41191
|
+
const startedAt = now2();
|
|
41192
|
+
const promise = enabled ? api.execute("getInstitutionalContext", { roomId }) : Promise.resolve(EMPTY_INSTITUTIONAL_CONTEXT);
|
|
41193
|
+
promise.catch(() => {
|
|
41194
|
+
});
|
|
41195
|
+
return { promise, startedAt };
|
|
41196
|
+
}
|
|
41197
|
+
async function awaitInstitutionalContext(fetch2, log2 = console.warn, budgetMs = INSTITUTIONAL_CONTEXT_TIMEOUT_MS, now2 = () => performance2.now()) {
|
|
41198
|
+
const remainingMs = budgetMs - (now2() - fetch2.startedAt);
|
|
41199
|
+
let timer;
|
|
41200
|
+
try {
|
|
41201
|
+
const snapshot = await Promise.race([
|
|
41202
|
+
fetch2.promise,
|
|
41203
|
+
new Promise((_, reject) => {
|
|
41204
|
+
timer = setTimeout(() => reject(new Error("institutional context timed out")), Math.max(0, remainingMs));
|
|
41205
|
+
timer.unref?.();
|
|
41206
|
+
})
|
|
41207
|
+
]);
|
|
41208
|
+
return { ...snapshot, outcome: snapshot.text !== "" ? "served" : "empty" };
|
|
41209
|
+
} catch (error) {
|
|
41210
|
+
log2(`institutional context unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
41211
|
+
return {
|
|
41212
|
+
...EMPTY_INSTITUTIONAL_CONTEXT,
|
|
41213
|
+
text: INSTITUTIONAL_CONTEXT_MISSED_TEXT,
|
|
41214
|
+
outcome: "timed-out"
|
|
41215
|
+
};
|
|
41216
|
+
} finally {
|
|
41217
|
+
if (timer)
|
|
41218
|
+
clearTimeout(timer);
|
|
41219
|
+
}
|
|
41220
|
+
}
|
|
41221
|
+
|
|
41222
|
+
// apps/body/dist/codegraph.js
|
|
41223
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
41224
|
+
import { access as access3, appendFile, mkdir as mkdir21, readFile as readFile28 } from "node:fs/promises";
|
|
41225
|
+
import { dirname as dirname23, isAbsolute as isAbsolute3, resolve as resolve33 } from "node:path";
|
|
41226
|
+
import { promisify as promisify6 } from "node:util";
|
|
41227
|
+
var execFileAsync6 = promisify6(execFile11);
|
|
41228
|
+
var CODEGRAPH_MCP_SERVER_NAME = "codegraph";
|
|
41229
|
+
var CODEGRAPH_INDEX_PATH = ".codegraph/codegraph.db";
|
|
41230
|
+
var CODEGRAPH_PREPARE_TIMEOUT_MS = 12e4;
|
|
41231
|
+
function codegraphIndexDirectory(cwd) {
|
|
41232
|
+
return resolve33(cwd, dirname23(CODEGRAPH_INDEX_PATH));
|
|
41233
|
+
}
|
|
41234
|
+
function codegraphMcpServer(config, cwd, options) {
|
|
41235
|
+
if (!config.codegraphCommand)
|
|
41236
|
+
return void 0;
|
|
41237
|
+
return {
|
|
41238
|
+
name: CODEGRAPH_MCP_SERVER_NAME,
|
|
41239
|
+
command: config.codegraphCommand,
|
|
41240
|
+
args: ["serve", "--mcp", "--path", resolve33(cwd), ...options.readonly ? ["--no-watch"] : []],
|
|
41241
|
+
env: [
|
|
41242
|
+
{ name: "CODEGRAPH_TELEMETRY", value: "0" },
|
|
41243
|
+
// One MCP child per harness session is easier to contain than a daemon
|
|
41244
|
+
// that can outlive the Room/corner whose index and permissions it used.
|
|
41245
|
+
{ name: "CODEGRAPH_NO_DAEMON", value: "1" }
|
|
41246
|
+
]
|
|
41247
|
+
};
|
|
41248
|
+
}
|
|
41249
|
+
function codegraphFingerprintServers(config, servers, mounted = Boolean(config.codegraphCommand)) {
|
|
41250
|
+
return config.codegraphCommand && mounted ? [...servers, CODEGRAPH_MCP_SERVER_NAME] : [...servers];
|
|
41251
|
+
}
|
|
41252
|
+
async function isGitWorktree(cwd) {
|
|
41253
|
+
try {
|
|
41254
|
+
const { stdout: stdout6 } = await execFileAsync6("git", ["rev-parse", "--show-toplevel"], {
|
|
41255
|
+
cwd,
|
|
41256
|
+
timeout: 1e4
|
|
41257
|
+
});
|
|
41258
|
+
return resolve33(stdout6.trim()) === resolve33(cwd);
|
|
41259
|
+
} catch {
|
|
41260
|
+
return false;
|
|
41261
|
+
}
|
|
41262
|
+
}
|
|
41263
|
+
async function excludeIndexFromGitStatus(cwd) {
|
|
41264
|
+
try {
|
|
41265
|
+
const { stdout: stdout6 } = await execFileAsync6("git", ["rev-parse", "--git-path", "info/exclude"], {
|
|
41266
|
+
cwd,
|
|
41267
|
+
timeout: 1e4
|
|
41268
|
+
});
|
|
41269
|
+
const gitPath = stdout6.trim();
|
|
41270
|
+
if (!gitPath)
|
|
41271
|
+
return;
|
|
41272
|
+
const path = isAbsolute3(gitPath) ? gitPath : resolve33(cwd, gitPath);
|
|
41273
|
+
await mkdir21(dirname23(path), { recursive: true });
|
|
41274
|
+
const existing = await readFile28(path, "utf8").catch(() => "");
|
|
41275
|
+
if (existing.split("\n").includes(".codegraph/"))
|
|
41276
|
+
return;
|
|
41277
|
+
await appendFile(path, `${existing && !existing.endsWith("\n") ? "\n" : ""}.codegraph/
|
|
41278
|
+
`);
|
|
41279
|
+
} catch {
|
|
41280
|
+
}
|
|
41281
|
+
}
|
|
41282
|
+
async function prepareCodegraphIndex(config, cwd) {
|
|
41283
|
+
const command = config.codegraphCommand;
|
|
41284
|
+
if (!command || !await isGitWorktree(cwd))
|
|
41285
|
+
return false;
|
|
41286
|
+
await excludeIndexFromGitStatus(cwd);
|
|
41287
|
+
const indexPath = resolve33(cwd, CODEGRAPH_INDEX_PATH);
|
|
41288
|
+
const indexed = await access3(indexPath).then(() => true, () => false);
|
|
41289
|
+
const args = indexed ? ["sync", "--quiet", cwd] : ["init", "--yes", cwd];
|
|
41290
|
+
try {
|
|
41291
|
+
await execFileAsync6(command, args, {
|
|
41292
|
+
cwd,
|
|
41293
|
+
env: { ...process.env, CODEGRAPH_TELEMETRY: "0", CODEGRAPH_NO_DAEMON: "1" },
|
|
41294
|
+
timeout: CODEGRAPH_PREPARE_TIMEOUT_MS,
|
|
41295
|
+
maxBuffer: 4 * 1024 * 1024
|
|
41296
|
+
});
|
|
41297
|
+
return true;
|
|
41298
|
+
} catch (error) {
|
|
41299
|
+
console.warn(`[body] CodeGraph ${args[0]} failed for ${cwd}; continuing with Beeline read tools:`, error);
|
|
41610
41300
|
return false;
|
|
41611
|
-
|
|
41612
|
-
return typeof record5.command === "string" || typeof record5.cmd === "string";
|
|
41301
|
+
}
|
|
41613
41302
|
}
|
|
41614
|
-
|
|
41615
|
-
|
|
41616
|
-
|
|
41617
|
-
|
|
41618
|
-
|
|
41619
|
-
|
|
41620
|
-
|
|
41621
|
-
|
|
41622
|
-
|
|
41303
|
+
|
|
41304
|
+
// apps/body/dist/monolith-room-turn.js
|
|
41305
|
+
import { mkdir as mkdir23, writeFile as writeFile18 } from "node:fs/promises";
|
|
41306
|
+
import { homedir as homedir17, hostname as hostname2 } from "node:os";
|
|
41307
|
+
import { dirname as dirname24, join as join18 } from "node:path";
|
|
41308
|
+
|
|
41309
|
+
// packages/api-contract/dist/scheduled-prompts.js
|
|
41310
|
+
var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
|
|
41311
|
+
var SCHEDULE_RAN_VERB = "ran a schedule for";
|
|
41312
|
+
|
|
41313
|
+
// apps/body/dist/transcript-budget.js
|
|
41314
|
+
function budgetTranscript(lines, modelContextTokens, otherPromptBytes) {
|
|
41315
|
+
const context = modelContextTokens && modelContextTokens > 0 ? modelContextTokens : 32768;
|
|
41316
|
+
const reserveTokens = Math.max(4096, Math.ceil(context * 0.3));
|
|
41317
|
+
const available = Math.max(0, context - Math.ceil(otherPromptBytes / 2) - reserveTokens);
|
|
41318
|
+
const budget = Math.max(0, Math.min(24e3, Math.floor(context * 0.25), available * 2) - 128);
|
|
41319
|
+
if (!budget)
|
|
41320
|
+
return [];
|
|
41321
|
+
const selected = [];
|
|
41322
|
+
let used = 0;
|
|
41323
|
+
const rowLimit = Math.min(4096, Math.floor(budget / 2));
|
|
41324
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
41325
|
+
const row = boundMessageText(lines[index], rowLimit);
|
|
41326
|
+
const bytes = Buffer.byteLength(row) + 1;
|
|
41327
|
+
if (used + bytes > budget)
|
|
41328
|
+
break;
|
|
41329
|
+
selected.push(row);
|
|
41330
|
+
used += bytes;
|
|
41331
|
+
}
|
|
41332
|
+
selected.reverse();
|
|
41333
|
+
if (selected.length < lines.length) {
|
|
41334
|
+
selected.unshift(`[${lines.length - selected.length} older messages omitted from this prompt]`);
|
|
41335
|
+
}
|
|
41336
|
+
return selected;
|
|
41623
41337
|
}
|
|
41624
|
-
function
|
|
41625
|
-
|
|
41626
|
-
|
|
41627
|
-
return DISPATCHER_TOOL_INPUT_KEYS.some((key) => key in record5 && shellPayload({ rawInput: record5[key] }));
|
|
41338
|
+
function boundRoomTaskBody(body, modelContextTokens) {
|
|
41339
|
+
const context = modelContextTokens && modelContextTokens > 0 ? modelContextTokens : 32768;
|
|
41340
|
+
return boundMessageText(body, Math.min(8e3, Math.floor(context / 4)));
|
|
41628
41341
|
}
|
|
41629
|
-
function
|
|
41630
|
-
const
|
|
41631
|
-
const
|
|
41632
|
-
|
|
41633
|
-
candidates.push(value.trim());
|
|
41634
|
-
};
|
|
41635
|
-
add(toolCall?.title);
|
|
41636
|
-
const record5 = permissionRecord(toolCall?.rawInput);
|
|
41637
|
-
for (const key of DISPATCHER_TOOL_NAME_KEYS)
|
|
41638
|
-
add(record5?.[key]);
|
|
41639
|
-
return candidates;
|
|
41342
|
+
function boundMessageText(original, maxBytes) {
|
|
41343
|
+
const sanitized = stripInlineData(original);
|
|
41344
|
+
const clean4 = sanitized === original ? original : sanitized + fetchPointer(original, "inline content omitted");
|
|
41345
|
+
return Buffer.byteLength(clean4) > maxBytes ? truncateRow(clean4, maxBytes) : clean4;
|
|
41640
41346
|
}
|
|
41641
|
-
function
|
|
41642
|
-
return
|
|
41347
|
+
function stripInlineData(text2) {
|
|
41348
|
+
return text2.replace(/data:[^\s"'<>)]*/gi, "[inline data omitted]").replace(/\b[A-Za-z0-9+/]{512,}={0,2}\b/g, (candidate) => /[A-Z]/.test(candidate) && /[a-z]/.test(candidate) && /[0-9+/]/.test(candidate) ? "[inline binary data omitted]" : candidate);
|
|
41643
41349
|
}
|
|
41644
|
-
function
|
|
41645
|
-
const
|
|
41646
|
-
|
|
41647
|
-
|
|
41648
|
-
|
|
41649
|
-
|
|
41650
|
-
return void 0;
|
|
41651
|
-
const candidates = toolIdentityCandidates(toolCall);
|
|
41652
|
-
for (const server of mountedServers) {
|
|
41653
|
-
const spellings = /* @__PURE__ */ new Set([server.toLowerCase(), normalizedServerName(server)]);
|
|
41654
|
-
for (const candidate of candidates) {
|
|
41655
|
-
const lowered = candidate.toLowerCase();
|
|
41656
|
-
for (const spelling of spellings) {
|
|
41657
|
-
for (const separator of TOOL_NAME_SEPARATORS) {
|
|
41658
|
-
const prefix = `${spelling}${separator}`;
|
|
41659
|
-
if (!lowered.startsWith(prefix))
|
|
41660
|
-
continue;
|
|
41661
|
-
const tool = candidate.slice(prefix.length).trim();
|
|
41662
|
-
if (TOOL_NAME_PATTERN.test(tool))
|
|
41663
|
-
return { server, tool };
|
|
41664
|
-
}
|
|
41665
|
-
}
|
|
41666
|
-
}
|
|
41667
|
-
}
|
|
41668
|
-
return void 0;
|
|
41350
|
+
function truncateRow(row, maxBytes) {
|
|
41351
|
+
const pointer = fetchPointer(row, "message truncated");
|
|
41352
|
+
const contentBytes = Math.max(0, maxBytes - Buffer.byteLength(pointer));
|
|
41353
|
+
let prefix = Buffer.from(row).subarray(0, contentBytes).toString("utf8");
|
|
41354
|
+
prefix = prefix.replace(/\uFFFD$/, "");
|
|
41355
|
+
return prefix + pointer;
|
|
41669
41356
|
}
|
|
41670
|
-
function
|
|
41671
|
-
const
|
|
41672
|
-
|
|
41673
|
-
|
|
41674
|
-
|
|
41675
|
-
if (mcpCall?.server === READ_ONLY_MCP_SERVER_NAME && typeof mcpCall.tool === "string" && READ_ONLY_TOOL_SET.has(mcpCall.tool)) {
|
|
41676
|
-
return true;
|
|
41677
|
-
}
|
|
41678
|
-
if (shellPayload(toolCall))
|
|
41679
|
-
return false;
|
|
41680
|
-
if (READ_ONLY_PERMISSION_TITLES.has(title))
|
|
41681
|
-
return true;
|
|
41682
|
-
if (READ_ONLY_SERVER_TITLE_PREFIXES.some((prefix) => title.startsWith(prefix))) {
|
|
41683
|
-
for (const tool of READ_ONLY_TOOL_NAMES) {
|
|
41684
|
-
if (title === tool || title.endsWith(`(${tool})`) || TOOL_NAME_SEPARATORS.some((separator) => title.endsWith(`${separator}${tool}`))) {
|
|
41685
|
-
return true;
|
|
41686
|
-
}
|
|
41687
|
-
}
|
|
41688
|
-
}
|
|
41689
|
-
return false;
|
|
41357
|
+
function fetchPointer(row, reason) {
|
|
41358
|
+
const id = /^\[message id: ([^\]\n]+)\]/.exec(row)?.[1];
|
|
41359
|
+
return id ? `
|
|
41360
|
+
[${reason}; call get_room_message with messageId "${id}" and offset 0 for the full text]` : `
|
|
41361
|
+
[${reason}; read the source conversation for the full text]`;
|
|
41690
41362
|
}
|
|
41691
|
-
|
|
41692
|
-
|
|
41693
|
-
|
|
41694
|
-
if (
|
|
41363
|
+
|
|
41364
|
+
// apps/body/dist/prompt-assembly.js
|
|
41365
|
+
function renderReplyContext(message) {
|
|
41366
|
+
if (!message.replyToMessageId)
|
|
41367
|
+
return "";
|
|
41368
|
+
const author = message.replyToAuthorName ?? message.replyToAuthorId ?? "unknown author";
|
|
41369
|
+
return [
|
|
41370
|
+
`Reply to message ${message.replyToMessageId} by ${JSON.stringify(author)}:`,
|
|
41371
|
+
...message.replyToExcerpt !== void 0 ? [`> ${JSON.stringify(boundReplyExcerpt(message.replyToExcerpt))}`] : []
|
|
41372
|
+
].join("\n");
|
|
41373
|
+
}
|
|
41374
|
+
var PROMPT_SURFACES = [
|
|
41375
|
+
"room",
|
|
41376
|
+
"dm",
|
|
41377
|
+
"code-corner",
|
|
41378
|
+
"repo-less-corner"
|
|
41379
|
+
];
|
|
41380
|
+
var CORNERS = ["code-corner", "repo-less-corner"];
|
|
41381
|
+
var EVERYWHERE = PROMPT_SURFACES;
|
|
41382
|
+
function cornerHasRepositoryWork(brief) {
|
|
41383
|
+
if (!brief)
|
|
41695
41384
|
return false;
|
|
41696
|
-
if (
|
|
41697
|
-
|
|
41698
|
-
|
|
41699
|
-
|
|
41385
|
+
if (brief.repositoryWork !== void 0)
|
|
41386
|
+
return brief.repositoryWork;
|
|
41387
|
+
const nonGoalsSections = [
|
|
41388
|
+
...brief.spec.matchAll(/^## (?:Non-goals|Not doing)\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/gim)
|
|
41389
|
+
].map((match) => match[1] ?? "");
|
|
41390
|
+
const prohibition = /^(?:repository edits?|repository changes?|pull requests?|(?:edit|modify|change) (?:the )?(?:repository|repo|code)|push(?:es)?|commit(?:s)?)[.!;]*$/i;
|
|
41391
|
+
for (const nonGoals of nonGoalsSections) {
|
|
41392
|
+
for (const line of nonGoals.split("\n")) {
|
|
41393
|
+
const bullet = /^\s*[-*]\s+/.test(line);
|
|
41394
|
+
const clause = line.trim().replace(/^[-*]\s+/, "");
|
|
41395
|
+
const negative = /^(?:no|never|do not|forbid(?:s|den)?)\s+/i;
|
|
41396
|
+
if (!bullet && !negative.test(clause))
|
|
41397
|
+
continue;
|
|
41398
|
+
const items = clause.replace(negative, "").split(/,\s*|\s+(?:and|or)\s+/i);
|
|
41399
|
+
if (items.some((item) => prohibition.test(item.trim())))
|
|
41400
|
+
return false;
|
|
41401
|
+
}
|
|
41700
41402
|
}
|
|
41701
|
-
|
|
41403
|
+
const assigned = brief.spec.match(/^## Assigned files\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/mi)?.[1];
|
|
41404
|
+
if (assigned !== void 0 && (!assigned.trim() || /^(?:\s*[-*]?\s*)(?:\(?none\)?|no assigned files)\b/i.test(assigned)))
|
|
41702
41405
|
return false;
|
|
41703
|
-
|
|
41704
|
-
if (/^mcp__[^_].*__[^_]/.test(title))
|
|
41705
|
-
return true;
|
|
41706
|
-
if (/^mcp\.[^.]+\.[^.]/.test(title))
|
|
41707
|
-
return true;
|
|
41708
|
-
if (resolveMountedMcpToolCall(request, mountedServers))
|
|
41709
|
-
return true;
|
|
41710
|
-
return isReadOnlyMcpPermissionRequest(request) || isBeelineAgentMcpPermissionRequest(request);
|
|
41406
|
+
return true;
|
|
41711
41407
|
}
|
|
41712
|
-
var
|
|
41713
|
-
|
|
41714
|
-
|
|
41715
|
-
|
|
41716
|
-
|
|
41717
|
-
|
|
41718
|
-
|
|
41719
|
-
|
|
41720
|
-
|
|
41721
|
-
|
|
41722
|
-
|
|
41723
|
-
|
|
41724
|
-
|
|
41725
|
-
|
|
41726
|
-
|
|
41727
|
-
|
|
41408
|
+
var CORNER_RUNTIME_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, criteria, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
|
|
41409
|
+
Meet every story; respect every non-goal and risk.
|
|
41410
|
+
Before any code or build action, the brief must hold this ask's outline; opening and prompting inside it is not that outline. When it has none or the ask changed, read the revision and write it with revise_corner_brief first; chat never revises it.
|
|
41411
|
+
Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
|
|
41412
|
+
Reproduce as triage isolated it with available emulator, Playwright, browser and test runner. Record attempts and observations. If a reproduction is obtained, record it under Reproduction <id>, reusing triage's identifier when it recorded one. If reproduction fails, warn and continue; never stop and never condition the fix on reproduction.
|
|
41413
|
+
Fix only the spec and approval quote. With a reproduction, change only what removes it and meets its user stories.
|
|
41414
|
+
Deliver files with post_artifact; answer plain chat questions directly.
|
|
41415
|
+
No unrequested features, flags, compatibility shims or refactors.`;
|
|
41416
|
+
var CORNER_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, criteria, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
|
|
41417
|
+
Meet every story; respect every non-goal and risk. Use its file manifest.
|
|
41418
|
+
Before any code or build action, the brief must hold this ask's outline; opening and prompting inside it is not that outline. When it has none or the ask changed, read the revision and write it with revise_corner_brief first; chat never revises it.
|
|
41419
|
+
Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
|
|
41420
|
+
Reproduce as triage isolated it with available emulator, Playwright, browser and test runner. Record attempts and observations. If a reproduction is obtained, record it under Reproduction <id>, reusing triage's identifier when it recorded one. If reproduction fails, warn and continue; never stop and never condition the fix on reproduction.
|
|
41421
|
+
Fix only the spec and approval quote. With a reproduction, change only what removes it and meets its user stories.
|
|
41422
|
+
Only when the brief calls for repository changes, follow the PR procedure below. Otherwise do not commit, push or open a PR; deliver with post_artifact.
|
|
41423
|
+
Before the PR, demonstrate Y: run the built app or affected service and perform X. If no interactive surface is reachable, run the narrowest test or script exercising the exact user path and printing Y. An inner-function unit test, log line or code read is not a demonstration.
|
|
41424
|
+
The PR body MUST have ## Reproduced and ## Demonstrated. Cite Reproduction <id> in both, with steps, the wrong result and the now-passing result. In ## Demonstrated, when none was obtained, state that plainly ("not obtained") and show the regression; for feature work say "not a defect report".
|
|
41425
|
+
No unrequested features, flags, compatibility shims or refactors.`;
|
|
41426
|
+
var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. A review turn prompt names the latest stable green PR head. Review and approve only that exact head; if no stable green head is named, end the turn without a verdict. Never merge a pull request you review.";
|
|
41427
|
+
var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "There is no stable green PR head for this active reviewer turn. Do not review or call approve_merge. End this turn without a verdict; the next green transition will wake you.";
|
|
41428
|
+
var CORNER_DELIVERY_NUDGE = "Only when you write code in this corner and the brief calls for repository changes, inspect the repository state and finish delivering the work unless a human hold stands: commit and push the intended changes and open the pull request if one does not exist. Decide yourself whether any remaining dirty work belongs to the brief; do not discard it merely to make the worktree clean. The pull request body must carry ## Reproduced and ## Demonstrated; if they are missing, add them before ending the turn. Otherwise do not commit, push or open a PR; deliver with post_artifact.";
|
|
41429
|
+
var CORNER_LAND_RULE = "Then, if a human's question or proposal in the corner has no answer, name it and do not merge; otherwise call merge_corner.";
|
|
41430
|
+
var CORNER_CHECKS_NUDGE = `Call pr_checks_status now. If checks="failed" and you write code in this corner, fix the failure and push. If checks="unknown", reply in this corner with the tool reason and stop instead of retrying. Otherwise stop: a reviewer's PASS wakes you to merge with merge_corner. Never merge it any other way.`;
|
|
41431
|
+
var handle = (value) => value.replace(/^@/, "");
|
|
41432
|
+
function cornerMergeInstruction(reviewerHandle) {
|
|
41433
|
+
if (!reviewerHandle)
|
|
41434
|
+
return "Once the pull request exists, reply with its full URL and end the turn; do not check or wait for CI. This Room has no configured reviewer, so it merges only on a person's yes: ask a Workspace owner or admin to approve the merge, then record their yes with order_corner_merge. You are woken to fix failing checks or a merge GitHub refused.";
|
|
41435
|
+
const reviewer = `the reviewer (${handle(reviewerHandle)})`;
|
|
41436
|
+
return `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Green checks wake ${reviewer}; its PASS wakes you to merge. ${CORNER_LAND_RULE} You are also woken to fix failing checks, requested changes, or a refused merge; a fixed head needs green checks and a fresh yes.`;
|
|
41437
|
+
}
|
|
41438
|
+
function cornerReviewerInstruction(input) {
|
|
41439
|
+
if (!input.isReviewer)
|
|
41440
|
+
return void 0;
|
|
41441
|
+
const author = input.authorHandle ? handle(input.authorHandle) : void 0;
|
|
41442
|
+
const number = input.pullRequestNumber ?? "N";
|
|
41443
|
+
const headSha = input.headSha ?? "<head sha>";
|
|
41444
|
+
return `Checks are green on PR #${number} at ${headSha}${input.briefRevision ? ` with assigned brief revision ${input.briefRevision}` : ""}. Review it now with the beeline-review skill against that exact head and current assigned brief. FAIL: ${author ? `reply \`@${author}\`` : "tag the agent that wrote this head"} with the confirmed findings and the reviewed head ${headSha} to fix. PASS: call the approve_merge tool for ${headSha}, then reply \`approved ${headSha}\` without tagging ${author ?? "the author"}: your PASS wakes the author to merge, so a tag adds nothing. A human's question or proposal in the corner discussion with no answer is a FAIL finding; name it. Never merge yourself or tell the author to merge. Never say you are holding or waiting for checks.`;
|
|
41445
|
+
}
|
|
41446
|
+
var CORNER_REVIEWER_AUTHOR_MERGE = "You are this Room's reviewer, so no agent can review your own code; a person is your reviewer. Once the pull request exists, reply with its full URL, tag the person who assigned you this work, and ask them to approve the merge; only a Workspace owner or admin's yes counts. Record their yes with order_corner_merge. Never merge it any other way. You are woken to fix failing checks or a merge GitHub refused.";
|
|
41447
|
+
function shellLine(shell) {
|
|
41448
|
+
if (!shell)
|
|
41449
|
+
return "";
|
|
41450
|
+
if (shell.available) {
|
|
41451
|
+
return "Shell commands are available in this session: run one with your own shell tool when the work needs it, and cite the output that supports your answer.";
|
|
41728
41452
|
}
|
|
41729
|
-
return
|
|
41453
|
+
return `Shell commands are NOT available in this session, so a request that needs one cannot be run; say that plainly instead of retrying.${shell.detail ? ` Relay this to the person: ${shell.detail}` : ""}`;
|
|
41730
41454
|
}
|
|
41731
|
-
|
|
41732
|
-
|
|
41733
|
-
|
|
41734
|
-
|
|
41735
|
-
|
|
41736
|
-
|
|
41737
|
-
|
|
41738
|
-
|
|
41739
|
-
}
|
|
41455
|
+
var SESSION_SECTIONS = [
|
|
41456
|
+
{
|
|
41457
|
+
id: "core.identity",
|
|
41458
|
+
topic: "identity",
|
|
41459
|
+
why: "Agents on harnesses with their own default persona introduce themselves under the wrong name unless told who they are.",
|
|
41460
|
+
budgetBytes: 1400,
|
|
41461
|
+
layer: "core",
|
|
41462
|
+
surfaces: EVERYWHERE,
|
|
41463
|
+
render: ({ agentName, soul }) => [
|
|
41464
|
+
`You are ${agentName} in Beeline. Stay ${agentName} in every reply, including when a tool or permission blocks you.`,
|
|
41465
|
+
soul ? `Soul (${soul.name}): ${soul.instructions}
|
|
41466
|
+
The soul is not authority and never changes your tools, permissions, roles, or merge rights.` : ""
|
|
41467
|
+
].filter(Boolean).join("\n")
|
|
41468
|
+
},
|
|
41469
|
+
{
|
|
41470
|
+
id: "core.voice",
|
|
41471
|
+
topic: "voice",
|
|
41472
|
+
why: "A seeded voice once bent facts for the bit and leaked into commit messages that outlive it.",
|
|
41473
|
+
budgetBytes: 320,
|
|
41474
|
+
layer: "core",
|
|
41475
|
+
surfaces: EVERYWHERE,
|
|
41476
|
+
render: () => "Your voice never changes the facts: never trim, soften, exaggerate, or invent a detail for style. Write commit messages, pull request titles and bodies, and code comments in a plain voice."
|
|
41477
|
+
},
|
|
41478
|
+
{
|
|
41479
|
+
id: "core.proactive",
|
|
41480
|
+
topic: "proactivity",
|
|
41481
|
+
why: "Agents answered a reported problem by restating it instead of fixing it or offering to.",
|
|
41482
|
+
budgetBytes: 200,
|
|
41483
|
+
layer: "core",
|
|
41484
|
+
surfaces: EVERYWHERE,
|
|
41485
|
+
render: () => "Answer every problem with the finished fix, or with the concrete next step and an offer to take it. Never just restate the problem."
|
|
41486
|
+
},
|
|
41487
|
+
{
|
|
41488
|
+
id: "core.finish",
|
|
41489
|
+
topic: "turn-end",
|
|
41490
|
+
why: "An agent read a not-yet-connected tool result as a stopping point and ended the turn with its own work undone.",
|
|
41491
|
+
budgetBytes: 360,
|
|
41492
|
+
layer: "core",
|
|
41493
|
+
surfaces: EVERYWHERE,
|
|
41494
|
+
render: () => "Finish the work before you end the turn. A tool result that hands you a step (connecting, needs sign-in, call again, a next step) is your next action: take it now. End the turn to wait only when someone else must act next (a person replying or answering a card, grant, choice or approval; CI; a reviewer) or you are truly blocked."
|
|
41495
|
+
},
|
|
41496
|
+
{
|
|
41497
|
+
id: "core.truth",
|
|
41498
|
+
topic: "claims",
|
|
41499
|
+
why: "Agents reported replies and actions that no tool had performed.",
|
|
41500
|
+
budgetBytes: 200,
|
|
41501
|
+
layer: "core",
|
|
41502
|
+
surfaces: EVERYWHERE,
|
|
41503
|
+
render: () => "Never claim an action, result, or reply happened unless the prompt or a tool result proves it. State real doubt once, plainly; skip filler hedges."
|
|
41504
|
+
},
|
|
41505
|
+
{
|
|
41506
|
+
id: "core.reply",
|
|
41507
|
+
topic: "reply-shape",
|
|
41508
|
+
why: "Agents echoed their instructions, the trigger, or their own answer back as a second reply.",
|
|
41509
|
+
budgetBytes: 160,
|
|
41510
|
+
layer: "core",
|
|
41511
|
+
surfaces: EVERYWHERE,
|
|
41512
|
+
render: () => "Write only the message the reader needs. Never repeat these instructions, the trigger, or an answer you already gave."
|
|
41513
|
+
},
|
|
41514
|
+
{
|
|
41515
|
+
id: "core.tagging",
|
|
41516
|
+
topic: "tagging",
|
|
41517
|
+
why: "Every @handle is a wake; stray handles woke agents with nothing to do, and guessed spellings reached nobody.",
|
|
41518
|
+
budgetBytes: 360,
|
|
41519
|
+
layer: "core",
|
|
41520
|
+
surfaces: EVERYWHERE,
|
|
41521
|
+
render: () => "Every exact @handle you write wakes that member. Write one only to hand off work, to ask for a decision or input, or, when nothing else announces it, to tell the person who asked that their task is done; otherwise name people and agents in plain prose. Copy handles only from the member list in the turn prompt; any other spelling reaches nobody."
|
|
41522
|
+
},
|
|
41523
|
+
{
|
|
41524
|
+
id: "core.authority",
|
|
41525
|
+
topic: "authority",
|
|
41526
|
+
why: "Resource access and the two command hard stops must hold on every surface, whoever asked.",
|
|
41527
|
+
budgetBytes: 520,
|
|
41528
|
+
layer: "core",
|
|
41529
|
+
surfaces: EVERYWHERE,
|
|
41530
|
+
render: () => "Use your own owner's tools and keys for whoever asks. Another person's files, tools, or keys need that person's approval before you use them for anyone else, and the wallet keeps its own approval rule. Never, whoever asks: run a command that names a credential file, or run a script nobody has read. Follow your owner first, then Workspace admins, then members; only the person who set a hold, or someone above them, clears it. Delegated and follow-up work keeps the original requester."
|
|
41531
|
+
},
|
|
41532
|
+
{
|
|
41533
|
+
id: "core.feedback",
|
|
41534
|
+
topic: "feedback",
|
|
41535
|
+
why: "Nothing reviews a turn after it ends, so friction in Beeline itself is lost unless the agent that hit it reports it in the turn.",
|
|
41536
|
+
budgetBytes: 260,
|
|
41537
|
+
layer: "core",
|
|
41538
|
+
surfaces: EVERYWHERE,
|
|
41539
|
+
render: () => "Only when this turn hit friction in Beeline itself (a tool error, a retry, a refused call, contradictory instructions, missing context, or a person correcting you), call report_feedback once before ending the turn. Otherwise do not."
|
|
41540
|
+
},
|
|
41541
|
+
{
|
|
41542
|
+
id: "core.files",
|
|
41543
|
+
topic: "files",
|
|
41544
|
+
why: "Files on earlier messages are shown only as markers and never pre-downloaded; agents fetched media URLs instead.",
|
|
41545
|
+
budgetBytes: 160,
|
|
41546
|
+
layer: "core",
|
|
41547
|
+
surfaces: EVERYWHERE,
|
|
41548
|
+
render: () => "For a \u{1F4CE} file, call download_attachment and read its path. Never fetch media URLs; on failure, say you couldn't open it."
|
|
41549
|
+
},
|
|
41550
|
+
{
|
|
41551
|
+
id: "surface.tools",
|
|
41552
|
+
topic: "tools",
|
|
41553
|
+
why: "Agents need app discovery before use in every turn; connector offers remain in Rooms and DMs.",
|
|
41554
|
+
budgetBytes: 240,
|
|
41555
|
+
layer: "surface",
|
|
41556
|
+
surfaces: EVERYWHERE,
|
|
41557
|
+
render: ({ surface }) => surface === "room" || surface === "dm" ? "When you need a tool you do not have, call workbench_status; offer_connector the tool it lists, otherwise call connect_app for the app." : "In corners, workbench_status discovers apps; connect_app starts sign-in here. Connector offers stay in Rooms and DMs."
|
|
41558
|
+
},
|
|
41559
|
+
{
|
|
41560
|
+
id: "surface.squire-drive",
|
|
41561
|
+
topic: "squire-browser",
|
|
41562
|
+
why: "Agents rarely called operate_drive and instead hand-drove checkouts/signups one flat step at a time, including after a drive handback (ts-drive-adoption-scout).",
|
|
41563
|
+
budgetBytes: 420,
|
|
41564
|
+
layer: "surface",
|
|
41565
|
+
surfaces: EVERYWHERE,
|
|
41566
|
+
render: () => "For a multi-step browser goal (signup, checkout, provisioning), start with Trusty Squire operate_drive {url, goal, facts} rather than individual steps. On a low_confidence or needs_value handback, answer it from the offered options or take one flat step (operate_click, operate_type, operate_select, \u2026), then resume operate_drive on that same session instead of finishing the task by hand."
|
|
41567
|
+
},
|
|
41568
|
+
// --- Room ----------------------------------------------------------------
|
|
41569
|
+
{
|
|
41570
|
+
id: "room.place",
|
|
41571
|
+
topic: "place",
|
|
41572
|
+
why: "The agent must know it cannot write here, where the checkout is, and whether it has a shell.",
|
|
41573
|
+
budgetBytes: 700,
|
|
41574
|
+
layer: "surface",
|
|
41575
|
+
surfaces: ["room"],
|
|
41576
|
+
render: ({ repository, shell }) => [
|
|
41577
|
+
`This is a Beeline Room. The repository filesystem is read-only here.${repository ? ` The Room is bound to ${repository.name} (branch ${repository.branch}); the read-only checkout is at the session root.` : ""} Web search is available.`,
|
|
41578
|
+
shellLine(shell),
|
|
41579
|
+
shell?.available === false ? "Read code with CodeGraph when available, then beeline-readonly-mcp search_text and read_file." : "If a shell command is refused, say so plainly rather than retrying, and read code with CodeGraph when available, then beeline-readonly-mcp search_text and read_file."
|
|
41580
|
+
].filter(Boolean).join(" ")
|
|
41581
|
+
},
|
|
41582
|
+
{
|
|
41583
|
+
id: "room.corners",
|
|
41584
|
+
topic: "repository-work",
|
|
41585
|
+
why: "Repository work must start through the host-governed corner, with a brief as its authority.",
|
|
41586
|
+
budgetBytes: 1100,
|
|
41587
|
+
layer: "surface",
|
|
41588
|
+
surfaces: ["room"],
|
|
41589
|
+
render: () => [
|
|
41590
|
+
"Repository changes happen only in a corner. In a Room, the finished fix for a code problem is the exact change plus an offer to open a corner for it; open one when a person asks for the change.",
|
|
41591
|
+
"Before opening one, consult beeline-triage and beeline-spec, then call open_corner with a name of at most three words joined by hyphens (no whitespace), an objective of at most 24 words, and the brief (a spec plus the approving message). The objective only titles the work; the brief is its authority. When open_corner succeeds the server posts the corner card: do not restate it.",
|
|
41592
|
+
"For corners you belong to, use inspect_corner for status, steer_corner to pass Room input down, and ask_corner for one answer. Never post into a corner without a Room command.",
|
|
41593
|
+
"A corner ends when its pull request merges; since a person may merge minutes after it opens, anything still needed must already live in a different corner before that pull request opens, not after. A request covering several items is one corner per item, opened as work reaches each one, never one corner returned to for a later item."
|
|
41594
|
+
].join(" ")
|
|
41595
|
+
},
|
|
41596
|
+
{
|
|
41597
|
+
id: "room.replying",
|
|
41598
|
+
topic: "room-replies",
|
|
41599
|
+
why: "Several agents share a Room; replies with nothing to act on, and echoed nudges, are noise.",
|
|
41600
|
+
budgetBytes: 520,
|
|
41601
|
+
layer: "surface",
|
|
41602
|
+
surfaces: ["room"],
|
|
41603
|
+
render: () => "If nothing is actionable for you, do not reply. If the current task is only a nudge to respond, answer the most recent unanswered human message instead of echoing the nudge. A poll plurality is a fact, never permission to deploy, delete, merge, or spend. Consult the using-beeline skill for Room mechanics (files, artifacts, schedules, reactions, events, mocks)."
|
|
41604
|
+
},
|
|
41605
|
+
// --- DM ------------------------------------------------------------------
|
|
41606
|
+
{
|
|
41607
|
+
id: "dm.place",
|
|
41608
|
+
topic: "place",
|
|
41609
|
+
why: "A DM has one person, no repository work, and no corners; the agent must not tag or try to open one.",
|
|
41610
|
+
budgetBytes: 700,
|
|
41611
|
+
layer: "surface",
|
|
41612
|
+
surfaces: ["dm"],
|
|
41613
|
+
render: ({ shell }) => [
|
|
41614
|
+
"This is a private direct message with one person: everything they send is for you, so reply without tagging. It has no repository work and no corners, and the filesystem is read-only. Web search is available.",
|
|
41615
|
+
shellLine(shell)
|
|
41616
|
+
].filter(Boolean).join(" ")
|
|
41617
|
+
},
|
|
41618
|
+
{
|
|
41619
|
+
id: "dm.choices",
|
|
41620
|
+
topic: "choices",
|
|
41621
|
+
why: "A pick in a DM is a preference, never authority, and polls need an electorate.",
|
|
41622
|
+
budgetBytes: 300,
|
|
41623
|
+
layer: "surface",
|
|
41624
|
+
surfaces: ["dm"],
|
|
41625
|
+
render: () => "ask_choice offers options; a pick is a preference, never sandbox, spend, or merge authority. Consult the using-beeline skill for files, artifacts, and reactions."
|
|
41626
|
+
},
|
|
41627
|
+
// --- Corners -------------------------------------------------------------
|
|
41628
|
+
{
|
|
41629
|
+
id: "corner.steering",
|
|
41630
|
+
topic: "corner-steering",
|
|
41631
|
+
why: "Active corner turns can steer member siblings without borrowing parent Room authority.",
|
|
41632
|
+
budgetBytes: 160,
|
|
41633
|
+
layer: "surface",
|
|
41634
|
+
surfaces: ["code-corner", "repo-less-corner"],
|
|
41635
|
+
render: () => "Use steer_corner during a turn to send input to a member sibling under this parent. Membership alone grants no turn."
|
|
41636
|
+
},
|
|
41637
|
+
{
|
|
41638
|
+
id: "corner.handover",
|
|
41639
|
+
topic: "corner-handover",
|
|
41640
|
+
why: "An agent asked to hand the corner on opened a duplicate sibling corner instead of tagging the other agent.",
|
|
41641
|
+
budgetBytes: 360,
|
|
41642
|
+
layer: "surface",
|
|
41643
|
+
surfaces: ["code-corner", "repo-less-corner"],
|
|
41644
|
+
render: () => "When a person asks you to hand this corner to another agent, tag in your reply only an agent that person named by handle or display name: it moves the implementer role here. A tag for any other reason does not move the role. Do not open a second corner for a hand-over."
|
|
41645
|
+
},
|
|
41646
|
+
{
|
|
41647
|
+
id: "corner.worktree",
|
|
41648
|
+
topic: "place",
|
|
41649
|
+
why: "Every agent in a code corner must know its branch and target, whether or not it writes code.",
|
|
41650
|
+
budgetBytes: 200,
|
|
41651
|
+
layer: "surface",
|
|
41652
|
+
surfaces: ["code-corner"],
|
|
41653
|
+
render: ({ worktree }) => `You are in an isolated git worktree on ${worktree?.featureBranch ?? "the feature branch"}, targeting ${worktree?.targetBranch ?? "the target branch"}.`
|
|
41654
|
+
},
|
|
41655
|
+
{
|
|
41656
|
+
id: "corner.android",
|
|
41657
|
+
topic: "android",
|
|
41658
|
+
why: "An agent under its isolated $HOME concluded the host had no emulator, and agents on the shared adb server drove each other's emulators.",
|
|
41659
|
+
budgetBytes: 380,
|
|
41660
|
+
layer: "surface",
|
|
41661
|
+
surfaces: ["code-corner"],
|
|
41662
|
+
when: ({ android }) => Boolean(android),
|
|
41663
|
+
render: ({ android }) => `Android: adb and emulator are on PATH; the SDK is $ANDROID_HOME, AVDs are in $ANDROID_AVD_HOME, and /dev/kvm works. Your adb server sees only your own emulator. Boot it with \`emulator -avd <name> -port ${android?.emulatorPort} -read-only -no-window -no-snapshot -gpu swiftshader_indirect\`; it reaches host services at 10.0.2.2.`
|
|
41664
|
+
},
|
|
41665
|
+
{
|
|
41666
|
+
id: "corner.implementer",
|
|
41667
|
+
topic: "implementer-condition",
|
|
41668
|
+
why: "A reviewer that opened its corner was given push steps and pushed a fix (PR #2224); only the agent knows whether it was told to write code, so the author rules are conditional.",
|
|
41669
|
+
budgetBytes: 400,
|
|
41670
|
+
layer: "surface",
|
|
41671
|
+
surfaces: ["code-corner"],
|
|
41672
|
+
render: ({ reviewer }) => `Implementer rules: the rules below${reviewer ? ", up to the reviewer rules," : ""} apply only when the brief or a person in this corner told you to write code or do this corner's work. A hand-over of the work to another agent ends that. Otherwise do not commit, push, or open a pull request.`
|
|
41673
|
+
},
|
|
41674
|
+
{
|
|
41675
|
+
id: "corner.prepare",
|
|
41676
|
+
topic: "corner-prepare",
|
|
41677
|
+
why: "Only a checkout that defines the command can run it; another repository fails on its first step.",
|
|
41678
|
+
budgetBytes: 60,
|
|
41679
|
+
layer: "surface",
|
|
41680
|
+
surfaces: ["code-corner"],
|
|
41681
|
+
when: ({ brief, cornerPrepareScript }) => cornerHasRepositoryWork(brief) && cornerPrepareScript === true,
|
|
41682
|
+
render: () => "Run npm run corner:prepare first."
|
|
41683
|
+
},
|
|
41684
|
+
{
|
|
41685
|
+
id: "corner.push",
|
|
41686
|
+
topic: "push",
|
|
41687
|
+
why: "The author must push only its own branch and rebase before each push, or it overwrites or loses work.",
|
|
41688
|
+
budgetBytes: 800,
|
|
41689
|
+
layer: "surface",
|
|
41690
|
+
surfaces: ["code-corner"],
|
|
41691
|
+
when: ({ brief }) => cornerHasRepositoryWork(brief),
|
|
41692
|
+
render: ({ worktree, githubCli }) => githubCli === "rest" ? `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, run \`git ls-remote --exit-code origin ${worktree?.featureBranch}\`. Exit code 0: rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Exit code 2: the branch is absent; skip rebase for the first push. Any other non-zero exit is a lookup failure: stop and retry; do not skip rebase or push. Open it with \`gh pr create\` and read it with \`gh pr view\`; this host has no gh, so the Beeline launcher answers both over the GitHub REST API with the app token.` : `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, run \`git ls-remote --exit-code origin ${worktree?.featureBranch}\`. Exit code 0: rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Exit code 2: the branch is absent; skip rebase for the first push. Any other non-zero exit is a lookup failure: stop and retry; do not skip rebase or push. Open the pull request with gh.`
|
|
41693
|
+
},
|
|
41694
|
+
{
|
|
41695
|
+
id: "corner.contract",
|
|
41696
|
+
topic: "author-contract",
|
|
41697
|
+
why: "The reviewer enforces this contract; an author that never saw it fails review.",
|
|
41698
|
+
budgetBytes: 2600,
|
|
41699
|
+
layer: "surface",
|
|
41700
|
+
surfaces: ["code-corner"],
|
|
41701
|
+
render: ({ brief }) => cornerHasRepositoryWork(brief) ? CORNER_AUTHOR_CONTRACT : CORNER_RUNTIME_AUTHOR_CONTRACT
|
|
41702
|
+
},
|
|
41703
|
+
{
|
|
41704
|
+
id: "corner.checks",
|
|
41705
|
+
topic: "checks-turns",
|
|
41706
|
+
why: "Server check notes were restated as replies, and agents scheduled polls of a gate that wakes them itself.",
|
|
41707
|
+
budgetBytes: 900,
|
|
41708
|
+
layer: "surface",
|
|
41709
|
+
surfaces: ["code-corner"],
|
|
41710
|
+
when: ({ brief }) => cornerHasRepositoryWork(brief),
|
|
41711
|
+
render: () => "When asked whether the reviewer was woken, call pr_checks_status and report reviewerWake; do not invent a cause. Never schedule polls of pr_checks_status or the merge gate; the server wakes you when it changes."
|
|
41712
|
+
},
|
|
41713
|
+
{
|
|
41714
|
+
id: "corner.merge",
|
|
41715
|
+
topic: "merge",
|
|
41716
|
+
why: 'Exactly one rule may say who merges and when; two rules here once said both "merge" and "never merge".',
|
|
41717
|
+
budgetBytes: 520,
|
|
41718
|
+
layer: "surface",
|
|
41719
|
+
surfaces: ["code-corner"],
|
|
41720
|
+
when: ({ brief }) => cornerHasRepositoryWork(brief),
|
|
41721
|
+
render: ({ reviewerHandle, reviewer }) => (reviewer ? CORNER_REVIEWER_AUTHOR_MERGE : cornerMergeInstruction(reviewerHandle)) + " Never merge any other pull request."
|
|
41722
|
+
},
|
|
41723
|
+
{
|
|
41724
|
+
id: "corner.review",
|
|
41725
|
+
topic: "review",
|
|
41726
|
+
why: "The reviewer approves one exact green head, never merges it, and returns defects instead of pushing them.",
|
|
41727
|
+
budgetBytes: 450,
|
|
41728
|
+
layer: "surface",
|
|
41729
|
+
surfaces: ["code-corner"],
|
|
41730
|
+
when: ({ reviewer }) => reviewer === true,
|
|
41731
|
+
render: () => `Reviewer rules: ${CORNER_REVIEWER_SESSION_INSTRUCTION} Report findings to the author; never edit or push another agent's branch.`
|
|
41732
|
+
},
|
|
41733
|
+
{
|
|
41734
|
+
id: "corner.repo-less",
|
|
41735
|
+
topic: "place",
|
|
41736
|
+
why: "A repo-less corner has no repository; the agent delivers files, not commits.",
|
|
41737
|
+
budgetBytes: 1e3,
|
|
41738
|
+
layer: "surface",
|
|
41739
|
+
surfaces: ["repo-less-corner"],
|
|
41740
|
+
render: () => [
|
|
41741
|
+
"This is a repo-less corner with no repository checkout and no GitHub workflow. Work in this corner's writable workspace: create files with write_scratch_file or your own tools, then send them with post_artifact.",
|
|
41742
|
+
"Do not initialize a repository, create a branch, commit, push, open a pull request, or wait for GitHub checks."
|
|
41743
|
+
].join("\n")
|
|
41744
|
+
},
|
|
41745
|
+
{
|
|
41746
|
+
id: "corner.repo-less-reply",
|
|
41747
|
+
topic: "reply-target",
|
|
41748
|
+
why: "Every turn was told to end with a one-line summary of files it posted, so answers were followed by a restatement of themselves.",
|
|
41749
|
+
budgetBytes: 260,
|
|
41750
|
+
layer: "surface",
|
|
41751
|
+
surfaces: ["repo-less-corner"],
|
|
41752
|
+
render: ({ requesterHandle }) => `${requesterHandle ? `Tag @${handle(requesterHandle)} once, when the deliverable is posted or you need their input. ` : ""}When you posted files, name them in one line; never restate your answer. Only a human closes this corner.`
|
|
41740
41753
|
}
|
|
41741
|
-
|
|
41742
|
-
|
|
41743
|
-
|
|
41744
|
-
return AGENT_SURFACE_TOOL_NAMES.flatMap((tool) => [
|
|
41745
|
-
`mcp__${BEELINE_AGENT_MCP_SERVER_NAME}__${tool}`,
|
|
41746
|
-
`mcp__${normalized}__${tool}`,
|
|
41747
|
-
`mcp.${BEELINE_AGENT_MCP_SERVER_NAME}.${tool}`
|
|
41748
|
-
]).includes(title);
|
|
41749
|
-
}
|
|
41750
|
-
function isMcpShapedPermissionRequest(request, hostServers = CODE_OWNED_HOST_MCP_NAMES) {
|
|
41751
|
-
if (isHostMcpPermissionRequest(request, hostServers))
|
|
41752
|
-
return true;
|
|
41753
|
-
const record5 = permissionRecord(request.toolCall?.rawInput);
|
|
41754
|
-
if (typeof record5?.server === "string")
|
|
41755
|
-
return true;
|
|
41756
|
-
if (DISPATCHER_TOOL_NAME_KEYS.some((key) => typeof record5?.[key] === "string"))
|
|
41757
|
-
return true;
|
|
41758
|
-
const title = request.toolCall?.title?.trim() ?? "";
|
|
41759
|
-
return /^mcp__[^_].*__[^_]/.test(title) || /^mcp\.[^.]+\.[^.]/.test(title);
|
|
41754
|
+
];
|
|
41755
|
+
function applicable(sections, context) {
|
|
41756
|
+
return sections.filter((section) => section.surfaces.includes(context.surface) && (section.when?.(context) ?? true)).map((section) => ({ section, text: section.render(context).trim() })).filter((entry) => entry.text);
|
|
41760
41757
|
}
|
|
41761
|
-
|
|
41762
|
-
|
|
41763
|
-
var ReadOnlyToolsUnavailableError = class extends Error {
|
|
41764
|
-
name = "ReadOnlyToolsUnavailableError";
|
|
41765
|
-
};
|
|
41766
|
-
function beelineAgentMcpServer(config, api, context) {
|
|
41767
|
-
if (!config.readonlyMcpCommand) {
|
|
41768
|
-
throw new ReadOnlyToolsUnavailableError("agent tools unavailable: the Beeline MCP command is required");
|
|
41769
|
-
}
|
|
41770
|
-
const connection = api.connection();
|
|
41771
|
-
return {
|
|
41772
|
-
name: BEELINE_AGENT_MCP_SERVER_NAME,
|
|
41773
|
-
command: config.readonlyMcpCommand,
|
|
41774
|
-
args: [...config.readonlyMcpArgs ?? []],
|
|
41775
|
-
env: [
|
|
41776
|
-
{ name: "BEELINE_MCP_SURFACE", value: "agent" },
|
|
41777
|
-
...context.turnContextPath ? [{ name: "BEELINE_TURN_CONTEXT_FILE", value: context.turnContextPath }] : [],
|
|
41778
|
-
...context.directMessage ? [{ name: "BEELINE_AGENT_DM", value: "1" }] : [],
|
|
41779
|
-
...context.squireRelay ? [
|
|
41780
|
-
{ name: "BEELINE_SQUIRE_RELAY_URL", value: context.squireRelay.url },
|
|
41781
|
-
{ name: "BEELINE_SQUIRE_RELAY_TOKEN", value: context.squireRelay.token }
|
|
41782
|
-
] : [],
|
|
41783
|
-
{ name: "BEELINE_DAEMON_BASE_URL", value: connection.baseUrl },
|
|
41784
|
-
{ name: "BEELINE_DAEMON_TOKEN", value: connection.daemonToken },
|
|
41785
|
-
{ name: "BEELINE_HELPER_VERSION", value: connection.helperVersion },
|
|
41786
|
-
{ name: "BEELINE_DAEMON_AGENT_ID", value: connection.agentId },
|
|
41787
|
-
{ name: "BEELINE_OPERATOR_HOME", value: config.operatorHome ?? homedir16() },
|
|
41788
|
-
{ name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
|
|
41789
|
-
{ name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
|
|
41790
|
-
...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
|
|
41791
|
-
...context.agentMayCloseCorner ? [{ name: "BEELINE_CORNER_AGENT_CLOSE", value: "1" }] : [],
|
|
41792
|
-
...context.agentMayUpgradeCorner ? [{ name: "BEELINE_CORNER_CAN_UPGRADE", value: "1" }] : [],
|
|
41793
|
-
...context.reviewer ? [{ name: "BEELINE_CORNER_REVIEWER", value: "1" }] : [],
|
|
41794
|
-
...context.cornerId && context.lane ? [{ name: "BEELINE_CORNER_LANE", value: context.lane }] : [],
|
|
41795
|
-
...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
|
|
41796
|
-
...context.attachScratchRoot ? [{ name: "BEELINE_ATTACH_SCRATCH_ROOT", value: context.attachScratchRoot }] : [],
|
|
41797
|
-
...context.grantRunner ? [
|
|
41798
|
-
{ name: "BEELINE_GRANT_RUNNER_URL", value: context.grantRunner.url },
|
|
41799
|
-
{ name: "BEELINE_GRANT_RUNNER_TOKEN", value: context.grantRunner.token }
|
|
41800
|
-
] : []
|
|
41801
|
-
]
|
|
41802
|
-
};
|
|
41758
|
+
function sessionSections(context) {
|
|
41759
|
+
return applicable(SESSION_SECTIONS, context);
|
|
41803
41760
|
}
|
|
41804
|
-
function
|
|
41805
|
-
|
|
41806
|
-
|
|
41807
|
-
}
|
|
41808
|
-
const skillDir = agentSkillDir(config.agentKind);
|
|
41761
|
+
function assembleSessionPrompt(context) {
|
|
41762
|
+
const entries = sessionSections(context);
|
|
41763
|
+
const systemPrompt = entries.map((entry) => entry.text).join("\n\n");
|
|
41809
41764
|
return {
|
|
41810
|
-
|
|
41811
|
-
|
|
41812
|
-
|
|
41813
|
-
|
|
41814
|
-
|
|
41815
|
-
|
|
41816
|
-
{
|
|
41817
|
-
name: "BEELINE_READONLY_AGENT_SKILLS_ROOT",
|
|
41818
|
-
value: resolve32(config.agentHomeRoot, skillDir, "skills")
|
|
41819
|
-
}
|
|
41820
|
-
] : [],
|
|
41821
|
-
...agentMemoryDir ? [{ name: "BEELINE_READONLY_AGENT_MEMORY_ROOT", value: resolve32(agentMemoryDir) }] : []
|
|
41822
|
-
]
|
|
41765
|
+
systemPrompt,
|
|
41766
|
+
turnPrefix: harnessHonorsSessionSystemPrompt(context.agentCommand) ? "" : systemPrompt,
|
|
41767
|
+
report: entries.map(({ section, text: text2 }) => ({
|
|
41768
|
+
id: section.id,
|
|
41769
|
+
bytes: Buffer.byteLength(text2)
|
|
41770
|
+
}))
|
|
41823
41771
|
};
|
|
41824
41772
|
}
|
|
41773
|
+
function renderAssignedCornerBrief(brief) {
|
|
41774
|
+
const approval = brief.approval ? `Approved by ${brief.approval.approverName}, message ${brief.approval.sourceMessageId}: ${brief.approval.text}` : "This revision predates recorded approvals.";
|
|
41775
|
+
return `Brief revision ${brief.revision}:
|
|
41776
|
+
${brief.spec}
|
|
41825
41777
|
|
|
41826
|
-
|
|
41827
|
-
import { performance as performance2 } from "node:perf_hooks";
|
|
41828
|
-
var INSTITUTIONAL_CONTEXT_TIMEOUT_MS = 500;
|
|
41829
|
-
var INSTITUTIONAL_MEMORY_LIVE_FLAG = "BEELINE_INSTITUTIONAL_MEMORY_ENABLED";
|
|
41830
|
-
function institutionalMemoryFlagEnabled(env, flag) {
|
|
41831
|
-
return env[flag] !== "false";
|
|
41832
|
-
}
|
|
41833
|
-
var EMPTY_INSTITUTIONAL_CONTEXT = {
|
|
41834
|
-
snapshotRevision: 0,
|
|
41835
|
-
text: "",
|
|
41836
|
-
itemIds: [],
|
|
41837
|
-
totalBytes: 0,
|
|
41838
|
-
omitted: {}
|
|
41839
|
-
};
|
|
41840
|
-
var INSTITUTIONAL_CONTEXT_MISSED_TEXT = "institutional memory did not load this turn; use search_memory before concluding anything is not stored.";
|
|
41841
|
-
function startInstitutionalContextFetch(api, roomId, enabled = institutionalMemoryFlagEnabled(process.env, INSTITUTIONAL_MEMORY_LIVE_FLAG), now2 = () => performance2.now()) {
|
|
41842
|
-
const startedAt = now2();
|
|
41843
|
-
const promise = enabled ? api.execute("getInstitutionalContext", { roomId }) : Promise.resolve(EMPTY_INSTITUTIONAL_CONTEXT);
|
|
41844
|
-
promise.catch(() => {
|
|
41845
|
-
});
|
|
41846
|
-
return { promise, startedAt };
|
|
41778
|
+
${approval}`;
|
|
41847
41779
|
}
|
|
41848
|
-
|
|
41849
|
-
const
|
|
41850
|
-
let
|
|
41851
|
-
|
|
41852
|
-
|
|
41853
|
-
|
|
41854
|
-
|
|
41855
|
-
|
|
41856
|
-
|
|
41857
|
-
|
|
41858
|
-
|
|
41859
|
-
|
|
41860
|
-
|
|
41861
|
-
|
|
41862
|
-
return {
|
|
41863
|
-
...EMPTY_INSTITUTIONAL_CONTEXT,
|
|
41864
|
-
text: INSTITUTIONAL_CONTEXT_MISSED_TEXT,
|
|
41865
|
-
outcome: "timed-out"
|
|
41866
|
-
};
|
|
41867
|
-
} finally {
|
|
41868
|
-
if (timer)
|
|
41869
|
-
clearTimeout(timer);
|
|
41780
|
+
function roomMentionDirectory(roster, selfId) {
|
|
41781
|
+
const rows = [];
|
|
41782
|
+
let selfHandle;
|
|
41783
|
+
for (const member of roster.members) {
|
|
41784
|
+
if (member.identityId === selfId) {
|
|
41785
|
+
selfHandle = member.handle?.trim().replace(/^@/, "");
|
|
41786
|
+
continue;
|
|
41787
|
+
}
|
|
41788
|
+
const memberHandle = member.handle?.trim().replace(/^@/, "");
|
|
41789
|
+
if (!memberHandle)
|
|
41790
|
+
continue;
|
|
41791
|
+
const name = member.name?.trim() ?? "";
|
|
41792
|
+
const kind = member.kind === "agent" ? "agent" : "person";
|
|
41793
|
+
rows.push(`- @${memberHandle}${name && name !== memberHandle ? ` \u2014 ${name}` : ""} (${kind})`);
|
|
41870
41794
|
}
|
|
41795
|
+
if (selfHandle)
|
|
41796
|
+
rows.push(`You are @${selfHandle}, a member of this Room; you are not listed above.`);
|
|
41797
|
+
if (!rows.length)
|
|
41798
|
+
return "";
|
|
41799
|
+
return ["Members (exact tag spellings):", ...rows].join("\n");
|
|
41871
41800
|
}
|
|
41801
|
+
var ROOMS = ["room", "dm"];
|
|
41802
|
+
var TURN_SECTIONS = [
|
|
41803
|
+
{
|
|
41804
|
+
id: "turn.session",
|
|
41805
|
+
topic: "session-delivery",
|
|
41806
|
+
why: "Codex, Pi, and Grok drop the ACP session prompt; without this they never saw the corner rules.",
|
|
41807
|
+
budgetBytes: 16e3,
|
|
41808
|
+
layer: "turn",
|
|
41809
|
+
surfaces: EVERYWHERE,
|
|
41810
|
+
render: ({ sessionPrefix }) => sessionPrefix ?? ""
|
|
41811
|
+
},
|
|
41812
|
+
{
|
|
41813
|
+
id: "turn.checks",
|
|
41814
|
+
topic: "checks-turns",
|
|
41815
|
+
why: "Only CI or merge-gate wakes suppress ordinary reporting; user schedules must deliver their task results.",
|
|
41816
|
+
budgetBytes: 350,
|
|
41817
|
+
layer: "turn",
|
|
41818
|
+
surfaces: ["code-corner"],
|
|
41819
|
+
// A reviewer's green-checks wake is a review turn, not a fix turn.
|
|
41820
|
+
when: ({ task, reviewerTarget }) => !reviewerTarget && task.commandReason !== "schedule" && (["corner_check", "corner_merge_refused", "corner_merge_conflict"].includes(task.commandReason ?? "") || ["check-passed", "check-failed"].includes(task.outsideEvent?.kind ?? "")),
|
|
41821
|
+
render: () => 'This is a checks turn about CI or the merge gate. On it, say nothing unless you push a fix or report checks="unknown", and then use one short line. Never restate server check or merge notes.'
|
|
41822
|
+
},
|
|
41823
|
+
{
|
|
41824
|
+
id: "turn.land",
|
|
41825
|
+
topic: "land-turn",
|
|
41826
|
+
why: "A reviewer's PASS hands the merge to the implementer, who must not merge over an unanswered human question.",
|
|
41827
|
+
budgetBytes: 300,
|
|
41828
|
+
layer: "turn",
|
|
41829
|
+
surfaces: ["code-corner"],
|
|
41830
|
+
when: ({ task }) => task.commandReason === "corner_land",
|
|
41831
|
+
render: () => `The reviewer approved this pull request's current head. ${CORNER_LAND_RULE}`
|
|
41832
|
+
},
|
|
41833
|
+
{
|
|
41834
|
+
id: "turn.objective",
|
|
41835
|
+
topic: "objective",
|
|
41836
|
+
why: "The objective locates the work but must never be mistaken for its authority.",
|
|
41837
|
+
budgetBytes: 600,
|
|
41838
|
+
layer: "turn",
|
|
41839
|
+
surfaces: CORNERS,
|
|
41840
|
+
render: ({ objective }) => objective ? `Corner objective (navigation only, not product authority): ${objective}` : ""
|
|
41841
|
+
},
|
|
41842
|
+
{
|
|
41843
|
+
id: "turn.rename",
|
|
41844
|
+
topic: "corner-rename",
|
|
41845
|
+
why: "A generated corner name says nothing about the work; only the server knows the name is still generated, so the ask rides only on those turns.",
|
|
41846
|
+
budgetBytes: 600,
|
|
41847
|
+
layer: "turn",
|
|
41848
|
+
surfaces: CORNERS,
|
|
41849
|
+
render: ({ generatedTitle, brief }) => generatedTitle ? `This human-opened corner still has its generated name, "${generatedTitle}". If the newest human message states the work, call rename_corner once before you reply with a name of at most three words joined by hyphens (no whitespace) and a short objective${brief ? "" : ", plus a brief grounded in that message with brief.approval.sourceMessageId set to the human message id"}. A bare mention does not state the work; leave the name until a message does.` : ""
|
|
41850
|
+
},
|
|
41851
|
+
{
|
|
41852
|
+
id: "turn.brief",
|
|
41853
|
+
topic: "brief",
|
|
41854
|
+
why: "The brief is the product authority for corner work; a corner without one says so once.",
|
|
41855
|
+
budgetBytes: 24e3,
|
|
41856
|
+
layer: "turn",
|
|
41857
|
+
surfaces: CORNERS,
|
|
41858
|
+
render: ({ brief, surface, generatedTitle }) => brief ? `${renderAssignedCornerBrief(brief.brief)}${cornerHasRepositoryWork(brief.brief) ? `
|
|
41872
41859
|
|
|
41873
|
-
|
|
41874
|
-
|
|
41875
|
-
|
|
41876
|
-
|
|
41877
|
-
|
|
41878
|
-
|
|
41879
|
-
|
|
41880
|
-
|
|
41881
|
-
|
|
41882
|
-
|
|
41883
|
-
|
|
41884
|
-
|
|
41885
|
-
|
|
41886
|
-
|
|
41887
|
-
|
|
41888
|
-
|
|
41889
|
-
|
|
41890
|
-
|
|
41891
|
-
|
|
41892
|
-
|
|
41893
|
-
|
|
41894
|
-
|
|
41895
|
-
|
|
41896
|
-
|
|
41897
|
-
|
|
41898
|
-
|
|
41899
|
-
}
|
|
41900
|
-
|
|
41901
|
-
|
|
41902
|
-
}
|
|
41903
|
-
|
|
41904
|
-
|
|
41905
|
-
|
|
41906
|
-
|
|
41907
|
-
|
|
41908
|
-
|
|
41909
|
-
|
|
41910
|
-
|
|
41911
|
-
|
|
41912
|
-
|
|
41913
|
-
|
|
41914
|
-
|
|
41915
|
-
|
|
41916
|
-
|
|
41917
|
-
|
|
41918
|
-
|
|
41919
|
-
})
|
|
41920
|
-
|
|
41921
|
-
|
|
41922
|
-
|
|
41923
|
-
|
|
41924
|
-
|
|
41925
|
-
|
|
41926
|
-
|
|
41927
|
-
|
|
41928
|
-
|
|
41929
|
-
|
|
41930
|
-
|
|
41860
|
+
Assigned files:
|
|
41861
|
+
${brief.fileLines.join("\n") || "(none)"}` : brief.fileLines.length ? `
|
|
41862
|
+
|
|
41863
|
+
Brief attachments:
|
|
41864
|
+
${brief.fileLines.join("\n")}` : ""}${brief.missingRequiredFile ? "\nRequired assignment files are unavailable. Pause work that depends on them and report the missing file precisely." : ""}` : surface === "code-corner" ? `No assigned brief yet. Before any code, write the first brief from the newest human request with ${generatedTitle ? "rename_corner" : "revise_corner_brief at expectedRevision 0"}.` : "No assigned brief: the human messages in this corner are the authority; skip steps that need a brief revision."
|
|
41865
|
+
},
|
|
41866
|
+
{
|
|
41867
|
+
id: "turn.checkout",
|
|
41868
|
+
topic: "checkout",
|
|
41869
|
+
why: "A Room checkout is refreshed per turn; the agent must know which commit it reads.",
|
|
41870
|
+
budgetBytes: 200,
|
|
41871
|
+
layer: "turn",
|
|
41872
|
+
surfaces: ROOMS,
|
|
41873
|
+
render: ({ checkout }) => checkout ? `Repository checkout: origin/${checkout.branch} at commit ${checkout.commit}, refreshed for this turn.` : ""
|
|
41874
|
+
},
|
|
41875
|
+
{
|
|
41876
|
+
id: "turn.transcript",
|
|
41877
|
+
topic: "transcript",
|
|
41878
|
+
why: "The conversation so far, with rows this session already saw left out.",
|
|
41879
|
+
budgetBytes: 2e5,
|
|
41880
|
+
layer: "turn",
|
|
41881
|
+
surfaces: EVERYWHERE,
|
|
41882
|
+
render: ({ transcript, surface }) => {
|
|
41883
|
+
if (!transcript?.lines.length)
|
|
41884
|
+
return "";
|
|
41885
|
+
const place = ROOMS.includes(surface) ? "Room" : "corner";
|
|
41886
|
+
return `${transcript.sinceLastTurn ? `New in the ${place} since your last turn:` : `${place === "Room" ? "Room conversation" : "Corner transcript"}:`}
|
|
41887
|
+
${transcript.lines.join("\n")}`;
|
|
41888
|
+
}
|
|
41889
|
+
},
|
|
41890
|
+
{
|
|
41891
|
+
id: "turn.resume",
|
|
41892
|
+
topic: "resume",
|
|
41893
|
+
why: "A card answer resumes a paused turn; the agent must know which question was answered.",
|
|
41894
|
+
budgetBytes: 600,
|
|
41895
|
+
layer: "turn",
|
|
41896
|
+
surfaces: ROOMS,
|
|
41897
|
+
render: ({ resume }) => resume ?? ""
|
|
41898
|
+
},
|
|
41899
|
+
{
|
|
41900
|
+
id: "turn.members",
|
|
41901
|
+
topic: "members",
|
|
41902
|
+
why: "Handle spellings change; only the live roster is a safe source.",
|
|
41903
|
+
budgetBytes: 4e3,
|
|
41904
|
+
layer: "turn",
|
|
41905
|
+
surfaces: EVERYWHERE,
|
|
41906
|
+
render: ({ members }) => members ?? ""
|
|
41907
|
+
},
|
|
41908
|
+
{
|
|
41909
|
+
id: "turn.memory",
|
|
41910
|
+
topic: "memory",
|
|
41911
|
+
why: "Keyword-matched institutional memory, compiled and capped by the server.",
|
|
41912
|
+
budgetBytes: 1e3,
|
|
41913
|
+
layer: "turn",
|
|
41914
|
+
surfaces: EVERYWHERE,
|
|
41915
|
+
render: ({ memory }) => memory ?? ""
|
|
41916
|
+
},
|
|
41917
|
+
{
|
|
41918
|
+
id: "turn.corners",
|
|
41919
|
+
topic: "corners",
|
|
41920
|
+
why: "Room and corner agents need exact member corner ids for supported relay actions.",
|
|
41921
|
+
budgetBytes: 8e3,
|
|
41922
|
+
layer: "turn",
|
|
41923
|
+
surfaces: ["room", "code-corner", "repo-less-corner"],
|
|
41924
|
+
render: ({ surface, corners, closedCorners }) => [
|
|
41925
|
+
corners?.length ? `Current corners you belong to (use the exact cornerId with ${surface === "room" ? "inspect_corner, steer_corner, or ask_corner" : "steer_corner for a sibling under this parent Room"}):
|
|
41926
|
+
${JSON.stringify(corners)}` : "",
|
|
41927
|
+
surface === "room" && closedCorners?.length ? `Corners you belong to closed in the last 24 hours (merge commit is unavailable when absent):
|
|
41928
|
+
${closedCorners.map((corner) => `- ${corner.name ?? corner.cornerId} (${corner.cornerId}): PR ${corner.pullRequestNumber ? `#${corner.pullRequestNumber}` : "unavailable"}, merge commit ${corner.mergeCommitSha ?? "unavailable"}`).join("\n")}` : ""
|
|
41929
|
+
].filter(Boolean).join("\n\n")
|
|
41930
|
+
},
|
|
41931
|
+
{
|
|
41932
|
+
id: "turn.reviewer-target",
|
|
41933
|
+
topic: "reviewer-target",
|
|
41934
|
+
why: "The reviewer must review the head that is green now, not one named in an older message.",
|
|
41935
|
+
budgetBytes: 1200,
|
|
41936
|
+
layer: "turn",
|
|
41937
|
+
surfaces: ["code-corner"],
|
|
41938
|
+
render: ({ reviewerTarget }) => reviewerTarget ?? ""
|
|
41939
|
+
},
|
|
41940
|
+
{
|
|
41941
|
+
id: "turn.task",
|
|
41942
|
+
topic: "task",
|
|
41943
|
+
why: "The message this turn answers, said once and last.",
|
|
41944
|
+
budgetBytes: 64e3,
|
|
41945
|
+
layer: "turn",
|
|
41946
|
+
surfaces: EVERYWHERE,
|
|
41947
|
+
render: ({ task, surface }) => ROOMS.includes(surface) ? [
|
|
41948
|
+
`Current task from ${task.fromName ?? "a Room member"}:`,
|
|
41949
|
+
task.cornerAskId ? `Corner ask id: ${task.cornerAskId}. Use get_corner_ask to retrieve its status and answer.` : "",
|
|
41950
|
+
task.body,
|
|
41951
|
+
renderReplyContext(task.reply ?? {})
|
|
41952
|
+
].filter(Boolean).join("\n\n") : [
|
|
41953
|
+
...task.reactionTargetId ? [`Reaction target message id: ${task.reactionTargetId}`] : [],
|
|
41954
|
+
`Newest message:
|
|
41955
|
+
${task.body}`,
|
|
41956
|
+
...task.reply?.replyToMessageId ? [renderReplyContext(task.reply)] : [],
|
|
41957
|
+
...task.attachmentLines ?? []
|
|
41958
|
+
].join("\n")
|
|
41931
41959
|
}
|
|
41932
|
-
|
|
41933
|
-
|
|
41934
|
-
|
|
41935
|
-
|
|
41936
|
-
return false;
|
|
41937
|
-
await excludeIndexFromGitStatus(cwd);
|
|
41938
|
-
const indexPath = resolve33(cwd, CODEGRAPH_INDEX_PATH);
|
|
41939
|
-
const indexed = await access3(indexPath).then(() => true, () => false);
|
|
41940
|
-
const args = indexed ? ["sync", "--quiet", cwd] : ["init", "--yes", cwd];
|
|
41941
|
-
try {
|
|
41942
|
-
await execFileAsync6(command, args, {
|
|
41943
|
-
cwd,
|
|
41944
|
-
env: { ...process.env, CODEGRAPH_TELEMETRY: "0", CODEGRAPH_NO_DAEMON: "1" },
|
|
41945
|
-
timeout: CODEGRAPH_PREPARE_TIMEOUT_MS,
|
|
41946
|
-
maxBuffer: 4 * 1024 * 1024
|
|
41947
|
-
});
|
|
41948
|
-
return true;
|
|
41949
|
-
} catch (error) {
|
|
41950
|
-
console.warn(`[body] CodeGraph ${args[0]} failed for ${cwd}; continuing with Beeline read tools:`, error);
|
|
41951
|
-
return false;
|
|
41960
|
+
];
|
|
41961
|
+
function assembleTurnPrompt(context) {
|
|
41962
|
+
if (context.task.outsideEvent?.kind?.startsWith("webhook:")) {
|
|
41963
|
+
context = { ...context, task: { ...context.task, body: quoteOutsideData(context.task.outsideEvent.kind, context.task.outsideEvent.payload) } };
|
|
41952
41964
|
}
|
|
41965
|
+
const boundedContext = ROOMS.includes(context.surface) ? {
|
|
41966
|
+
...context,
|
|
41967
|
+
task: {
|
|
41968
|
+
...context.task,
|
|
41969
|
+
body: boundRoomTaskBody(context.task.body, context.modelContextTokens)
|
|
41970
|
+
}
|
|
41971
|
+
} : context;
|
|
41972
|
+
const withoutTranscript = applicable(TURN_SECTIONS, {
|
|
41973
|
+
...boundedContext,
|
|
41974
|
+
transcript: void 0
|
|
41975
|
+
});
|
|
41976
|
+
const otherBytes = withoutTranscript.reduce((sum, entry) => sum + Buffer.byteLength(entry.text) + 2, 0);
|
|
41977
|
+
const lines = budgetTranscript(boundedContext.transcript?.lines ?? [], boundedContext.modelContextTokens, otherBytes);
|
|
41978
|
+
const entries = applicable(TURN_SECTIONS, {
|
|
41979
|
+
...boundedContext,
|
|
41980
|
+
transcript: boundedContext.transcript ? { ...boundedContext.transcript, lines } : void 0
|
|
41981
|
+
});
|
|
41982
|
+
return {
|
|
41983
|
+
text: entries.map((entry) => entry.text).join("\n\n"),
|
|
41984
|
+
report: entries.map(({ section, text: text2 }) => ({
|
|
41985
|
+
id: section.id,
|
|
41986
|
+
bytes: Buffer.byteLength(text2)
|
|
41987
|
+
}))
|
|
41988
|
+
};
|
|
41953
41989
|
}
|
|
41954
41990
|
|
|
41955
|
-
// apps/body/dist/monolith-room-turn.js
|
|
41956
|
-
import { mkdir as mkdir23, writeFile as writeFile18 } from "node:fs/promises";
|
|
41957
|
-
import { homedir as homedir17, hostname as hostname2 } from "node:os";
|
|
41958
|
-
import { dirname as dirname24, join as join18 } from "node:path";
|
|
41959
|
-
|
|
41960
|
-
// packages/api-contract/dist/scheduled-prompts.js
|
|
41961
|
-
var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
|
|
41962
|
-
var SCHEDULE_RAN_VERB = "ran a schedule for";
|
|
41963
|
-
|
|
41964
41991
|
// apps/body/dist/turn-trace.js
|
|
41965
41992
|
import { appendFile as appendFile2, mkdir as mkdir22, readdir as readdir7, rm as rm13 } from "node:fs/promises";
|
|
41966
41993
|
import { resolve as resolve34 } from "node:path";
|
|
@@ -44082,7 +44109,7 @@ if (!refusal) {
|
|
|
44082
44109
|
}
|
|
44083
44110
|
|
|
44084
44111
|
// apps/body/dist/warm-node-modules.js
|
|
44085
|
-
import { createHash as
|
|
44112
|
+
import { createHash as createHash12, randomUUID as randomUUID11 } from "node:crypto";
|
|
44086
44113
|
import { constants as fsConstants3 } from "node:fs";
|
|
44087
44114
|
import { chmod as chmod10, copyFile as copyFile2, link, lstat as lstat4, mkdir as mkdir25, readdir as readdir8, readFile as readFile29, readlink as readlink2, rename as rename10, rm as rm14, stat as stat8, symlink as symlink3, utimes } from "node:fs/promises";
|
|
44088
44115
|
import { dirname as dirname25, join as join19, resolve as resolve36 } from "node:path";
|
|
@@ -44127,7 +44154,7 @@ async function readWarmPlan(worktreePath) {
|
|
|
44127
44154
|
const unsafe = trees.find((tree) => !isContainedTreePath(tree));
|
|
44128
44155
|
if (unsafe)
|
|
44129
44156
|
return { failure: "unsafe-lockfile", detail: unsafe };
|
|
44130
|
-
const key =
|
|
44157
|
+
const key = createHash12("sha256").update(`${STORE_FORMAT}\0${process.platform}\0${process.arch}\0${process.versions.modules}\0`).update(lockfile).digest("hex").slice(0, 32);
|
|
44131
44158
|
return { key, trees };
|
|
44132
44159
|
}
|
|
44133
44160
|
function nodeModulesTreePaths(packages) {
|
|
@@ -44380,9 +44407,13 @@ var execFileAsync7 = promisify7(execFile12);
|
|
|
44380
44407
|
var TOOL_ARGUMENT_MAX_BYTES = 1200;
|
|
44381
44408
|
var TOOL_OUTPUT_MAX_BYTES = 3200;
|
|
44382
44409
|
var TOOL_PATH_LIMIT = 12;
|
|
44410
|
+
var GITHUB_OUTAGE_WAIT_MS = 12e4;
|
|
44383
44411
|
function isCornerChecksTurn(trigger, restates) {
|
|
44384
44412
|
return Boolean(restates) || /\b(?:passed|failed) a check\b/i.test(trigger);
|
|
44385
44413
|
}
|
|
44414
|
+
function isCornerReviewTurn(reviewer, trigger) {
|
|
44415
|
+
return reviewer && /\bpassed a check\b/i.test(trigger);
|
|
44416
|
+
}
|
|
44386
44417
|
async function cornerUndeliveredRepositoryState(worktreePath, featureBranch, targetBranch) {
|
|
44387
44418
|
try {
|
|
44388
44419
|
const [{ stdout: status }, ahead] = await Promise.all([
|
|
@@ -44541,11 +44572,8 @@ function toolArguments(call) {
|
|
|
44541
44572
|
const input = serialized(call.rawInput);
|
|
44542
44573
|
return input ? { input: clampBytes(redactToolDetail(input), TOOL_ARGUMENT_MAX_BYTES) } : {};
|
|
44543
44574
|
}
|
|
44544
|
-
function spentAuthorityOnLaneUpgrade(call) {
|
|
44545
|
-
return /(?:^|[._:/-])upgrade_corner_to_code$/i.test(call.title ?? "") && isCompletedToolCall(call);
|
|
44546
|
-
}
|
|
44547
44575
|
function toolCallKey(call, index) {
|
|
44548
|
-
return call.id ? `id-${
|
|
44576
|
+
return call.id ? `id-${createHash13("sha256").update(call.id).digest("hex")}` : `tool-${index}`;
|
|
44549
44577
|
}
|
|
44550
44578
|
function toolCallSettled(call) {
|
|
44551
44579
|
if (call.resultReceived)
|
|
@@ -44627,9 +44655,10 @@ var MonolithCornerTurnLoop = class {
|
|
|
44627
44655
|
modelTakesImages;
|
|
44628
44656
|
/** The one provider re-pinned after an empty completion, until the session ends. */
|
|
44629
44657
|
pinnedProviderOverride;
|
|
44630
|
-
/**
|
|
44631
|
-
|
|
44632
|
-
|
|
44658
|
+
/**
|
|
44659
|
+
* Identity-only reviewer context, set when this agent holds the Room's
|
|
44660
|
+
* reviewer post; the exact PR head is refreshed inside each review turn.
|
|
44661
|
+
*/
|
|
44633
44662
|
reviewerInstructionInput;
|
|
44634
44663
|
/** The role-specific second-chance instruction for this session. */
|
|
44635
44664
|
/** Repository state already given a delivery reminder, until that state changes. */
|
|
@@ -44915,17 +44944,13 @@ var MonolithCornerTurnLoop = class {
|
|
|
44915
44944
|
]);
|
|
44916
44945
|
const { hostRoutes: grantedHostRoutes, devices: grantedDevices } = grants;
|
|
44917
44946
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
44918
|
-
this.yoloMode = configuration.yoloMode;
|
|
44919
44947
|
const opener = this.options.openedBy ? roster.members.find((member) => member.identityId === this.options.openedBy) : void 0;
|
|
44920
|
-
const
|
|
44921
|
-
|
|
44922
|
-
|
|
44923
|
-
|
|
44924
|
-
};
|
|
44925
|
-
|
|
44926
|
-
this.reviewerInstructionInput = reviewerInstruction ? reviewerInput : void 0;
|
|
44927
|
-
const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
|
|
44928
|
-
this.sessionSurface = !this.options.repository ? "no-code-corner" : reviewerInstruction ? "review-corner" : "code-corner";
|
|
44948
|
+
const reviewer = Boolean(this.options.repository) && configuration.isReviewer === true;
|
|
44949
|
+
this.reviewerInstructionInput = reviewer ? {
|
|
44950
|
+
isReviewer: true,
|
|
44951
|
+
...opener?.handle && opener.identityId !== this.agent.publicKey ? { authorHandle: opener.handle } : {}
|
|
44952
|
+
} : void 0;
|
|
44953
|
+
this.sessionSurface = this.options.repository ? "code-corner" : "repo-less-corner";
|
|
44929
44954
|
this.repositoryWork = cornerHasRepositoryWork(restored.brief);
|
|
44930
44955
|
const cornerPrepareScript = await hasCornerPrepareScript(this.options.worktreePath);
|
|
44931
44956
|
this.sessionPromptContext = {
|
|
@@ -44933,8 +44958,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
44933
44958
|
surface: this.sessionSurface,
|
|
44934
44959
|
agentName: self?.name ?? this.agent.name,
|
|
44935
44960
|
...configuration.reviewerHandle ? { reviewerHandle: configuration.reviewerHandle } : {},
|
|
44936
|
-
|
|
44937
|
-
yoloMode: configuration.yoloMode,
|
|
44961
|
+
reviewer,
|
|
44938
44962
|
cornerPrepareScript
|
|
44939
44963
|
};
|
|
44940
44964
|
await mkdir26(this.options.worktreePath, { recursive: true });
|
|
@@ -44964,7 +44988,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
44964
44988
|
root: this.options.config.agentHomeRoot,
|
|
44965
44989
|
squireScope,
|
|
44966
44990
|
sharedSkills: this.options.config.sharedSkills ?? [],
|
|
44967
|
-
isReviewer:
|
|
44991
|
+
isReviewer: reviewer,
|
|
44968
44992
|
grantedHostRoutes: mountedHostRoutes,
|
|
44969
44993
|
extraHostRoutes: registryHostDeclarations,
|
|
44970
44994
|
resourceAuthFile,
|
|
@@ -45150,9 +45174,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
45150
45174
|
workspaceId: this.options.workspaceId,
|
|
45151
45175
|
cornerId: this.options.cornerId,
|
|
45152
45176
|
agentMayCloseCorner: Boolean(repository),
|
|
45153
|
-
|
|
45154
|
-
|
|
45155
|
-
lane: this.options.lane ?? (repository ? "code" : "no_code"),
|
|
45177
|
+
reviewer,
|
|
45178
|
+
repositoryBacked: Boolean(repository),
|
|
45156
45179
|
attachRoot: this.options.worktreePath,
|
|
45157
45180
|
// The whole per-session overlay, not an enumerated subset: see
|
|
45158
45181
|
// `monolith-room-turn.ts`'s matching comment.
|
|
@@ -45190,7 +45213,6 @@ var MonolithCornerTurnLoop = class {
|
|
|
45190
45213
|
}
|
|
45191
45214
|
} : {},
|
|
45192
45215
|
...this.options.requesterHandle ? { requesterHandle: this.options.requesterHandle } : {},
|
|
45193
|
-
...this.options.agentMayUpgradeCorner ? { agentMayUpgradeCorner: true } : {},
|
|
45194
45216
|
...agentEnv.BEELINE_ANDROID_EMULATOR_PORT ? { android: { emulatorPort: agentEnv.BEELINE_ANDROID_EMULATOR_PORT } } : {}
|
|
45195
45217
|
});
|
|
45196
45218
|
this.turnSessionPrefix = session.turnPrefix;
|
|
@@ -45301,24 +45323,6 @@ var MonolithCornerTurnLoop = class {
|
|
|
45301
45323
|
servingProviders() {
|
|
45302
45324
|
return this.pinnedProviderOverride ? [this.pinnedProviderOverride] : this.pinnedProviders;
|
|
45303
45325
|
}
|
|
45304
|
-
/**
|
|
45305
|
-
* Did this corner's lane already move under the running turn?
|
|
45306
|
-
*
|
|
45307
|
-
* The upgrade is a server fact, and only the server can say it happened: a
|
|
45308
|
-
* harness that reports no tool calls at all (`cursor-acp-bridge.ts` emits
|
|
45309
|
-
* message chunks only) would otherwise finish its turn believing it still
|
|
45310
|
-
* owns write authority the upgrade has already spent. One bounded read, and
|
|
45311
|
-
* only where an upgrade is possible — the tool is mounted nowhere else.
|
|
45312
|
-
*/
|
|
45313
|
-
async laneUpgradeCommitted() {
|
|
45314
|
-
if (this.options.lane !== "no_code" || !this.options.agentMayUpgradeCorner)
|
|
45315
|
-
return false;
|
|
45316
|
-
const state = await this.options.api.execute("getCornerRestoreState", { cornerId: this.options.cornerId }).catch((error) => {
|
|
45317
|
-
console.error(`[thin-core] corner ${this.options.cornerId} lane read failed:`, error);
|
|
45318
|
-
return void 0;
|
|
45319
|
-
});
|
|
45320
|
-
return Boolean(state?.lane && state.lane !== "no_code");
|
|
45321
|
-
}
|
|
45322
45326
|
/** Why a turn carried no answer text, or undefined when it did. */
|
|
45323
45327
|
async explainEmpty(result) {
|
|
45324
45328
|
if (durableReplyText(result.agentText))
|
|
@@ -45412,7 +45416,6 @@ var MonolithCornerTurnLoop = class {
|
|
|
45412
45416
|
await trace.finish(outcome, reason);
|
|
45413
45417
|
};
|
|
45414
45418
|
let deliberateNoReply = false;
|
|
45415
|
-
let laneUpgraded = false;
|
|
45416
45419
|
try {
|
|
45417
45420
|
await withTurnReceiptHeartbeat(api, {
|
|
45418
45421
|
agentId: this.agent.publicKey,
|
|
@@ -45431,6 +45434,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
45431
45434
|
this.turnMetrics = {};
|
|
45432
45435
|
this.turnUsage = new TurnUsageAccumulator();
|
|
45433
45436
|
await this.syncBranch();
|
|
45437
|
+
const reviewTurn = isCornerReviewTurn(Boolean(this.reviewerInstructionInput), trigger);
|
|
45434
45438
|
const [conversation, roster, restored, activeReviewerInstruction, institutionalContext, memberCorners] = await trace.measure("context-fetch", () => Promise.all([
|
|
45435
45439
|
api.execute("getRoomConversation", {
|
|
45436
45440
|
roomId: cornerId,
|
|
@@ -45439,7 +45443,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
45439
45443
|
}),
|
|
45440
45444
|
this.roster(),
|
|
45441
45445
|
api.execute("getCornerRestoreState", { cornerId }),
|
|
45442
|
-
this.activeReviewerInstruction(),
|
|
45446
|
+
reviewTurn ? this.activeReviewerInstruction() : void 0,
|
|
45443
45447
|
awaitInstitutionalContext(institutionalContextFetch, (message) => console.warn(`[thin-core] corner ${cornerId}: ${message}`)),
|
|
45444
45448
|
api.execute("listRoomCorners", { roomId: this.options.parentRoomId })
|
|
45445
45449
|
]));
|
|
@@ -45464,7 +45468,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
45464
45468
|
const briefDelivered = this.attachmentDir && briefAttachments.length ? await deliverAttachments(briefAttachments, join20(this.attachmentDir, requestId.replace(/[^\w-]/g, "_"), "brief", String(assignedBrief.revision)), this.options.fetchImpl) : [];
|
|
45465
45469
|
briefFileLines = await Promise.all((assignedBrief?.attachments ?? []).map(async (file, index) => {
|
|
45466
45470
|
const entry = briefDelivered.find((item) => item.attachment === briefAttachments[index]);
|
|
45467
|
-
const verified = entry?.path &&
|
|
45471
|
+
const verified = entry?.path && createHash13("sha256").update(await readFile30(entry.path)).digest("hex") === file.sha256;
|
|
45468
45472
|
return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
|
|
45469
45473
|
}));
|
|
45470
45474
|
missingRequiredBriefFile = (assignedBrief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
|
|
@@ -45580,8 +45584,6 @@ var MonolithCornerTurnLoop = class {
|
|
|
45580
45584
|
let activityAttempt = 0;
|
|
45581
45585
|
const publishToolCalls = (calls, settledOnly, exceptKey) => {
|
|
45582
45586
|
calls.forEach((call, index) => {
|
|
45583
|
-
if (spentAuthorityOnLaneUpgrade(call))
|
|
45584
|
-
return;
|
|
45585
45587
|
const key = `${activityAttempt}:${toolCallKey(call, index)}`;
|
|
45586
45588
|
if (settledOnly && !observedToolCalls.has(key)) {
|
|
45587
45589
|
observedToolCalls.add(key);
|
|
@@ -45732,12 +45734,6 @@ ${continuation}` : assembled;
|
|
|
45732
45734
|
};
|
|
45733
45735
|
let result = await runPrompt();
|
|
45734
45736
|
trace.promptSettled();
|
|
45735
|
-
laneUpgraded = await this.laneUpgradeCommitted();
|
|
45736
|
-
if (laneUpgraded) {
|
|
45737
|
-
console.log(`[thin-core] corner ${cornerId} turn ${requestId} upgraded to the code lane`);
|
|
45738
|
-
await stream.retract();
|
|
45739
|
-
return;
|
|
45740
|
-
}
|
|
45741
45737
|
const grantedDevice = approvedDeviceGrant(result.toolCalls);
|
|
45742
45738
|
if (grantedDevice) {
|
|
45743
45739
|
await flushToolCalls(result.toolCalls, "");
|
|
@@ -45778,19 +45774,19 @@ ${continuation}` : assembled;
|
|
|
45778
45774
|
let refreshedReviewerInstruction;
|
|
45779
45775
|
let reviewerSecondPass = false;
|
|
45780
45776
|
let reviewerTargetChanged = false;
|
|
45781
|
-
if (!explained &&
|
|
45777
|
+
if (!explained && reviewTurn) {
|
|
45782
45778
|
await this.syncBranch();
|
|
45783
45779
|
refreshedReviewerInstruction = await this.activeReviewerInstruction();
|
|
45784
45780
|
reviewerTargetChanged = refreshedReviewerInstruction !== activeReviewerInstruction;
|
|
45785
|
-
reviewerSecondPass = reviewerTargetChanged || !result.toolCalls.some((call) => /(?:^|[._:/-])
|
|
45781
|
+
reviewerSecondPass = reviewerTargetChanged || !result.toolCalls.some((call) => /(?:^|[._:/-])approve_merge$/i.test(call.title ?? "") && isCompletedToolCall(call));
|
|
45786
45782
|
}
|
|
45787
45783
|
let replyBeforeNudge = "";
|
|
45788
|
-
if (!explained && (needsDeliveryNudge || checksTurn && (
|
|
45784
|
+
if (!explained && (needsDeliveryNudge || checksTurn && (reviewTurn ? reviewerSecondPass : repositoryWork && !repositoryHeld && !result.toolCalls.some((call) => /(?:^|[._:/-])pr_checks_status$/i.test(call.title ?? "") && isCompletedToolCall(call))))) {
|
|
45789
45785
|
if (needsDeliveryNudge)
|
|
45790
45786
|
this.lastDeliveryNudgeState = deliveryState;
|
|
45791
45787
|
await flushToolCalls(result.toolCalls, "");
|
|
45792
45788
|
replyBeforeNudge = durableReplyText(stream.remainderOf(result.agentText));
|
|
45793
|
-
result = await runPrompt(
|
|
45789
|
+
result = await runPrompt(reviewTurn ? refreshedReviewerInstruction ?? CORNER_REVIEWER_SESSION_INSTRUCTION : checksTurn ? CORNER_CHECKS_NUDGE : CORNER_DELIVERY_NUDGE);
|
|
45794
45790
|
trace.promptSettled();
|
|
45795
45791
|
explained = await this.explainEmpty(result);
|
|
45796
45792
|
}
|
|
@@ -45836,10 +45832,6 @@ ${continuation}` : assembled;
|
|
|
45836
45832
|
console.warn(`[thin-core] corner ${cornerId} turn ${requestId} dropped: the server revoked its output authority`);
|
|
45837
45833
|
this.stopTurn(requestId);
|
|
45838
45834
|
});
|
|
45839
|
-
if (laneUpgraded) {
|
|
45840
|
-
void finishTrace("complete").catch(() => void 0);
|
|
45841
|
-
return;
|
|
45842
|
-
}
|
|
45843
45835
|
startTraceStats();
|
|
45844
45836
|
await api.execute("postAgentTurnReceipt", {
|
|
45845
45837
|
agentId: this.agent.publicKey,
|
|
@@ -45858,12 +45850,6 @@ ${continuation}` : assembled;
|
|
|
45858
45850
|
void finishTrace("cancelled").catch(() => void 0);
|
|
45859
45851
|
return;
|
|
45860
45852
|
}
|
|
45861
|
-
laneUpgraded ||= await this.laneUpgradeCommitted();
|
|
45862
|
-
if (laneUpgraded) {
|
|
45863
|
-
console.log(`[thin-core] corner ${cornerId} turn ${requestId} ended after its lane upgrade:`, error);
|
|
45864
|
-
void finishTrace("complete").catch(() => void 0);
|
|
45865
|
-
return;
|
|
45866
|
-
}
|
|
45867
45853
|
const reason = distillTurnFailureReason(error);
|
|
45868
45854
|
startTraceStats();
|
|
45869
45855
|
await api.execute("postAgentTurnReceipt", {
|
|
@@ -45942,16 +45928,19 @@ ${continuation}` : assembled;
|
|
|
45942
45928
|
if (!repository)
|
|
45943
45929
|
return;
|
|
45944
45930
|
let token;
|
|
45945
|
-
|
|
45931
|
+
const outageDeadline = Date.now() + GITHUB_OUTAGE_WAIT_MS;
|
|
45932
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
45946
45933
|
try {
|
|
45947
45934
|
token = (await this.options.api.execute("getRoomGitHubToken", {
|
|
45948
45935
|
roomId: this.options.parentRoomId
|
|
45949
45936
|
})).token;
|
|
45950
45937
|
break;
|
|
45951
45938
|
} catch (error) {
|
|
45952
|
-
|
|
45939
|
+
const outage = error instanceof DaemonApiError && error.status === 502;
|
|
45940
|
+
const delay = outage ? Math.min(2e3 * 2 ** attempt, 3e4) : 250 * (attempt + 1);
|
|
45941
|
+
if ((outage ? Date.now() + delay > outageDeadline : attempt === 2) || this.forcedStop || this.currentTurn && this.stoppedTurns.has(this.currentTurn.requestId))
|
|
45953
45942
|
throw error;
|
|
45954
|
-
await new Promise((resolve45) => setTimeout(resolve45,
|
|
45943
|
+
await new Promise((resolve45) => setTimeout(resolve45, delay));
|
|
45955
45944
|
}
|
|
45956
45945
|
}
|
|
45957
45946
|
if (!token)
|
|
@@ -46029,10 +46018,6 @@ ${continuation}` : assembled;
|
|
|
46029
46018
|
return false;
|
|
46030
46019
|
this.reconcileCloseRequested = false;
|
|
46031
46020
|
const state = await api.execute("getCornerRestoreState", { cornerId });
|
|
46032
|
-
if (this.options.lane === "no_code" && state.lane !== "no_code") {
|
|
46033
|
-
this.options.onLaneChanged?.();
|
|
46034
|
-
return true;
|
|
46035
|
-
}
|
|
46036
46021
|
if (!state.closeRequested)
|
|
46037
46022
|
return false;
|
|
46038
46023
|
await this.harvest;
|
|
@@ -46562,7 +46547,7 @@ function cornerStartConfigKey(repository, objective) {
|
|
|
46562
46547
|
}
|
|
46563
46548
|
function cornerRepositoryCacheDir(supervisorRoot, remote) {
|
|
46564
46549
|
const normalizedRemote = roomCheckoutRemote(remote);
|
|
46565
|
-
const repositoryHash =
|
|
46550
|
+
const repositoryHash = createHash14("sha256").update(normalizedRemote).digest("hex").slice(0, 24);
|
|
46566
46551
|
return resolve38(supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
|
|
46567
46552
|
}
|
|
46568
46553
|
function isStandingCornerStartFault(error) {
|
|
@@ -46737,6 +46722,7 @@ async function removeCornerWorktreeAndBranches(worktree, options = {}) {
|
|
|
46737
46722
|
if (head === localRef) {
|
|
46738
46723
|
throw new Error(`refusing to delete repository HEAD ${localRef}`);
|
|
46739
46724
|
}
|
|
46725
|
+
await assertLocalBranchPublished(worktree);
|
|
46740
46726
|
}
|
|
46741
46727
|
if (!preserveRemoteBranch) {
|
|
46742
46728
|
await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
|
|
@@ -46757,26 +46743,38 @@ async function assertCornerWorktreePublished(worktree) {
|
|
|
46757
46743
|
const originalHead = (await execFileAsync8("git", ["-C", worktree.path, "rev-parse", "--verify", "HEAD"])).stdout.trim();
|
|
46758
46744
|
if (await status())
|
|
46759
46745
|
throw new Error(`corner ${worktree.cornerId} has unpushed working-tree work`);
|
|
46760
|
-
|
|
46746
|
+
await assertPublishedOnOrigin(worktree.gitCommonDir, originalHead, worktree.cornerId, worktree.token);
|
|
46747
|
+
const settledHead = (await execFileAsync8("git", ["-C", worktree.path, "rev-parse", "--verify", "HEAD"])).stdout.trim();
|
|
46748
|
+
if (settledHead !== originalHead || await status()) {
|
|
46749
|
+
throw new Error(`corner ${worktree.cornerId} changed during cleanup`);
|
|
46750
|
+
}
|
|
46751
|
+
}
|
|
46752
|
+
async function assertLocalBranchPublished(worktree) {
|
|
46753
|
+
const head = (await execFileAsync8("git", [
|
|
46754
|
+
`--git-dir=${worktree.gitCommonDir}`,
|
|
46755
|
+
"rev-parse",
|
|
46756
|
+
"--verify",
|
|
46757
|
+
`refs/heads/${worktree.branch}`
|
|
46758
|
+
])).stdout.trim();
|
|
46759
|
+
await assertPublishedOnOrigin(worktree.gitCommonDir, head, worktree.cornerId, worktree.token);
|
|
46760
|
+
}
|
|
46761
|
+
async function assertPublishedOnOrigin(gitCommonDir, head, cornerId, token) {
|
|
46762
|
+
let published = await originContains(gitCommonDir, head);
|
|
46761
46763
|
if (!published) {
|
|
46762
46764
|
await execFileAsync8("git", [
|
|
46763
|
-
`--git-dir=${
|
|
46765
|
+
`--git-dir=${gitCommonDir}`,
|
|
46764
46766
|
"fetch",
|
|
46765
46767
|
"--prune",
|
|
46766
46768
|
"origin",
|
|
46767
46769
|
"+refs/heads/*:refs/remotes/origin/*"
|
|
46768
|
-
], { env: githubGitEnv(
|
|
46769
|
-
published = await originContains(
|
|
46770
|
+
], { env: githubGitEnv(token), maxBuffer: 4 * 1024 * 1024 });
|
|
46771
|
+
published = await originContains(gitCommonDir, head);
|
|
46770
46772
|
}
|
|
46771
46773
|
if (!published)
|
|
46772
|
-
throw new Error(`corner ${
|
|
46773
|
-
const settledHead = (await execFileAsync8("git", ["-C", worktree.path, "rev-parse", "--verify", "HEAD"])).stdout.trim();
|
|
46774
|
-
if (settledHead !== originalHead || await status()) {
|
|
46775
|
-
throw new Error(`corner ${worktree.cornerId} changed during cleanup`);
|
|
46776
|
-
}
|
|
46774
|
+
throw new Error(`corner ${cornerId} has unpushed commits`);
|
|
46777
46775
|
}
|
|
46778
|
-
function
|
|
46779
|
-
return error instanceof Error && / has unpushed commits$/.test(error.message);
|
|
46776
|
+
function hasUnpublishedWork(error) {
|
|
46777
|
+
return error instanceof Error && / has unpushed (?:commits|working-tree work)$/.test(error.message);
|
|
46780
46778
|
}
|
|
46781
46779
|
async function originContains(gitCommonDir, head) {
|
|
46782
46780
|
const result = await execFileAsync8("git", [
|
|
@@ -46883,8 +46881,11 @@ var RoomRuntimeCoordinator = class {
|
|
|
46883
46881
|
this.connectorUsage = new ConnectorUsageRecorder();
|
|
46884
46882
|
this.options.daemonApi.setRoomsChangedListener?.((event) => {
|
|
46885
46883
|
if (event?.repositoryChanged) {
|
|
46886
|
-
if (event.roomId)
|
|
46884
|
+
if (event.roomId) {
|
|
46887
46885
|
this.invalidateParentRepository(event.roomId);
|
|
46886
|
+
void this.refreshRoomCorners(event.roomId);
|
|
46887
|
+
return;
|
|
46888
|
+
}
|
|
46888
46889
|
this.wakeDiscovery();
|
|
46889
46890
|
return;
|
|
46890
46891
|
}
|
|
@@ -46898,9 +46899,6 @@ var RoomRuntimeCoordinator = class {
|
|
|
46898
46899
|
this.options.daemonApi.setCornerCompleteListener?.((roomId) => {
|
|
46899
46900
|
void this.applyCornerComplete(roomId).catch((error) => console.error("[thin-core] live corner-complete apply failed", error));
|
|
46900
46901
|
});
|
|
46901
|
-
this.options.daemonApi.setCornerRestartListener?.((roomId) => {
|
|
46902
|
-
void this.applyCornerRestart(roomId).catch((error) => console.error("[thin-core] live corner-restart apply failed", error));
|
|
46903
|
-
});
|
|
46904
46902
|
this.options.daemonApi.setConfigChangedListener?.(() => {
|
|
46905
46903
|
void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
|
|
46906
46904
|
void Promise.resolve(this.options.onConfigChanged?.()).catch((error) => console.error("[body] config-change catalog refresh failed", error));
|
|
@@ -47218,27 +47216,14 @@ var RoomRuntimeCoordinator = class {
|
|
|
47218
47216
|
async applyCornerComplete(cornerId) {
|
|
47219
47217
|
this.running.get(cornerId)?.body.requestClose?.();
|
|
47220
47218
|
}
|
|
47221
|
-
|
|
47222
|
-
* the same corner id again and re-reads its now-code lane. */
|
|
47223
|
-
async applyCornerRestart(cornerId) {
|
|
47224
|
-
if (this.stopped)
|
|
47225
|
-
return;
|
|
47226
|
-
const running = this.running.get(cornerId);
|
|
47227
|
-
if (!running) {
|
|
47228
|
-
this.wakeDiscovery();
|
|
47229
|
-
return;
|
|
47230
|
-
}
|
|
47231
|
-
await this.stopRunning(cornerId, running, true);
|
|
47232
|
-
this.wakeDiscovery();
|
|
47233
|
-
}
|
|
47234
|
-
async stopRunning(channelId, running, preserveScratch = false) {
|
|
47219
|
+
async stopRunning(channelId, running) {
|
|
47235
47220
|
this.deferredRepositoryRestarts.delete(channelId);
|
|
47236
47221
|
running.controller.abort();
|
|
47237
47222
|
await running.promise.catch(() => void 0);
|
|
47238
47223
|
try {
|
|
47239
47224
|
if (running.worktree)
|
|
47240
47225
|
await this.reapCornerWorktree(running.worktree);
|
|
47241
|
-
else if (running.scratch
|
|
47226
|
+
else if (running.scratch)
|
|
47242
47227
|
await this.reapCornerScratch(running.scratch);
|
|
47243
47228
|
} catch (error) {
|
|
47244
47229
|
console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
|
|
@@ -47353,7 +47338,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
47353
47338
|
return this.roomRoot(roomId);
|
|
47354
47339
|
const remote = roomCheckoutRemote(repository.remote);
|
|
47355
47340
|
const targetBranch = repository.targetBranch || "main";
|
|
47356
|
-
const checkoutId =
|
|
47341
|
+
const checkoutId = createHash14("sha256").update(`${roomId}\0${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
|
|
47357
47342
|
const path = resolve38(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
|
|
47358
47343
|
await mkdir27(dirname27(path), { recursive: true, mode: 448 });
|
|
47359
47344
|
const token = remote.startsWith("https://github.com/") ? await this.parentRepositoryToken(roomId) : void 0;
|
|
@@ -47432,12 +47417,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
47432
47417
|
if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
|
|
47433
47418
|
throw new Error("corner parent Room has an incomplete repository binding");
|
|
47434
47419
|
}
|
|
47435
|
-
if (!objective)
|
|
47420
|
+
if (!objective && restore.kind !== "human")
|
|
47436
47421
|
throw new Error("corner has no authoritative objective fact");
|
|
47437
|
-
const repositoryBacked = repository.resolution === "repository"
|
|
47438
|
-
const scratchPath = resolve38(this.roomRoot(corner.cornerId), "scratch");
|
|
47439
|
-
if (repositoryBacked && existsSync10(scratchPath) && !restore.featureBranch)
|
|
47440
|
-
throw new Error("promoted corner has no server-assigned feature branch");
|
|
47422
|
+
const repositoryBacked = repository.resolution === "repository";
|
|
47441
47423
|
const targetBranch = repositoryBacked ? repository.targetBranch || "main" : void 0;
|
|
47442
47424
|
const featureBranch = repositoryBacked ? restore.featureBranch ?? `feature/corner-${corner.cornerId.replaceAll("-", "").slice(0, 12)}` : void 0;
|
|
47443
47425
|
const granted = repositoryBacked ? await this.cornerRepositoryToken(corner.parentRoomId) : void 0;
|
|
@@ -47448,8 +47430,6 @@ var RoomRuntimeCoordinator = class {
|
|
|
47448
47430
|
featureBranch,
|
|
47449
47431
|
token: granted.token
|
|
47450
47432
|
}) : void 0;
|
|
47451
|
-
if (worktree && existsSync10(scratchPath))
|
|
47452
|
-
await this.reapCornerScratch({ path: scratchPath, cornerId: corner.cornerId });
|
|
47453
47433
|
const workspacePath = worktree?.path ?? resolve38(this.roomRoot(corner.cornerId), "scratch");
|
|
47454
47434
|
if (!worktree)
|
|
47455
47435
|
await mkdir27(workspacePath, { recursive: true, mode: 448 });
|
|
@@ -47473,8 +47453,6 @@ var RoomRuntimeCoordinator = class {
|
|
|
47473
47453
|
...corner.openedBy ? { openedBy: corner.openedBy } : {},
|
|
47474
47454
|
objective,
|
|
47475
47455
|
worktreePath: workspacePath,
|
|
47476
|
-
lane: restore.lane,
|
|
47477
|
-
agentMayUpgradeCorner: repository.resolution === "repository" && restore.lane === "no_code",
|
|
47478
47456
|
...restore.requesterHandle ? { requesterHandle: restore.requesterHandle } : {},
|
|
47479
47457
|
...worktree ? {
|
|
47480
47458
|
repository: {
|
|
@@ -47499,7 +47477,6 @@ var RoomRuntimeCoordinator = class {
|
|
|
47499
47477
|
parentRoomId: corner.parentRoomId,
|
|
47500
47478
|
token: ""
|
|
47501
47479
|
}) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId }),
|
|
47502
|
-
onLaneChanged: () => void this.applyCornerRestart(corner.cornerId).catch((error) => console.error("[thin-core] corner lane-change restart failed", error)),
|
|
47503
47480
|
onRestartRequested: () => this.requestLifecycleRestart(),
|
|
47504
47481
|
canStartTurn: () => !this.restartRequested,
|
|
47505
47482
|
supervisor: this.supervisor
|
|
@@ -47536,7 +47513,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
47536
47513
|
});
|
|
47537
47514
|
this.standingCornerStartFaults.delete(corner.cornerId);
|
|
47538
47515
|
this.reportedCornerStartFailures.delete(corner.cornerId);
|
|
47539
|
-
console.log(worktree ? `[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${workspacePath}` : `[thin-core] serving
|
|
47516
|
+
console.log(worktree ? `[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${workspacePath}` : `[thin-core] serving repo-less corner ${corner.cornerId} at ${workspacePath}`);
|
|
47540
47517
|
} catch (error) {
|
|
47541
47518
|
this.surfaceHealth.degraded(corner.cornerId, "corner failed to start");
|
|
47542
47519
|
console.error(`[thin-core] failed to start corner ${corner.cornerId}:`, error);
|
|
@@ -47613,6 +47590,61 @@ var RoomRuntimeCoordinator = class {
|
|
|
47613
47590
|
this.tokenCache.delete(roomId);
|
|
47614
47591
|
this.tokenFlights.delete(roomId);
|
|
47615
47592
|
}
|
|
47593
|
+
/**
|
|
47594
|
+
* A repository-change event names its own Room, so only that Room's
|
|
47595
|
+
* corners need a fresh listing; re-listing every Room's corners for one
|
|
47596
|
+
* Room's push would repeat that read for every other Room on each push.
|
|
47597
|
+
* Only new or newly-unarchived corners are started here - removals and
|
|
47598
|
+
* archived-corner cleanup stay on the regular reconcile pass. The named
|
|
47599
|
+
* Room itself is restarted the same way a full reconcile would, so a
|
|
47600
|
+
* running Room picks up the change (a new GitHub installation, a token) too.
|
|
47601
|
+
*/
|
|
47602
|
+
async refreshRoomCorners(roomId) {
|
|
47603
|
+
if (this.stopped)
|
|
47604
|
+
return;
|
|
47605
|
+
try {
|
|
47606
|
+
await this.restartRunningRoomForRepositoryChange(roomId);
|
|
47607
|
+
if (this.stopped)
|
|
47608
|
+
return;
|
|
47609
|
+
const result = await this.options.daemonApi.execute("listRoomCorners", { roomId });
|
|
47610
|
+
for (const corner of result.corners) {
|
|
47611
|
+
this.monolithCornerParents.set(corner.cornerId, roomId);
|
|
47612
|
+
if (corner.archived)
|
|
47613
|
+
continue;
|
|
47614
|
+
this.surfaceHealth.discover(corner.cornerId, "corner");
|
|
47615
|
+
this.roomRemovalConfirmations.delete(corner.cornerId);
|
|
47616
|
+
if (this.running.has(corner.cornerId) || this.startingCorners.has(corner.cornerId))
|
|
47617
|
+
continue;
|
|
47618
|
+
await this.watchCorner({
|
|
47619
|
+
cornerId: corner.cornerId,
|
|
47620
|
+
parentRoomId: roomId,
|
|
47621
|
+
...corner.createdBy ? { openedBy: corner.createdBy } : {}
|
|
47622
|
+
});
|
|
47623
|
+
}
|
|
47624
|
+
} catch (error) {
|
|
47625
|
+
console.error(`[thin-core] scoped corner refresh failed for Room ${roomId}:`, error);
|
|
47626
|
+
this.wakeDiscovery();
|
|
47627
|
+
}
|
|
47628
|
+
}
|
|
47629
|
+
/**
|
|
47630
|
+
* Same stop/restart (or, mid-turn, defer-to-`notePoll`) a full reconcile
|
|
47631
|
+
* applies to a Room whose `repositoryRevision` changed, for the one Room a
|
|
47632
|
+
* live repository-change event names. A no-op when that id names nothing
|
|
47633
|
+
* currently running - e.g. a Room whose corners changed but is not itself
|
|
47634
|
+
* being served, or a corner id, which this event never actually names.
|
|
47635
|
+
*/
|
|
47636
|
+
async restartRunningRoomForRepositoryChange(roomId) {
|
|
47637
|
+
const running = this.running.get(roomId);
|
|
47638
|
+
if (!running)
|
|
47639
|
+
return;
|
|
47640
|
+
if (running.body.isBusy()) {
|
|
47641
|
+
this.deferredRepositoryRestarts.add(roomId);
|
|
47642
|
+
return;
|
|
47643
|
+
}
|
|
47644
|
+
await this.stopRunning(roomId, running);
|
|
47645
|
+
if (!this.stopped)
|
|
47646
|
+
await this.startRoom(roomId);
|
|
47647
|
+
}
|
|
47616
47648
|
async parentRepositoryState(roomId) {
|
|
47617
47649
|
const cached = this.repositoryStateCache.get(roomId);
|
|
47618
47650
|
if (cached && cached.until > this.now())
|
|
@@ -47729,9 +47761,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
47729
47761
|
try {
|
|
47730
47762
|
await this.reapCornerWorktree(worktree);
|
|
47731
47763
|
} catch (error) {
|
|
47732
|
-
if (
|
|
47764
|
+
if (hasUnpublishedWork(error)) {
|
|
47733
47765
|
this.archiveCleanupAbandoned.add(cornerId);
|
|
47734
|
-
console.error(`[thin-core] corner ${cornerId} branch cleanup abandoned;
|
|
47766
|
+
console.error(`[thin-core] corner ${cornerId} branch cleanup abandoned; unpublished work kept:`, error);
|
|
47735
47767
|
continue;
|
|
47736
47768
|
}
|
|
47737
47769
|
this.deferArchiveCleanup(cornerId);
|