usebeeline 0.0.54 → 0.0.57
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/dist/usebeeline.mjs +433 -136
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -291,6 +291,7 @@ __export(self_update_exports, {
|
|
|
291
291
|
beelineInstallLayout: () => beelineInstallLayout,
|
|
292
292
|
defaultBeelineInstallLayout: () => defaultBeelineInstallLayout,
|
|
293
293
|
describeIdentity: () => describeIdentity,
|
|
294
|
+
discoveredBeelineInstallLayout: () => discoveredBeelineInstallLayout,
|
|
294
295
|
hostPlatformKey: () => hostPlatformKey,
|
|
295
296
|
normalizeLegacyBundleShape: () => normalizeLegacyBundleShape,
|
|
296
297
|
readInstalledBundleIdentity: () => readInstalledBundleIdentity,
|
|
@@ -345,6 +346,15 @@ function defaultBeelineInstallLayout(env = process.env) {
|
|
|
345
346
|
const home = env.HOME?.trim() || homedir9();
|
|
346
347
|
return anchorLayout(resolve21(home, ".local", "lib", "beeline"));
|
|
347
348
|
}
|
|
349
|
+
function discoveredBeelineInstallLayout(env = process.env) {
|
|
350
|
+
const explicitAnchor = env.BEELINE_INSTALL_LIB_DIR?.trim();
|
|
351
|
+
if (explicitAnchor)
|
|
352
|
+
return anchorLayout(explicitAnchor);
|
|
353
|
+
const explicitBinDir = env.BEELINE_INSTALL_DIR?.trim();
|
|
354
|
+
if (explicitBinDir)
|
|
355
|
+
return anchorLayout(resolve21(dirname9(resolve21(explicitBinDir)), "lib", "beeline"));
|
|
356
|
+
return defaultBeelineInstallLayout(env);
|
|
357
|
+
}
|
|
348
358
|
function hostPlatformKey() {
|
|
349
359
|
const os = process.platform === "linux" ? "linux" : process.platform === "darwin" ? "darwin" : "";
|
|
350
360
|
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : "";
|
|
@@ -639,27 +649,38 @@ async function normalizeLegacyBundleShape(bundleDir) {
|
|
|
639
649
|
}
|
|
640
650
|
}
|
|
641
651
|
async function writeBinForwarders(layout, activeBundleRoot) {
|
|
642
|
-
|
|
652
|
+
const entries = [
|
|
653
|
+
...FORWARDER_TOOLS.map((tool) => [tool, tool]),
|
|
654
|
+
...Object.entries(FORWARDER_ALIASES).map(([alias, tool]) => [alias, tool])
|
|
655
|
+
];
|
|
656
|
+
for (const [name, tool] of entries) {
|
|
643
657
|
const target = join7(activeBundleRoot, "bin", tool);
|
|
644
658
|
try {
|
|
645
659
|
await access(target, fsConstants.X_OK);
|
|
646
660
|
} catch {
|
|
647
661
|
continue;
|
|
648
662
|
}
|
|
649
|
-
await replaceFile(join7(layout.binDir,
|
|
663
|
+
await replaceFile(join7(layout.binDir, name), forwarderScript(tool), 493);
|
|
650
664
|
}
|
|
651
665
|
}
|
|
652
666
|
async function repairInstallForwarders(layout, opts = {}) {
|
|
653
667
|
if (await pathKind(layout.libDir) !== "symlink")
|
|
654
668
|
return false;
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
669
|
+
const forwarderHealthy = async (name, tool) => {
|
|
670
|
+
let current;
|
|
671
|
+
try {
|
|
672
|
+
current = await readFile9(join7(layout.binDir, name), "utf8");
|
|
673
|
+
} catch {
|
|
674
|
+
current = void 0;
|
|
675
|
+
}
|
|
676
|
+
return current === forwarderScript(tool);
|
|
677
|
+
};
|
|
678
|
+
let healthy = await forwarderHealthy("beeline", "beeline");
|
|
679
|
+
for (const [alias, tool] of Object.entries(FORWARDER_ALIASES)) {
|
|
680
|
+
if (!await forwarderHealthy(alias, tool))
|
|
681
|
+
healthy = false;
|
|
661
682
|
}
|
|
662
|
-
if (
|
|
683
|
+
if (healthy)
|
|
663
684
|
return false;
|
|
664
685
|
await mkdir13(layout.binDir, { recursive: true });
|
|
665
686
|
await writeBinForwarders(layout, layout.libDir);
|
|
@@ -741,7 +762,7 @@ function describeIdentity(identity) {
|
|
|
741
762
|
parts.push(identity.commit.slice(0, 12));
|
|
742
763
|
return parts.join(" ") || "unknown";
|
|
743
764
|
}
|
|
744
|
-
var RELEASES_SEGMENT, BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, FORWARDER_TOOLS, LEGACY_FLAT_BUNDLE_FILES, DEFAULT_UPDATE_CONFIRM_WINDOW_MS, SelfUpdateManager;
|
|
765
|
+
var RELEASES_SEGMENT, BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, FORWARDER_TOOLS, FORWARDER_ALIASES, LEGACY_FLAT_BUNDLE_FILES, DEFAULT_UPDATE_CONFIRM_WINDOW_MS, SelfUpdateManager;
|
|
745
766
|
var init_self_update = __esm({
|
|
746
767
|
"apps/body/dist/self-update.js"() {
|
|
747
768
|
"use strict";
|
|
@@ -750,6 +771,9 @@ var init_self_update = __esm({
|
|
|
750
771
|
BUNDLE_ENTRYPOINT = "lib/beeline/beeline-cli.mjs";
|
|
751
772
|
PI_MCP_ADAPTER_ENTRYPOINT = "lib/beeline/pi-mcp-adapter.mjs";
|
|
752
773
|
FORWARDER_TOOLS = ["beeline", "buzz-agent", "buzz-dev-mcp", "beeline-readonly-mcp"];
|
|
774
|
+
FORWARDER_ALIASES = {
|
|
775
|
+
usebeeline: "beeline"
|
|
776
|
+
};
|
|
753
777
|
LEGACY_FLAT_BUNDLE_FILES = [
|
|
754
778
|
"beeline-cli.mjs",
|
|
755
779
|
"beeline-readonly-mcp.mjs",
|
|
@@ -956,7 +980,7 @@ var init_self_update = __esm({
|
|
|
956
980
|
|
|
957
981
|
// apps/body/dist/cli.js
|
|
958
982
|
import { dirname as dirname15, resolve as resolve29 } from "node:path";
|
|
959
|
-
import { readFile as readFile14, unlink as
|
|
983
|
+
import { readFile as readFile14, unlink as unlink5, writeFile as writeFile15 } from "node:fs/promises";
|
|
960
984
|
import { stdin as stdin4, stdout as stdout5 } from "node:process";
|
|
961
985
|
|
|
962
986
|
// node_modules/@clack/core/dist/index.mjs
|
|
@@ -2951,13 +2975,17 @@ function parseSandboxMaskEnv(env) {
|
|
|
2951
2975
|
return entries.length ? entries : void 0;
|
|
2952
2976
|
}
|
|
2953
2977
|
|
|
2954
|
-
//
|
|
2978
|
+
// packages/api-contract/dist/agent-access.js
|
|
2955
2979
|
var AGENT_ACCESS_POLICIES = ["everyone", "creator", "allowlist"];
|
|
2956
|
-
var
|
|
2957
|
-
var LEGACY_ACCESS_POLICY = "everyone";
|
|
2980
|
+
var DEFAULT_AGENT_ACCESS_POLICY = "everyone";
|
|
2958
2981
|
function isAgentAccessPolicy(value) {
|
|
2959
2982
|
return AGENT_ACCESS_POLICIES.includes(value);
|
|
2960
2983
|
}
|
|
2984
|
+
var ACCESS_NOTICE_WINDOW_MS = 10 * 6e4;
|
|
2985
|
+
|
|
2986
|
+
// apps/body/dist/access-policy.js
|
|
2987
|
+
var DEFAULT_ACCESS_POLICY = DEFAULT_AGENT_ACCESS_POLICY;
|
|
2988
|
+
var LEGACY_ACCESS_POLICY = "everyone";
|
|
2961
2989
|
var ACCESS_REFUSAL_WINDOW_MS = 60 * 60 * 1e3;
|
|
2962
2990
|
function isSenderPermitted(policy, senderPubkey, ownerPubkey, allowlist = void 0) {
|
|
2963
2991
|
if (!senderPubkey)
|
|
@@ -13280,12 +13308,12 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
|
|
|
13280
13308
|
}
|
|
13281
13309
|
|
|
13282
13310
|
// apps/body/dist/room-runtime.js
|
|
13283
|
-
import { execFile as
|
|
13311
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
13284
13312
|
import { createHash as createHash4 } from "node:crypto";
|
|
13285
13313
|
import { existsSync as existsSync4, mkdirSync } from "node:fs";
|
|
13286
13314
|
import { mkdir as mkdir11, rm as rm4 } from "node:fs/promises";
|
|
13287
13315
|
import { dirname as dirname6, resolve as resolve19 } from "node:path";
|
|
13288
|
-
import { promisify as
|
|
13316
|
+
import { promisify as promisify4 } from "node:util";
|
|
13289
13317
|
|
|
13290
13318
|
// apps/body/dist/grant-runner.js
|
|
13291
13319
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -14125,11 +14153,11 @@ var GrantRunnerServer = class {
|
|
|
14125
14153
|
};
|
|
14126
14154
|
|
|
14127
14155
|
// apps/body/dist/monolith-corner-turn.js
|
|
14128
|
-
import { execFile as
|
|
14156
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
14129
14157
|
import { mkdir as mkdir9 } from "node:fs/promises";
|
|
14130
14158
|
import { homedir as homedir6 } from "node:os";
|
|
14131
14159
|
import { join as join5 } from "node:path";
|
|
14132
|
-
import { promisify as
|
|
14160
|
+
import { promisify as promisify3 } from "node:util";
|
|
14133
14161
|
|
|
14134
14162
|
// apps/body/dist/agent-home.js
|
|
14135
14163
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
@@ -14177,7 +14205,7 @@ function isAgentPairingCode(value) {
|
|
|
14177
14205
|
var USING_BEELINE_SKILL_NAME = "using-beeline";
|
|
14178
14206
|
var BEELINE_ROOM_CAPABILITIES = [
|
|
14179
14207
|
"The repository filesystem is read-only in this Room session.",
|
|
14180
|
-
"You may address any Room member, including another agent, by writing @name in your reply; the server routes that mention to them.",
|
|
14208
|
+
"You may address any Room member, including another agent, by writing @name in your reply; the server routes that mention to them. Each turn prompt lists the Room members and the exact spelling that tags each one - use those spellings, and never guess or reuse one from an older message.",
|
|
14181
14209
|
"Tag another agent only when you need something from them: a question, a handoff, a task. Never tag to acknowledge, agree, or say you are ready. If nothing is actionable, do not reply.",
|
|
14182
14210
|
"Tag the user only when you need a decision or input, or when the task they asked for is finished. Never tag for progress, acknowledgement, or questions the transcript already answers.",
|
|
14183
14211
|
"Every MCP server mounted into this session is approved tool by tool - use operator and host tools freely; the read-only filesystem sandbox is the boundary, not a tool list. Network web search is enabled.",
|
|
@@ -15931,6 +15959,55 @@ export default async function (pi) {
|
|
|
15931
15959
|
}
|
|
15932
15960
|
`;
|
|
15933
15961
|
|
|
15962
|
+
// apps/body/dist/corner-branch-sync.js
|
|
15963
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
15964
|
+
import { promisify as promisify2 } from "node:util";
|
|
15965
|
+
var execFileAsync2 = promisify2(execFile3);
|
|
15966
|
+
var CornerBranchDivergedError = class extends Error {
|
|
15967
|
+
name = "CornerBranchDivergedError";
|
|
15968
|
+
};
|
|
15969
|
+
async function syncCornerBranch(input) {
|
|
15970
|
+
const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
|
|
15971
|
+
...input.env ? { env: input.env } : {},
|
|
15972
|
+
maxBuffer: 4 * 1024 * 1024
|
|
15973
|
+
})).stdout);
|
|
15974
|
+
const remoteRef = `refs/remotes/origin/${input.featureBranch}`;
|
|
15975
|
+
const fetched = await git([
|
|
15976
|
+
"fetch",
|
|
15977
|
+
"origin",
|
|
15978
|
+
`+refs/heads/${input.featureBranch}:${remoteRef}`
|
|
15979
|
+
]).then(() => true, () => false);
|
|
15980
|
+
if (!fetched)
|
|
15981
|
+
return "unchanged";
|
|
15982
|
+
const remote = await git(["rev-parse", remoteRef]).then((value) => value.trim(), () => "");
|
|
15983
|
+
if (!remote)
|
|
15984
|
+
return "unchanged";
|
|
15985
|
+
const local = (await git(["rev-parse", "HEAD"])).trim();
|
|
15986
|
+
if (local === remote)
|
|
15987
|
+
return "unchanged";
|
|
15988
|
+
if (await contains(git, local, remote))
|
|
15989
|
+
return "unchanged";
|
|
15990
|
+
const behind = await contains(git, remote, local);
|
|
15991
|
+
try {
|
|
15992
|
+
await git(behind ? ["merge", "--ff-only", remote] : ["rebase", remote]);
|
|
15993
|
+
return behind ? "fast-forwarded" : "rebased";
|
|
15994
|
+
} catch (error) {
|
|
15995
|
+
await git(["rebase", "--abort"]).catch(() => void 0);
|
|
15996
|
+
throw new CornerBranchDivergedError(divergedReason(input.featureBranch, error));
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15999
|
+
async function contains(git, head, ancestor) {
|
|
16000
|
+
return git(["merge-base", "--is-ancestor", ancestor, head]).then(() => true, () => false);
|
|
16001
|
+
}
|
|
16002
|
+
function divergedReason(featureBranch, error) {
|
|
16003
|
+
const detail = String(error?.stderr ?? "").split("\n").map((line) => line.trim()).find((line) => /conflict|could not|error:/i.test(line));
|
|
16004
|
+
return [
|
|
16005
|
+
`could not rebase onto origin/${featureBranch}: another agent pushed to this corner's branch`,
|
|
16006
|
+
detail ? ` (${detail})` : "",
|
|
16007
|
+
". Nothing was pushed; the branch needs a person."
|
|
16008
|
+
].join("").slice(0, 200);
|
|
16009
|
+
}
|
|
16010
|
+
|
|
15934
16011
|
// apps/body/dist/room-session.js
|
|
15935
16012
|
import { resolve as resolve15 } from "node:path";
|
|
15936
16013
|
|
|
@@ -16740,7 +16817,7 @@ process.exit(result.status ?? 1);
|
|
|
16740
16817
|
}
|
|
16741
16818
|
|
|
16742
16819
|
// apps/body/dist/monolith-corner-turn.js
|
|
16743
|
-
var
|
|
16820
|
+
var execFileAsync3 = promisify3(execFile4);
|
|
16744
16821
|
var TOOL_ARGUMENT_MAX_BYTES = 1200;
|
|
16745
16822
|
var TOOL_OUTPUT_MAX_BYTES = 3200;
|
|
16746
16823
|
var TOOL_PATH_LIMIT = 12;
|
|
@@ -16844,7 +16921,7 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
|
|
|
16844
16921
|
let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
|
|
16845
16922
|
if (isSuccessfulCommit(call)) {
|
|
16846
16923
|
try {
|
|
16847
|
-
const shown = await
|
|
16924
|
+
const shown = await execFileAsync3("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
|
|
16848
16925
|
const lines = shown.stdout.split(/\r?\n/);
|
|
16849
16926
|
const subject = oneLine(lines.shift() ?? "commit");
|
|
16850
16927
|
const files = new Set(lines.map(oneLine).filter(Boolean));
|
|
@@ -16900,6 +16977,10 @@ var MonolithCornerTurnLoop = class {
|
|
|
16900
16977
|
/** Operator-local turn traces; built once when the daemon configured a directory. */
|
|
16901
16978
|
turnTraceSink;
|
|
16902
16979
|
memberNames = /* @__PURE__ */ new Map();
|
|
16980
|
+
/** Agent identities in this Workspace, so a mention can be told from a human's. */
|
|
16981
|
+
agentMembers = /* @__PURE__ */ new Set();
|
|
16982
|
+
/** The member agent that answered in this corner last (`carriesCorner`). */
|
|
16983
|
+
carrier;
|
|
16903
16984
|
/** The last server check state that started a turn; the same state never starts another. */
|
|
16904
16985
|
lastChecksState;
|
|
16905
16986
|
constructor(options) {
|
|
@@ -16941,6 +17022,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
16941
17022
|
workspaceId: this.options.workspaceId
|
|
16942
17023
|
});
|
|
16943
17024
|
this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
17025
|
+
this.agentMembers = new Set(roster.members.filter((member) => member.kind === "agent").map((member) => member.identityId));
|
|
16944
17026
|
return roster;
|
|
16945
17027
|
}
|
|
16946
17028
|
/**
|
|
@@ -17017,8 +17099,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
17017
17099
|
GITHUB_TOKEN: this.options.githubToken
|
|
17018
17100
|
};
|
|
17019
17101
|
if (this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
|
|
17020
|
-
const gitBinary = (await
|
|
17021
|
-
const ghBinary = await
|
|
17102
|
+
const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
|
|
17103
|
+
const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
|
|
17022
17104
|
githubEnv = await installCornerGitHubWrappers({
|
|
17023
17105
|
root: this.options.config.agentHomeRoot,
|
|
17024
17106
|
runtimeConfigPath: this.options.config.runtimeConfigPath,
|
|
@@ -17123,6 +17205,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
17123
17205
|
personaInstructions,
|
|
17124
17206
|
`You are in an isolated git worktree on ${this.options.featureBranch}, targeting ${this.options.targetBranch}.`,
|
|
17125
17207
|
"Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
|
|
17208
|
+
`This corner is shared: any of its member agents may be addressed in it and work on ${this.options.featureBranch}. Run git pull --rebase origin ${this.options.featureBranch} before you push, and never force-push it.`,
|
|
17126
17209
|
"PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
|
|
17127
17210
|
'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
|
|
17128
17211
|
"Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
|
|
@@ -17222,6 +17305,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
17222
17305
|
...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
|
|
17223
17306
|
} : void 0;
|
|
17224
17307
|
this.currentTurn = { requestId, ...requester ? { requester } : {} };
|
|
17308
|
+
this.carrier = this.agent.publicKey;
|
|
17225
17309
|
const trace = this.beginTurnTrace(requestId);
|
|
17226
17310
|
try {
|
|
17227
17311
|
await withTurnReceiptHeartbeat(api, {
|
|
@@ -17238,6 +17322,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
17238
17322
|
if (this.forcedStop)
|
|
17239
17323
|
throw new Error("corner turn stopped for daemon handoff");
|
|
17240
17324
|
this.busy = true;
|
|
17325
|
+
await this.syncBranch();
|
|
17241
17326
|
const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
|
|
17242
17327
|
api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
|
|
17243
17328
|
this.roster(),
|
|
@@ -17361,6 +17446,63 @@ ${trigger}`,
|
|
|
17361
17446
|
this.currentTurn = void 0;
|
|
17362
17447
|
}
|
|
17363
17448
|
}
|
|
17449
|
+
/** Whether this agent opened the corner. A corner with no recorded opener
|
|
17450
|
+
* behaves exactly as it did before members could carry it. */
|
|
17451
|
+
isOpener() {
|
|
17452
|
+
return !this.options.openedBy || this.options.openedBy === this.agent.publicKey;
|
|
17453
|
+
}
|
|
17454
|
+
/**
|
|
17455
|
+
* Whether this agent is the one carrying the corner right now.
|
|
17456
|
+
*
|
|
17457
|
+
* Every member agent polls the corner, but its lifecycle — a server check
|
|
17458
|
+
* note, a close request answered with work — is ONE fact and must start ONE
|
|
17459
|
+
* turn, not one per member (the "one check turn per changed server state"
|
|
17460
|
+
* rule). The carrier is whoever answered in the corner last, which is the
|
|
17461
|
+
* opener until a human hands the work to someone else.
|
|
17462
|
+
*/
|
|
17463
|
+
carriesCorner() {
|
|
17464
|
+
return (this.carrier ?? this.options.openedBy ?? this.agent.publicKey) === this.agent.publicKey;
|
|
17465
|
+
}
|
|
17466
|
+
/** Notes an agent's durable message as the corner changing hands. */
|
|
17467
|
+
noteCarrier(authorId) {
|
|
17468
|
+
if (this.agentMembers.has(authorId))
|
|
17469
|
+
this.carrier = authorId;
|
|
17470
|
+
}
|
|
17471
|
+
/**
|
|
17472
|
+
* Whether a human message in this corner is addressed to THIS agent.
|
|
17473
|
+
*
|
|
17474
|
+
* A corner now runs like a Room — every member agent polls it — so an
|
|
17475
|
+
* unrouted message would start one turn per member on one branch. A mention
|
|
17476
|
+
* routes: the mentioned agent answers and nobody else. A message that names
|
|
17477
|
+
* no agent at all keeps the old behaviour and falls to the opener, which is
|
|
17478
|
+
* every single-agent corner ever opened.
|
|
17479
|
+
*/
|
|
17480
|
+
async addressesThisAgent(item) {
|
|
17481
|
+
if (item.mentionIds.includes(this.agent.publicKey))
|
|
17482
|
+
return true;
|
|
17483
|
+
if (!item.mentionIds.length)
|
|
17484
|
+
return this.carriesCorner();
|
|
17485
|
+
await this.roster().catch(() => void 0);
|
|
17486
|
+
return item.mentionIds.some((id) => this.agentMembers.has(id)) ? false : this.carriesCorner();
|
|
17487
|
+
}
|
|
17488
|
+
/**
|
|
17489
|
+
* Bring this worktree onto the corner's branch as GitHub currently has it,
|
|
17490
|
+
* before any work is done on top of it.
|
|
17491
|
+
*
|
|
17492
|
+
* The branch is the shared artifact: another member agent may have pushed to
|
|
17493
|
+
* it since this helper last looked, and a first touch of a corner this
|
|
17494
|
+
* helper did not open starts from whatever `room-runtime.ts` restored. A
|
|
17495
|
+
* divergence this cannot rebase away is raised, not pushed over — the turn
|
|
17496
|
+
* fails with that sentence and the server inscribes it in the corner.
|
|
17497
|
+
*/
|
|
17498
|
+
async syncBranch() {
|
|
17499
|
+
const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => this.options.githubToken);
|
|
17500
|
+
await syncCornerBranch({
|
|
17501
|
+
worktreePath: this.options.worktreePath,
|
|
17502
|
+
featureBranch: this.options.featureBranch,
|
|
17503
|
+
env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token, GIT_TERMINAL_PROMPT: "0" }
|
|
17504
|
+
});
|
|
17505
|
+
}
|
|
17364
17506
|
/** The server's check state for this head, or the notes' own verdict when the server carries none. */
|
|
17365
17507
|
async checksState(notes) {
|
|
17366
17508
|
try {
|
|
@@ -17379,8 +17521,12 @@ ${trigger}`,
|
|
|
17379
17521
|
const { api, cornerId, signal } = this.options;
|
|
17380
17522
|
let cursor3 = (await api.execute("getRoomInbox", { roomId: cornerId, startAtLatest: true })).cursor;
|
|
17381
17523
|
const history = await api.execute("getRoomConversation", { roomId: cornerId, limit: 200 });
|
|
17524
|
+
await this.roster().catch(() => void 0);
|
|
17525
|
+
for (const item of history.items)
|
|
17526
|
+
if (item.type === "message")
|
|
17527
|
+
this.noteCarrier(item.authorId);
|
|
17382
17528
|
const durableAgentReplies = history.items.filter((item) => item.type === "message" && item.authorId === this.agent.publicKey && item.body.trim() !== this.options.objective.trim());
|
|
17383
|
-
if (durableAgentReplies.length === 0) {
|
|
17529
|
+
if (durableAgentReplies.length === 0 && this.isOpener()) {
|
|
17384
17530
|
await this.prompt(history.items.find((item) => item.requestId)?.requestId ?? cornerId.replaceAll("-", ""), this.options.objective);
|
|
17385
17531
|
}
|
|
17386
17532
|
try {
|
|
@@ -17398,8 +17544,11 @@ ${trigger}`,
|
|
|
17398
17544
|
const checkNotes = [];
|
|
17399
17545
|
for (const item of inbox.items) {
|
|
17400
17546
|
if (item.type === "message") {
|
|
17547
|
+
this.noteCarrier(item.authorId);
|
|
17401
17548
|
if (item.authorId === this.agent.publicKey)
|
|
17402
17549
|
continue;
|
|
17550
|
+
if (!await this.addressesThisAgent(item))
|
|
17551
|
+
continue;
|
|
17403
17552
|
const authority = await api.execute("getRoomAuthority", {
|
|
17404
17553
|
roomId: cornerId,
|
|
17405
17554
|
principalId: item.authorId
|
|
@@ -17424,7 +17573,7 @@ This answers your grant request; resume the paused work. If approved and it is a
|
|
|
17424
17573
|
if (completedCheckNote(item))
|
|
17425
17574
|
checkNotes.push(item);
|
|
17426
17575
|
}
|
|
17427
|
-
if (checkNotes.length) {
|
|
17576
|
+
if (checkNotes.length && this.carriesCorner()) {
|
|
17428
17577
|
const state = await this.checksState(checkNotes);
|
|
17429
17578
|
if (state && state !== "pending" && state !== this.lastChecksState) {
|
|
17430
17579
|
this.lastChecksState = state;
|
|
@@ -17500,7 +17649,13 @@ function isRoomMcpPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_S
|
|
|
17500
17649
|
return isMountedMcpToolPermissionRequest(request, mountedServers);
|
|
17501
17650
|
}
|
|
17502
17651
|
function roomPrincipalMayAddressAgent(authority, humanPermitted) {
|
|
17503
|
-
|
|
17652
|
+
if (!authority.member)
|
|
17653
|
+
return false;
|
|
17654
|
+
if (authority.principalKind === "agent")
|
|
17655
|
+
return true;
|
|
17656
|
+
if (authority.principalKind !== "human")
|
|
17657
|
+
return false;
|
|
17658
|
+
return authority.mayAddressAgent ?? humanPermitted;
|
|
17504
17659
|
}
|
|
17505
17660
|
function isScheduledPrompt(item, agentId) {
|
|
17506
17661
|
if (item.type !== "system" || !item.mentionIds.includes(agentId))
|
|
@@ -17547,6 +17702,27 @@ function pendingGrantToolCall(call) {
|
|
|
17547
17702
|
return false;
|
|
17548
17703
|
return /pending, card posted/i.test(typeof call.content === "string" ? call.content : JSON.stringify(call.content ?? ""));
|
|
17549
17704
|
}
|
|
17705
|
+
function roomMentionDirectory(roster, selfId) {
|
|
17706
|
+
const rows = [];
|
|
17707
|
+
for (const member of roster.members) {
|
|
17708
|
+
if (member.identityId === selfId)
|
|
17709
|
+
continue;
|
|
17710
|
+
const handle = member.handle?.trim().replace(/^@/, "");
|
|
17711
|
+
const name = member.name?.trim() ?? "";
|
|
17712
|
+
const alias = handle || name;
|
|
17713
|
+
if (!alias)
|
|
17714
|
+
continue;
|
|
17715
|
+
const kind = member.kind === "agent" ? "agent" : "person";
|
|
17716
|
+
rows.push(`- @${alias}${name && name !== alias ? ` \u2014 ${name}` : ""} (${kind})`);
|
|
17717
|
+
}
|
|
17718
|
+
if (!rows.length)
|
|
17719
|
+
return "";
|
|
17720
|
+
return [
|
|
17721
|
+
"Room members, and the exact spelling that tags each one:",
|
|
17722
|
+
...rows,
|
|
17723
|
+
"Write a tag exactly as spelled here. An @name spelled any other way is plain text: it reaches nobody, and nobody is told it was meant for them. Never invent a handle, shorten one, or copy an @name out of the conversation \u2014 old messages carry spellings that no longer exist."
|
|
17724
|
+
].join("\n");
|
|
17725
|
+
}
|
|
17550
17726
|
function escapeRegExp(value) {
|
|
17551
17727
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17552
17728
|
}
|
|
@@ -18057,6 +18233,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
18057
18233
|
"If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
|
|
18058
18234
|
"If it was declined, try another way or say plainly what you cannot do."
|
|
18059
18235
|
].join(" ") : "",
|
|
18236
|
+
roomMentionDirectory(roster, this.agent.publicKey),
|
|
18060
18237
|
[
|
|
18061
18238
|
"Write only the substantive Room message you want the human to read.",
|
|
18062
18239
|
"Do not repeat or paraphrase these instructions.",
|
|
@@ -18681,8 +18858,77 @@ var ROOM_JOIN_CONCURRENCY = 4;
|
|
|
18681
18858
|
var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
|
|
18682
18859
|
var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
|
|
18683
18860
|
var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
|
|
18684
|
-
function shouldPostInitialCornerWorkingState(restore) {
|
|
18685
|
-
return !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
|
|
18861
|
+
function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
|
|
18862
|
+
return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
|
|
18863
|
+
}
|
|
18864
|
+
async function materializeCornerWorktree(input) {
|
|
18865
|
+
const remote = roomCheckoutRemote(input.remote);
|
|
18866
|
+
const repositoryHash = createHash4("sha256").update(remote).digest("hex").slice(0, 24);
|
|
18867
|
+
const gitCommonDir = resolve19(input.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
|
|
18868
|
+
const path = resolve19(input.supervisorRoot, "beeline", "corners", input.cornerId);
|
|
18869
|
+
await mkdir11(dirname6(gitCommonDir), { recursive: true, mode: 448 });
|
|
18870
|
+
await mkdir11(dirname6(path), { recursive: true, mode: 448 });
|
|
18871
|
+
const authEnv = githubGitEnv(input.token);
|
|
18872
|
+
if (!existsSync4(resolve19(gitCommonDir, "HEAD"))) {
|
|
18873
|
+
await execFileAsync4("git", ["clone", "--bare", remote, gitCommonDir], {
|
|
18874
|
+
env: authEnv,
|
|
18875
|
+
maxBuffer: 4 * 1024 * 1024
|
|
18876
|
+
});
|
|
18877
|
+
}
|
|
18878
|
+
await execFileAsync4("git", [
|
|
18879
|
+
`--git-dir=${gitCommonDir}`,
|
|
18880
|
+
"fetch",
|
|
18881
|
+
"--prune",
|
|
18882
|
+
"origin",
|
|
18883
|
+
`+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
|
|
18884
|
+
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
|
|
18885
|
+
const restored = await execFileAsync4("git", [
|
|
18886
|
+
`--git-dir=${gitCommonDir}`,
|
|
18887
|
+
"fetch",
|
|
18888
|
+
"origin",
|
|
18889
|
+
`+refs/heads/${input.featureBranch}:refs/remotes/origin/${input.featureBranch}`
|
|
18890
|
+
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 }).then(() => true, () => false);
|
|
18891
|
+
if (!existsSync4(resolve19(path, ".git"))) {
|
|
18892
|
+
await rm4(path, { recursive: true, force: true });
|
|
18893
|
+
await execFileAsync4("git", [
|
|
18894
|
+
`--git-dir=${gitCommonDir}`,
|
|
18895
|
+
"worktree",
|
|
18896
|
+
"add",
|
|
18897
|
+
"-B",
|
|
18898
|
+
input.featureBranch,
|
|
18899
|
+
path,
|
|
18900
|
+
restored ? `refs/remotes/origin/${input.featureBranch}` : `refs/remotes/origin/${input.targetBranch}`
|
|
18901
|
+
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
|
|
18902
|
+
}
|
|
18903
|
+
await execFileAsync4("git", [
|
|
18904
|
+
`--git-dir=${gitCommonDir}`,
|
|
18905
|
+
"config",
|
|
18906
|
+
"extensions.worktreeConfig",
|
|
18907
|
+
"true"
|
|
18908
|
+
]);
|
|
18909
|
+
await execFileAsync4("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
|
|
18910
|
+
await execFileAsync4("git", [
|
|
18911
|
+
"-C",
|
|
18912
|
+
path,
|
|
18913
|
+
"config",
|
|
18914
|
+
"--worktree",
|
|
18915
|
+
"credential.https://github.com.helper",
|
|
18916
|
+
"!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
|
|
18917
|
+
]);
|
|
18918
|
+
await execFileAsync4("git", ["-C", path, "config", "--worktree", "user.name", input.committer.name]);
|
|
18919
|
+
await execFileAsync4("git", [
|
|
18920
|
+
"-C",
|
|
18921
|
+
path,
|
|
18922
|
+
"config",
|
|
18923
|
+
"--worktree",
|
|
18924
|
+
"user.email",
|
|
18925
|
+
`${input.committer.publicKey.slice(0, 16)}@users.noreply.github.com`
|
|
18926
|
+
]);
|
|
18927
|
+
const top = await execFileAsync4("git", ["-C", path, "rev-parse", "--show-toplevel"]);
|
|
18928
|
+
if (resolve19(top.stdout.trim()) !== resolve19(path)) {
|
|
18929
|
+
throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
|
|
18930
|
+
}
|
|
18931
|
+
return { path, gitCommonDir };
|
|
18686
18932
|
}
|
|
18687
18933
|
function reconcileRetryMs(error, pollMs) {
|
|
18688
18934
|
const match = String(error).match(/retry in\s+(\d+)s/i);
|
|
@@ -18697,7 +18943,7 @@ async function mapWithConcurrency(values, limit, visit) {
|
|
|
18697
18943
|
}
|
|
18698
18944
|
}));
|
|
18699
18945
|
}
|
|
18700
|
-
var
|
|
18946
|
+
var execFileAsync4 = promisify4(execFile5);
|
|
18701
18947
|
var RoomRuntimeCoordinator = class {
|
|
18702
18948
|
configPath;
|
|
18703
18949
|
baseConfig;
|
|
@@ -18705,6 +18951,8 @@ var RoomRuntimeCoordinator = class {
|
|
|
18705
18951
|
runtime;
|
|
18706
18952
|
running = /* @__PURE__ */ new Map();
|
|
18707
18953
|
startingCorners = /* @__PURE__ */ new Set();
|
|
18954
|
+
/** Corners whose start failure has already been said out loud, once each. */
|
|
18955
|
+
reportedCornerStartFailures = /* @__PURE__ */ new Set();
|
|
18708
18956
|
scheduler;
|
|
18709
18957
|
agent;
|
|
18710
18958
|
/** Parent ownership retained so a failed corner listing never authorizes removal. */
|
|
@@ -18830,7 +19078,8 @@ var RoomRuntimeCoordinator = class {
|
|
|
18830
19078
|
desired.add(corner.cornerId);
|
|
18831
19079
|
desiredCorners.set(corner.cornerId, {
|
|
18832
19080
|
cornerId: corner.cornerId,
|
|
18833
|
-
parentRoomId: room.roomId
|
|
19081
|
+
parentRoomId: room.roomId,
|
|
19082
|
+
...corner.createdBy ? { openedBy: corner.createdBy } : {}
|
|
18834
19083
|
});
|
|
18835
19084
|
}
|
|
18836
19085
|
}
|
|
@@ -18972,12 +19221,12 @@ var RoomRuntimeCoordinator = class {
|
|
|
18972
19221
|
const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
|
|
18973
19222
|
const env = token ? githubGitEnv(token.token) : process.env;
|
|
18974
19223
|
if (!existsSync4(resolve19(path, ".git"))) {
|
|
18975
|
-
await
|
|
19224
|
+
await execFileAsync4("git", ["clone", "--no-checkout", remote, path], {
|
|
18976
19225
|
env,
|
|
18977
19226
|
maxBuffer: 4 * 1024 * 1024
|
|
18978
19227
|
});
|
|
18979
19228
|
}
|
|
18980
|
-
await
|
|
19229
|
+
await execFileAsync4("git", [
|
|
18981
19230
|
"-C",
|
|
18982
19231
|
path,
|
|
18983
19232
|
"fetch",
|
|
@@ -18985,7 +19234,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
18985
19234
|
"origin",
|
|
18986
19235
|
`+refs/heads/${targetBranch}:refs/remotes/origin/${targetBranch}`
|
|
18987
19236
|
], { env, maxBuffer: 4 * 1024 * 1024 });
|
|
18988
|
-
await
|
|
19237
|
+
await execFileAsync4("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
|
|
18989
19238
|
env,
|
|
18990
19239
|
maxBuffer: 4 * 1024 * 1024
|
|
18991
19240
|
});
|
|
@@ -19028,7 +19277,8 @@ var RoomRuntimeCoordinator = class {
|
|
|
19028
19277
|
featureBranch,
|
|
19029
19278
|
token: granted.token
|
|
19030
19279
|
});
|
|
19031
|
-
|
|
19280
|
+
const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
|
|
19281
|
+
if (shouldPostInitialCornerWorkingState(restore, isOpener)) {
|
|
19032
19282
|
await this.options.daemonApi.execute("postCornerRemoteState", {
|
|
19033
19283
|
cornerId: corner.cornerId,
|
|
19034
19284
|
branch: featureBranch,
|
|
@@ -19045,6 +19295,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
19045
19295
|
...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
|
|
19046
19296
|
parentRoomId: corner.parentRoomId,
|
|
19047
19297
|
workspaceId: this.runtime.communityId,
|
|
19298
|
+
...corner.openedBy ? { openedBy: corner.openedBy } : {},
|
|
19048
19299
|
objective,
|
|
19049
19300
|
featureBranch,
|
|
19050
19301
|
targetBranch,
|
|
@@ -19086,79 +19337,60 @@ var RoomRuntimeCoordinator = class {
|
|
|
19086
19337
|
branch: featureBranch
|
|
19087
19338
|
}
|
|
19088
19339
|
});
|
|
19340
|
+
this.reportedCornerStartFailures.delete(corner.cornerId);
|
|
19089
19341
|
console.log(`[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${worktree.path}`);
|
|
19090
19342
|
} catch (error) {
|
|
19091
19343
|
console.error(`[thin-core] failed to start corner ${corner.cornerId}:`, error);
|
|
19344
|
+
await this.reportCornerStartFailure(corner.cornerId, error);
|
|
19092
19345
|
} finally {
|
|
19093
19346
|
this.startingCorners.delete(corner.cornerId);
|
|
19094
19347
|
}
|
|
19095
19348
|
}
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19349
|
+
/**
|
|
19350
|
+
* An agent addressed in a corner it then could not restore must not be
|
|
19351
|
+
* silent about it.
|
|
19352
|
+
*
|
|
19353
|
+
* The corner never starts, so no turn ever runs and nothing else in the
|
|
19354
|
+
* daemon has a Room to say it in. This posts a FAILED receipt against the
|
|
19355
|
+
* message that asked, which the server inscribes as `<agent> could not
|
|
19356
|
+
* answer · <reason>` in the corner itself. Once per corner per process: the
|
|
19357
|
+
* reconciliation heartbeat retries the start for as long as it keeps
|
|
19358
|
+
* failing, and the fact is worth saying once, not once a minute.
|
|
19359
|
+
*/
|
|
19360
|
+
async reportCornerStartFailure(cornerId, error) {
|
|
19361
|
+
if (this.reportedCornerStartFailures.has(cornerId))
|
|
19362
|
+
return;
|
|
19363
|
+
this.reportedCornerStartFailures.add(cornerId);
|
|
19364
|
+
try {
|
|
19365
|
+
const conversation = await this.options.daemonApi.execute("getRoomConversation", {
|
|
19366
|
+
roomId: cornerId,
|
|
19367
|
+
limit: 50
|
|
19108
19368
|
});
|
|
19369
|
+
const asked = [...conversation.items].reverse().find((item) => item.type === "message" && item.mentionIds.includes(this.agent.publicKey));
|
|
19370
|
+
if (!asked)
|
|
19371
|
+
return;
|
|
19372
|
+
await this.options.daemonApi.execute("postAgentTurnReceipt", {
|
|
19373
|
+
agentId: this.agent.publicKey,
|
|
19374
|
+
roomId: cornerId,
|
|
19375
|
+
requestId: asked.id,
|
|
19376
|
+
status: "failed",
|
|
19377
|
+
generationId: `${this.agent.publicKey}:${cornerId}`,
|
|
19378
|
+
reason: distillTurnFailureReason(error)
|
|
19379
|
+
});
|
|
19380
|
+
} catch (reportError) {
|
|
19381
|
+
console.error(`[thin-core] corner ${cornerId} start-failure report failed:`, reportError);
|
|
19109
19382
|
}
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
if (!existsSync4(resolve19(path, ".git"))) {
|
|
19118
|
-
await rm4(path, { recursive: true, force: true });
|
|
19119
|
-
await execFileAsync3("git", [
|
|
19120
|
-
`--git-dir=${gitCommonDir}`,
|
|
19121
|
-
"worktree",
|
|
19122
|
-
"add",
|
|
19123
|
-
"-B",
|
|
19124
|
-
input.featureBranch,
|
|
19125
|
-
path,
|
|
19126
|
-
`refs/remotes/origin/${input.targetBranch}`
|
|
19127
|
-
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
|
|
19128
|
-
}
|
|
19129
|
-
await execFileAsync3("git", [
|
|
19130
|
-
`--git-dir=${gitCommonDir}`,
|
|
19131
|
-
"config",
|
|
19132
|
-
"extensions.worktreeConfig",
|
|
19133
|
-
"true"
|
|
19134
|
-
]);
|
|
19135
|
-
await execFileAsync3("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
|
|
19136
|
-
await execFileAsync3("git", [
|
|
19137
|
-
"-C",
|
|
19138
|
-
path,
|
|
19139
|
-
"config",
|
|
19140
|
-
"--worktree",
|
|
19141
|
-
"credential.https://github.com.helper",
|
|
19142
|
-
"!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
|
|
19143
|
-
]);
|
|
19144
|
-
await execFileAsync3("git", ["-C", path, "config", "--worktree", "user.name", this.agent.name]);
|
|
19145
|
-
await execFileAsync3("git", [
|
|
19146
|
-
"-C",
|
|
19147
|
-
path,
|
|
19148
|
-
"config",
|
|
19149
|
-
"--worktree",
|
|
19150
|
-
"user.email",
|
|
19151
|
-
`${this.agent.publicKey.slice(0, 16)}@users.noreply.github.com`
|
|
19152
|
-
]);
|
|
19153
|
-
const top = await execFileAsync3("git", ["-C", path, "rev-parse", "--show-toplevel"]);
|
|
19154
|
-
if (resolve19(top.stdout.trim()) !== resolve19(path)) {
|
|
19155
|
-
throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
|
|
19156
|
-
}
|
|
19157
|
-
return { path, gitCommonDir };
|
|
19383
|
+
}
|
|
19384
|
+
async materializeCornerWorktree(input) {
|
|
19385
|
+
return materializeCornerWorktree({
|
|
19386
|
+
...input,
|
|
19387
|
+
supervisorRoot: this.runtime.supervisorRoot,
|
|
19388
|
+
committer: { name: this.agent.name, publicKey: this.agent.publicKey }
|
|
19389
|
+
});
|
|
19158
19390
|
}
|
|
19159
19391
|
async reapCornerWorktree(worktree) {
|
|
19160
19392
|
if (existsSync4(worktree.path)) {
|
|
19161
|
-
await
|
|
19393
|
+
await execFileAsync4("git", [
|
|
19162
19394
|
`--git-dir=${worktree.gitCommonDir}`,
|
|
19163
19395
|
"worktree",
|
|
19164
19396
|
"remove",
|
|
@@ -19328,13 +19560,13 @@ var ThinDaemonCore = class {
|
|
|
19328
19560
|
};
|
|
19329
19561
|
|
|
19330
19562
|
// apps/body/dist/systemd.js
|
|
19331
|
-
import { execFile as
|
|
19563
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
19332
19564
|
import { mkdir as mkdir12, readFile as readFile8, writeFile as writeFile8 } from "node:fs/promises";
|
|
19333
19565
|
import { homedir as homedir8 } from "node:os";
|
|
19334
19566
|
import { dirname as dirname7, resolve as resolve20 } from "node:path";
|
|
19335
19567
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
19336
|
-
import { promisify as
|
|
19337
|
-
var
|
|
19568
|
+
import { promisify as promisify5 } from "node:util";
|
|
19569
|
+
var execFileAsync5 = promisify5(execFile6);
|
|
19338
19570
|
var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
|
|
19339
19571
|
var DAEMON_DISTRESS_EXIT_STATUS = 77;
|
|
19340
19572
|
var UNKNOWN_AGENT_EXIT_STATUS = 79;
|
|
@@ -19387,7 +19619,7 @@ function systemdUserUnitPath(env = process.env) {
|
|
|
19387
19619
|
return resolve20(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
19388
19620
|
}
|
|
19389
19621
|
var runSystemctl = async (args) => {
|
|
19390
|
-
const result = await
|
|
19622
|
+
const result = await execFileAsync5("systemctl", ["--user", ...args], {
|
|
19391
19623
|
timeout: SYSTEMD_COMMAND_TIMEOUT_MS,
|
|
19392
19624
|
encoding: "utf8"
|
|
19393
19625
|
});
|
|
@@ -19453,7 +19685,7 @@ async function disableAgentService(publicKey, options = {}) {
|
|
|
19453
19685
|
async function notify(fields) {
|
|
19454
19686
|
if (process.env.BEELINE_MANAGED_BY_SYSTEMD !== "1")
|
|
19455
19687
|
return;
|
|
19456
|
-
await
|
|
19688
|
+
await execFileAsync5("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
|
|
19457
19689
|
}
|
|
19458
19690
|
async function extendSystemdStartTimeout(ms) {
|
|
19459
19691
|
await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
|
|
@@ -20414,8 +20646,10 @@ import { mkdir as mkdir16, rm as rm6, stat as stat2, writeFile as writeFile12 }
|
|
|
20414
20646
|
import { dirname as dirname12, resolve as resolve24 } from "node:path";
|
|
20415
20647
|
|
|
20416
20648
|
// apps/body/dist/update-rollback-alert.js
|
|
20417
|
-
import { mkdir as mkdir15, readFile as readFile11, rename as rename4, writeFile as writeFile11 } from "node:fs/promises";
|
|
20649
|
+
import { mkdir as mkdir15, readFile as readFile11, rename as rename4, unlink as unlink3, writeFile as writeFile11 } from "node:fs/promises";
|
|
20418
20650
|
import { dirname as dirname11, resolve as resolve23 } from "node:path";
|
|
20651
|
+
var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
20652
|
+
var lastLogged = /* @__PURE__ */ new Map();
|
|
20419
20653
|
function updateRollbackAlertPath(runtimeDir) {
|
|
20420
20654
|
return resolve23(runtimeDir, "update-rollback-alert.json");
|
|
20421
20655
|
}
|
|
@@ -20443,11 +20677,37 @@ async function readUpdateRollbackAlert(runtimeDir) {
|
|
|
20443
20677
|
return void 0;
|
|
20444
20678
|
}
|
|
20445
20679
|
}
|
|
20680
|
+
async function clearUpdateRollbackAlert(runtimeDir) {
|
|
20681
|
+
const existing = await readUpdateRollbackAlert(runtimeDir);
|
|
20682
|
+
if (!existing)
|
|
20683
|
+
return false;
|
|
20684
|
+
await unlink3(updateRollbackAlertPath(runtimeDir)).catch(() => void 0);
|
|
20685
|
+
lastLogged.delete(runtimeDir);
|
|
20686
|
+
return true;
|
|
20687
|
+
}
|
|
20688
|
+
async function clearUpdateRollbackAlertIfConfirmed(runtimeDir, loadedRelease) {
|
|
20689
|
+
if (!loadedRelease)
|
|
20690
|
+
return false;
|
|
20691
|
+
const existing = await readUpdateRollbackAlert(runtimeDir);
|
|
20692
|
+
if (!existing || existing.releaseId !== loadedRelease)
|
|
20693
|
+
return false;
|
|
20694
|
+
await unlink3(updateRollbackAlertPath(runtimeDir)).catch(() => void 0);
|
|
20695
|
+
lastLogged.delete(runtimeDir);
|
|
20696
|
+
return true;
|
|
20697
|
+
}
|
|
20446
20698
|
async function reportUpdateRollback(input) {
|
|
20447
20699
|
const pending = await readUpdateRollbackAlert(input.runtimeDir);
|
|
20448
|
-
if (!pending)
|
|
20700
|
+
if (!pending) {
|
|
20701
|
+
lastLogged.delete(input.runtimeDir);
|
|
20702
|
+
return false;
|
|
20703
|
+
}
|
|
20704
|
+
const now2 = input.now ?? Date.now();
|
|
20705
|
+
const last = lastLogged.get(input.runtimeDir);
|
|
20706
|
+
if (last && last.releaseId === pending.releaseId && now2 - last.loggedAt < REPORT_INTERVAL_MS) {
|
|
20449
20707
|
return false;
|
|
20708
|
+
}
|
|
20450
20709
|
console.error(`[thin-core] UPDATE ROLLBACK: ${pending.releaseId}; durable operator record: ` + updateRollbackAlertPath(input.runtimeDir));
|
|
20710
|
+
lastLogged.set(input.runtimeDir, { releaseId: pending.releaseId, loggedAt: now2 });
|
|
20451
20711
|
return true;
|
|
20452
20712
|
}
|
|
20453
20713
|
|
|
@@ -20972,12 +21232,14 @@ default 30s) with the same busy gate; BEELINE_UPDATE_DISABLE=1 turns the
|
|
|
20972
21232
|
automatic path off. \`beeline update\` always works.
|
|
20973
21233
|
`);
|
|
20974
21234
|
}
|
|
20975
|
-
function requireLayout() {
|
|
21235
|
+
async function requireLayout() {
|
|
20976
21236
|
const layout = beelineInstallLayout(process.env);
|
|
20977
|
-
if (
|
|
20978
|
-
|
|
20979
|
-
|
|
20980
|
-
|
|
21237
|
+
if (layout)
|
|
21238
|
+
return layout;
|
|
21239
|
+
const discovered = discoveredBeelineInstallLayout(process.env);
|
|
21240
|
+
if (await readInstalledBundleIdentity(discovered) !== void 0)
|
|
21241
|
+
return discovered;
|
|
21242
|
+
throw new Error("this host has no Beeline install; run `npx usebeeline connect` first.");
|
|
20981
21243
|
}
|
|
20982
21244
|
async function runningDaemonConfigPaths() {
|
|
20983
21245
|
const cwd = process.cwd();
|
|
@@ -21025,7 +21287,7 @@ async function runUpdateCommand(args) {
|
|
|
21025
21287
|
updateUsage();
|
|
21026
21288
|
return;
|
|
21027
21289
|
}
|
|
21028
|
-
const layout = requireLayout();
|
|
21290
|
+
const layout = await requireLayout();
|
|
21029
21291
|
const manifestUrlFlag = args.indexOf("--manifest-url");
|
|
21030
21292
|
const manifestUrl = manifestUrlFlag >= 0 && args[manifestUrlFlag + 1] ? args[manifestUrlFlag + 1] : resolveManifestUrl(process.env);
|
|
21031
21293
|
const checkOnly = args.includes("--check");
|
|
@@ -21152,6 +21414,7 @@ import { resolve as resolve26 } from "node:path";
|
|
|
21152
21414
|
var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
|
|
21153
21415
|
var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
|
|
21154
21416
|
var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
|
|
21417
|
+
var UPDATE_PROBE_RETRY_DELAY_MS = 2e3;
|
|
21155
21418
|
var ACP_ERROR_CODE = /\bACP error (-\d+):/;
|
|
21156
21419
|
var ACP_PROMPT_INACTIVITY = /\bACP session\/prompt timed out after \d+ms of inactivity\b/;
|
|
21157
21420
|
function isServerInternalCode(code) {
|
|
@@ -21275,8 +21538,7 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
21275
21538
|
});
|
|
21276
21539
|
const sessionTimeoutMs = input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_TIMEOUT_MS;
|
|
21277
21540
|
const sessionOpenTimeoutMs = input.sessionOpenTimeoutMs ?? input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS;
|
|
21278
|
-
|
|
21279
|
-
await client.start(sessionTimeoutMs);
|
|
21541
|
+
const openSessionAndPrompt = async () => {
|
|
21280
21542
|
const opened = await client.sessionNew({
|
|
21281
21543
|
cwd,
|
|
21282
21544
|
mcpServers: [],
|
|
@@ -21287,15 +21549,22 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
21287
21549
|
if (input.config.modelSelection) {
|
|
21288
21550
|
await applyAgentModelSelection(client, opened.sessionId, parseAdvertisedConfigOptions(opened.raw, input.config.modelSelection.model, isGrokAgentCommand(selectedAgent)), input.config.modelSelection);
|
|
21289
21551
|
}
|
|
21552
|
+
return {
|
|
21553
|
+
sessionId: opened.sessionId,
|
|
21554
|
+
served: await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS)
|
|
21555
|
+
};
|
|
21556
|
+
};
|
|
21557
|
+
try {
|
|
21558
|
+
await client.start(sessionTimeoutMs);
|
|
21290
21559
|
try {
|
|
21291
|
-
const
|
|
21560
|
+
const { sessionId, served } = await openSessionAndPrompt();
|
|
21292
21561
|
if (served.agentText.trim()) {
|
|
21293
21562
|
modelAnswer = { modelAnswer: "served" };
|
|
21294
21563
|
} else {
|
|
21295
21564
|
const explained = await explainEmptyAgentTurn({
|
|
21296
21565
|
agentLabel: command,
|
|
21297
21566
|
agentEnv,
|
|
21298
|
-
sessionId
|
|
21567
|
+
sessionId,
|
|
21299
21568
|
result: served
|
|
21300
21569
|
});
|
|
21301
21570
|
const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
|
|
@@ -21323,29 +21592,55 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
21323
21592
|
modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
|
|
21324
21593
|
}
|
|
21325
21594
|
}
|
|
21326
|
-
} catch (
|
|
21327
|
-
if (
|
|
21328
|
-
throw
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
|
|
21332
|
-
|
|
21595
|
+
} catch (initialError) {
|
|
21596
|
+
if (initialError instanceof UpdateFunctionalProbeError)
|
|
21597
|
+
throw initialError;
|
|
21598
|
+
let error = initialError;
|
|
21599
|
+
let detail = error instanceof Error ? error.message : String(error);
|
|
21600
|
+
let failure = classifyAcpTurnFailure(error);
|
|
21601
|
+
if (failure) {
|
|
21602
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, input.retryDelayMs ?? UPDATE_PROBE_RETRY_DELAY_MS));
|
|
21603
|
+
try {
|
|
21604
|
+
const retried = await openSessionAndPrompt();
|
|
21605
|
+
if (retried.served.agentText.trim()) {
|
|
21606
|
+
turnCompleted = true;
|
|
21607
|
+
modelAnswer = { modelAnswer: "served" };
|
|
21608
|
+
console.warn(`[body] update probe: the probe turn failed once (${detail}) but a fresh retry answered; treating the first failure as the provider's, not this bundle's`);
|
|
21609
|
+
error = void 0;
|
|
21610
|
+
}
|
|
21611
|
+
} catch (retryError) {
|
|
21612
|
+
const retryDetail = retryError instanceof Error ? retryError.message : String(retryError);
|
|
21613
|
+
const retryFailure = classifyAcpTurnFailure(retryError);
|
|
21614
|
+
if (!retryFailure || !sameAcpTurnFailure(failure, retryFailure)) {
|
|
21615
|
+
throw new UpdateFunctionalProbeError("turn-failed", retryDetail, {
|
|
21616
|
+
cause: retryError
|
|
21617
|
+
});
|
|
21618
|
+
}
|
|
21619
|
+
error = retryError;
|
|
21620
|
+
detail = retryDetail;
|
|
21621
|
+
failure = retryFailure;
|
|
21622
|
+
}
|
|
21333
21623
|
}
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21624
|
+
if (error === void 0) {
|
|
21625
|
+
} else if (!failure || !input.compareWithCurrentRelease) {
|
|
21626
|
+
throw new UpdateFunctionalProbeError("turn-failed", detail, { cause: error });
|
|
21627
|
+
} else {
|
|
21628
|
+
const current = await input.compareWithCurrentRelease({
|
|
21629
|
+
kind: "acp-turn-failure",
|
|
21630
|
+
reason: detail,
|
|
21631
|
+
failure
|
|
21632
|
+
});
|
|
21633
|
+
const currentFailure = current.kind === "unavailable" ? classifyAcpTurnFailure(current.reason) : void 0;
|
|
21634
|
+
if (!currentFailure || !sameAcpTurnFailure(failure, currentFailure)) {
|
|
21635
|
+
throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { cause: error });
|
|
21636
|
+
}
|
|
21637
|
+
console.warn(`[body] update probe: the probe turn failed the same way on this release and the current release (${detail}); that failure is not this bundle's doing, so the probe passes as inconclusive`);
|
|
21638
|
+
turnCompleted = false;
|
|
21639
|
+
modelAnswer = {
|
|
21640
|
+
modelAnswer: "unavailable",
|
|
21641
|
+
modelAnswerReason: `${detail} (the current release fails the same way)`
|
|
21642
|
+
};
|
|
21342
21643
|
}
|
|
21343
|
-
console.warn(`[body] update probe: the probe turn failed the same way on this release and the current release (${detail}); that failure is not this bundle's doing, so the probe passes as inconclusive`);
|
|
21344
|
-
turnCompleted = false;
|
|
21345
|
-
modelAnswer = {
|
|
21346
|
-
modelAnswer: "unavailable",
|
|
21347
|
-
modelAnswerReason: `${detail} (the current release fails the same way)`
|
|
21348
|
-
};
|
|
21349
21644
|
}
|
|
21350
21645
|
} catch (error) {
|
|
21351
21646
|
if (error instanceof UpdateFunctionalProbeError)
|
|
@@ -21525,7 +21820,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
|
|
|
21525
21820
|
}
|
|
21526
21821
|
|
|
21527
21822
|
// apps/body/dist/scratch-sweep.js
|
|
21528
|
-
import { lstat as lstat3, readdir as readdir6, rmdir, unlink as
|
|
21823
|
+
import { lstat as lstat3, readdir as readdir6, rmdir, unlink as unlink4 } from "node:fs/promises";
|
|
21529
21824
|
import { resolve as resolve28 } from "node:path";
|
|
21530
21825
|
var DEFAULT_SCRATCH_TTL_HOURS = 72;
|
|
21531
21826
|
var NEVER_SWEEP_SUBDIR_NAMES = new Set(HOME_SUBDIRS.filter((name) => name !== "tmp"));
|
|
@@ -21580,7 +21875,7 @@ async function removeStaleFiles(dir, cutoffMs, protectNamesHere) {
|
|
|
21580
21875
|
}
|
|
21581
21876
|
if (!stats.isFile() || stats.mtimeMs >= cutoffMs)
|
|
21582
21877
|
continue;
|
|
21583
|
-
await
|
|
21878
|
+
await unlink4(path).catch(() => void 0);
|
|
21584
21879
|
removedFiles += 1;
|
|
21585
21880
|
removedBytes += stats.size;
|
|
21586
21881
|
}
|
|
@@ -21739,6 +22034,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
21739
22034
|
pendingSuccessor = true;
|
|
21740
22035
|
}
|
|
21741
22036
|
loadedRelease = await activeReleaseId(layout);
|
|
22037
|
+
await clearUpdateRollbackAlertIfConfirmed(runtimeDir, loadedRelease);
|
|
21742
22038
|
loadedReleaseIdentity = await readInstalledBundleIdentity(layout);
|
|
21743
22039
|
config.daemonReleaseVersion = loadedReleaseIdentity?.version;
|
|
21744
22040
|
config.daemonSourceSha = loadedReleaseIdentity?.commit;
|
|
@@ -21807,6 +22103,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
21807
22103
|
}
|
|
21808
22104
|
functionalProof = gate.proof;
|
|
21809
22105
|
pendingSuccessor = false;
|
|
22106
|
+
await clearUpdateRollbackAlert(runtimeDir);
|
|
21810
22107
|
console.log(`[thin-core] successor functional probe passed on exact release ${loadedRelease}: ${functionalProof?.harness ?? "unknown"} session/new + turn` + (functionalProof?.modelAnswer === "unavailable" ? ` (model answer unavailable: ${functionalProof.modelAnswerReason})` : ""));
|
|
21811
22108
|
}
|
|
21812
22109
|
await clearDaemonStartFailures(runtimeDir);
|
|
@@ -21854,7 +22151,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
21854
22151
|
const pidPath = resolve29(dirname15(configPath), "daemon.pid");
|
|
21855
22152
|
const recorded = Number((await readFile14(pidPath, "utf8").catch(() => "")).trim());
|
|
21856
22153
|
if (recorded === process.pid) {
|
|
21857
|
-
await
|
|
22154
|
+
await unlink5(pidPath).catch(() => void 0);
|
|
21858
22155
|
}
|
|
21859
22156
|
}
|
|
21860
22157
|
}
|