usebeeline 0.0.40 → 0.0.44
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 +473 -69
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -489,7 +489,7 @@ function requiredBundlePaths() {
|
|
|
489
489
|
}
|
|
490
490
|
async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
491
491
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
492
|
-
const
|
|
492
|
+
const log2 = opts.logger ?? ((line) => console.log(`[body] self-update: ${line}`));
|
|
493
493
|
const releaseId = sanitizeReleaseId(published.commit ?? published.version ?? `release-${Date.now()}`);
|
|
494
494
|
const releaseDir = join6(layout.releasesRoot, releaseId);
|
|
495
495
|
const okMarker = join6(releaseDir, ".stage-ok");
|
|
@@ -504,7 +504,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
504
504
|
await mkdir10(releaseDir, { recursive: true });
|
|
505
505
|
const tempArchive = join6(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
|
|
506
506
|
try {
|
|
507
|
-
|
|
507
|
+
log2(`downloading ${published.file}`);
|
|
508
508
|
const response = await fetchImpl(archiveUrlFor(manifestUrl, published.file), {
|
|
509
509
|
signal: AbortSignal.timeout(10 * 6e4)
|
|
510
510
|
});
|
|
@@ -555,7 +555,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
555
555
|
}
|
|
556
556
|
await writeFile8(okMarker, `${published.sha256}
|
|
557
557
|
`, "utf8");
|
|
558
|
-
|
|
558
|
+
log2(`staged release ${releaseId} (sha256 verified)`);
|
|
559
559
|
return releaseId;
|
|
560
560
|
} catch (error) {
|
|
561
561
|
if (!previouslyVerified)
|
|
@@ -955,7 +955,7 @@ var init_self_update = __esm({
|
|
|
955
955
|
});
|
|
956
956
|
|
|
957
957
|
// apps/body/dist/cli.js
|
|
958
|
-
import { dirname as
|
|
958
|
+
import { dirname as dirname15, resolve as resolve25 } from "node:path";
|
|
959
959
|
import { readFile as readFile13, unlink as unlink3, writeFile as writeFile14 } from "node:fs/promises";
|
|
960
960
|
import { stdin as stdin4, stdout as stdout5 } from "node:process";
|
|
961
961
|
|
|
@@ -2057,6 +2057,49 @@ var MULTISELECT_INSTRUCTIONS = [
|
|
|
2057
2057
|
`${styleText2("dim", "Space:")} select`,
|
|
2058
2058
|
`${styleText2("dim", "Enter:")} confirm`
|
|
2059
2059
|
];
|
|
2060
|
+
var log = {
|
|
2061
|
+
message: (s = [], {
|
|
2062
|
+
symbol: e = styleText2("gray", S_BAR),
|
|
2063
|
+
secondarySymbol: r2 = styleText2("gray", S_BAR),
|
|
2064
|
+
output: m2 = process.stdout,
|
|
2065
|
+
spacing: l2 = 1,
|
|
2066
|
+
withGuide: c2
|
|
2067
|
+
} = {}) => {
|
|
2068
|
+
const t2 = [], o = c2 ?? settings.withGuide, f = o ? r2 : "", O = o ? `${e} ` : "", u4 = o ? `${r2} ` : "";
|
|
2069
|
+
for (let i3 = 0; i3 < l2; i3++)
|
|
2070
|
+
t2.push(f);
|
|
2071
|
+
const g2 = Array.isArray(s) ? s : s.split(`
|
|
2072
|
+
`);
|
|
2073
|
+
if (g2.length > 0) {
|
|
2074
|
+
const [i3, ...y] = g2;
|
|
2075
|
+
i3.length > 0 ? t2.push(`${O}${i3}`) : t2.push(o ? e : "");
|
|
2076
|
+
for (const p of y)
|
|
2077
|
+
p.length > 0 ? t2.push(`${u4}${p}`) : t2.push(o ? r2 : "");
|
|
2078
|
+
}
|
|
2079
|
+
m2.write(`${t2.join(`
|
|
2080
|
+
`)}
|
|
2081
|
+
`);
|
|
2082
|
+
},
|
|
2083
|
+
info: (s, e) => {
|
|
2084
|
+
log.message(s, { ...e, symbol: styleText2("blue", S_INFO) });
|
|
2085
|
+
},
|
|
2086
|
+
success: (s, e) => {
|
|
2087
|
+
log.message(s, { ...e, symbol: styleText2("green", S_SUCCESS) });
|
|
2088
|
+
},
|
|
2089
|
+
step: (s, e) => {
|
|
2090
|
+
log.message(s, { ...e, symbol: styleText2("green", S_STEP_SUBMIT) });
|
|
2091
|
+
},
|
|
2092
|
+
warn: (s, e) => {
|
|
2093
|
+
log.message(s, { ...e, symbol: styleText2("yellow", S_WARN) });
|
|
2094
|
+
},
|
|
2095
|
+
/** alias for `log.warn()`. */
|
|
2096
|
+
warning: (s, e) => {
|
|
2097
|
+
log.warn(s, e);
|
|
2098
|
+
},
|
|
2099
|
+
error: (s, e) => {
|
|
2100
|
+
log.message(s, { ...e, symbol: styleText2("red", S_ERROR) });
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2060
2103
|
var cancel = (o = "", t2) => {
|
|
2061
2104
|
const i3 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
|
|
2062
2105
|
i3.write(`${e}${styleText2("red", o)}
|
|
@@ -4173,12 +4216,25 @@ async function applyAgentModelSelection(client, sessionId, advertisedOptions, se
|
|
|
4173
4216
|
}
|
|
4174
4217
|
|
|
4175
4218
|
// apps/body/dist/model-catalog.js
|
|
4219
|
+
function modelCatalogProbeEnvironment(agent, agentEnv, scratchCwd) {
|
|
4220
|
+
if (agent.kind !== "goose")
|
|
4221
|
+
return agentEnv;
|
|
4222
|
+
return {
|
|
4223
|
+
...agentEnv,
|
|
4224
|
+
GOOSE_PATH_ROOT: resolve4(scratchCwd, "goose")
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
function filterAgentModelCatalog(agent, raw, agentEnv) {
|
|
4228
|
+
const allowed = filterAllowedModelConfigOptions(raw);
|
|
4229
|
+
return agent.kind === "goose" ? allowed : filterModelOptionsByCredentials(allowed, agentEnv);
|
|
4230
|
+
}
|
|
4176
4231
|
async function withAgentModelCatalog(agent, agentEnv, selection, inspect) {
|
|
4177
4232
|
const scratchCwd = await mkdtemp(resolve4(tmpdir(), "beeline-pair-model-check-"));
|
|
4233
|
+
const probeEnv = modelCatalogProbeEnvironment(agent, agentEnv, scratchCwd);
|
|
4178
4234
|
const client = new AcpClient({
|
|
4179
4235
|
agentCommand: agent.command,
|
|
4180
4236
|
agentArgs: agentArgsWithModelSelection(agent, selection),
|
|
4181
|
-
agentEnv,
|
|
4237
|
+
agentEnv: probeEnv,
|
|
4182
4238
|
agentCwd: scratchCwd,
|
|
4183
4239
|
// The wizard probe runs on the human's own machine for a few seconds —
|
|
4184
4240
|
// inherit the caller's environment so harness launchers resolve (`pi`,
|
|
@@ -4191,7 +4247,7 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect) {
|
|
|
4191
4247
|
await client.start();
|
|
4192
4248
|
const { sessionId, raw: sessionRaw } = await client.sessionNew({ cwd: scratchCwd });
|
|
4193
4249
|
const raw = parseAdvertisedConfigOptions(sessionRaw, selection?.model, isGrokAgentCommand(agent));
|
|
4194
|
-
const catalog =
|
|
4250
|
+
const catalog = filterAgentModelCatalog(agent, raw, probeEnv);
|
|
4195
4251
|
return await inspect({ client, sessionId, raw, catalog });
|
|
4196
4252
|
} finally {
|
|
4197
4253
|
await client.stop().catch(() => void 0);
|
|
@@ -4274,7 +4330,7 @@ async function withTimeout(work, timeoutMs, what) {
|
|
|
4274
4330
|
}
|
|
4275
4331
|
}
|
|
4276
4332
|
async function syncAgentModelCatalog(input) {
|
|
4277
|
-
const
|
|
4333
|
+
const log2 = input.log ?? ((line) => console.log(line));
|
|
4278
4334
|
const fetchCatalog = input.fetchCatalog ?? fetchAgentModelCatalog;
|
|
4279
4335
|
const hashPath = resolve5(input.runtimeDir, MODEL_CATALOG_HASH_FILE);
|
|
4280
4336
|
try {
|
|
@@ -4299,10 +4355,10 @@ async function syncAgentModelCatalog(input) {
|
|
|
4299
4355
|
});
|
|
4300
4356
|
await writeFile(hashPath, `${hash}
|
|
4301
4357
|
`, { mode: 384 });
|
|
4302
|
-
|
|
4358
|
+
log2(`[body] model catalog posted: ${catalog.length} axis(es)` + (selection?.model ? `, model ${selection.model}` : "") + (selection?.effort ? `, effort ${selection.effort}` : ""));
|
|
4303
4359
|
return "posted";
|
|
4304
4360
|
} catch (error) {
|
|
4305
|
-
|
|
4361
|
+
log2(`[body] model catalog not posted this activation: ${error instanceof Error ? error.message : String(error)}`);
|
|
4306
4362
|
return "failed";
|
|
4307
4363
|
}
|
|
4308
4364
|
}
|
|
@@ -4690,6 +4746,7 @@ var BEELINE_ROOM_CAPABILITIES = [
|
|
|
4690
4746
|
"To send a file, call beeline-agent attach_file with a path inside your checkout; it is attached to your reply.",
|
|
4691
4747
|
"To run something later or repeatedly, call beeline-agent create_schedule (interval in minutes or a 5-field cron, optional maxRuns); list_schedules / delete_schedule manage them.",
|
|
4692
4748
|
"When repository work is needed, you MUST call beeline-agent open_corner with a complete objective of no more than 24 words. The host-governed call is the only way to start write work.",
|
|
4749
|
+
"When open_corner succeeds, the server posts the corner card: do not announce or restate the opening. End the turn with nothing more unless the person asked something else.",
|
|
4693
4750
|
"Never claim an action or reply happened unless the prompt or a tool result proves it."
|
|
4694
4751
|
].join(" ");
|
|
4695
4752
|
var BEELINE_DM_CAPABILITIES = [
|
|
@@ -4843,7 +4900,7 @@ function openRouterRoutingFor(providers) {
|
|
|
4843
4900
|
only: [...providers],
|
|
4844
4901
|
order: [...providers],
|
|
4845
4902
|
allow_fallbacks: true,
|
|
4846
|
-
require_parameters:
|
|
4903
|
+
require_parameters: false
|
|
4847
4904
|
};
|
|
4848
4905
|
}
|
|
4849
4906
|
function openRouterRoutingCacheDir(runtimeDir) {
|
|
@@ -5657,6 +5714,52 @@ function sanitizeAgentReply(message) {
|
|
|
5657
5714
|
}
|
|
5658
5715
|
return lines.slice(1).join("\n").trim();
|
|
5659
5716
|
}
|
|
5717
|
+
var CORNER_OPEN_ECHO = /^(?:(?:ok(?:ay)?|done|sure|great|alright)[,.!:]?\s+)?(?:i(?:'ve| have|'ll| will|'m| am)?\s+)?(?:just\s+)?open(?:ed|ing)?\s+(?:up\s+)?(?:a\s+|the\s+|your\s+)?(?:new\s+|write(?:-enabled)?\s+|repository\s+)*corner\b/iu;
|
|
5718
|
+
var CORNER_OPEN_ECHO_MAX_CHARS = 320;
|
|
5719
|
+
function stripCornerOpenEcho(message) {
|
|
5720
|
+
const visible = message.trim();
|
|
5721
|
+
if (!visible)
|
|
5722
|
+
return "";
|
|
5723
|
+
const paragraphs = visible.split(/\n\s*\n/);
|
|
5724
|
+
const first = paragraphs[0].trim();
|
|
5725
|
+
if (first.length > CORNER_OPEN_ECHO_MAX_CHARS || !CORNER_OPEN_ECHO.test(first))
|
|
5726
|
+
return visible;
|
|
5727
|
+
return paragraphs.slice(1).join("\n\n").trim();
|
|
5728
|
+
}
|
|
5729
|
+
var STATUS_FILLER = new Set("a an the this that it its is are was were be been being has have had do does did now still remains remain remaining ready for review reviewing pr pull request all every both and or so to of in on at as with by no not nothing new further more else needed needs need required action actions change changes changed status state update updates updated waiting wait awaiting pending running progress idle done ok okay good great well fine confirmed confirm already again yet just currently right i we will can should would github remote upstream branch head latest current report reported noted note see looks look like everything nothing appears appear seems seem there here which what from without any".split(/\s+/));
|
|
5730
|
+
var STATUS_SYNONYMS = {
|
|
5731
|
+
passed: ["pass", "passed", "passing", "passes", "green", "succeeded", "success", "successful", "successfully"],
|
|
5732
|
+
failed: ["fail", "failed", "failing", "fails", "red", "broke", "broken", "failure", "failures"],
|
|
5733
|
+
check: ["check", "checks", "ci", "run", "runs", "workflow", "workflows", "build", "builds", "test", "tests", "suite"],
|
|
5734
|
+
started: ["start", "started", "starting", "began", "begun", "kicked", "off", "queued"],
|
|
5735
|
+
merged: ["merge", "merged", "merging", "landed", "closed"]
|
|
5736
|
+
};
|
|
5737
|
+
var STATUS_RESTATEMENT_MAX_CHARS = 200;
|
|
5738
|
+
var STATUS_RESTATEMENT_MAX_SENTENCES = 2;
|
|
5739
|
+
function statusWords(text2) {
|
|
5740
|
+
return (text2.toLowerCase().match(/[\p{L}\p{N}'-]+/gu) ?? []).map((word) => word.replace(/^'+|'+$/g, "").replace(/'s$/, ""));
|
|
5741
|
+
}
|
|
5742
|
+
function isCornerStatusRestatement(reply, systemLines) {
|
|
5743
|
+
const text2 = reply.trim();
|
|
5744
|
+
if (!text2)
|
|
5745
|
+
return true;
|
|
5746
|
+
if (text2.length > STATUS_RESTATEMENT_MAX_CHARS)
|
|
5747
|
+
return false;
|
|
5748
|
+
if (/https?:\/\/\S+/i.test(text2))
|
|
5749
|
+
return false;
|
|
5750
|
+
const sentences = text2.split(/[.!?]+(?:\s+|$)/).filter((part) => part.trim());
|
|
5751
|
+
if (sentences.length > STATUS_RESTATEMENT_MAX_SENTENCES)
|
|
5752
|
+
return false;
|
|
5753
|
+
const admitted = new Set(STATUS_FILLER);
|
|
5754
|
+
for (const line of systemLines) {
|
|
5755
|
+
for (const word of statusWords(line)) {
|
|
5756
|
+
admitted.add(word);
|
|
5757
|
+
for (const synonym of STATUS_SYNONYMS[word] ?? [])
|
|
5758
|
+
admitted.add(synonym);
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
return statusWords(text2).every((word) => !word || admitted.has(word));
|
|
5762
|
+
}
|
|
5660
5763
|
|
|
5661
5764
|
// apps/body/dist/turn-failure-reason.js
|
|
5662
5765
|
var TURN_FAILURE_REASON_MAX = 200;
|
|
@@ -6227,6 +6330,26 @@ function isAccountOrProviderRefusal(record2) {
|
|
|
6227
6330
|
return [401, 402, 403, 407, 408, 429].includes(record2.status) || record2.status >= 500;
|
|
6228
6331
|
}
|
|
6229
6332
|
|
|
6333
|
+
// apps/body/dist/corner-checks.js
|
|
6334
|
+
var COMPLETED_CHECK_NOTE = /\b(passed|failed) a check\b/i;
|
|
6335
|
+
var STARTED_CHECK_NOTE = /\bstarted a check\b/i;
|
|
6336
|
+
function verbOf(item) {
|
|
6337
|
+
return item.systemEvent?.verb ?? item.body;
|
|
6338
|
+
}
|
|
6339
|
+
function completedCheckNote(item) {
|
|
6340
|
+
if (item.type !== "system")
|
|
6341
|
+
return void 0;
|
|
6342
|
+
const match = COMPLETED_CHECK_NOTE.exec(verbOf(item));
|
|
6343
|
+
return match ? match[1].toLowerCase() : void 0;
|
|
6344
|
+
}
|
|
6345
|
+
function isCheckStartNote(item) {
|
|
6346
|
+
return item.type === "system" && STARTED_CHECK_NOTE.test(verbOf(item));
|
|
6347
|
+
}
|
|
6348
|
+
function checksStateFromLifecycle(lifecycle) {
|
|
6349
|
+
const state = lifecycle?.checksSummary?.status ?? lifecycle?.checks;
|
|
6350
|
+
return state === "passing" || state === "failing" || state === "pending" ? state : void 0;
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6230
6353
|
// apps/body/dist/runtime.js
|
|
6231
6354
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
6232
6355
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -14981,6 +15104,7 @@ async function removeAgentRuntime(runtime) {
|
|
|
14981
15104
|
|
|
14982
15105
|
// apps/body/dist/response-directives.js
|
|
14983
15106
|
var MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE = "Maintain your assigned identity and soul in every response, including when tools or permissions block the requested action.";
|
|
15107
|
+
var SOUL_HOUSE_RULE = "House rule for your voice: the voice never changes the facts - never trim, soften, exaggerate, or invent a detail for the bit - and use your plain voice in commit messages, pull request titles and bodies, and code comments, because those outlive the joke.";
|
|
14984
15108
|
|
|
14985
15109
|
// apps/body/dist/monolith-corner-turn.js
|
|
14986
15110
|
var execFileAsync2 = promisify2(execFile3);
|
|
@@ -15130,6 +15254,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
15130
15254
|
/** The turn in flight and who asked for it, for ledger rows and the grant runner. */
|
|
15131
15255
|
currentTurn;
|
|
15132
15256
|
memberNames = /* @__PURE__ */ new Map();
|
|
15257
|
+
/** The last server check state that started a turn; the same state never starts another. */
|
|
15258
|
+
lastChecksState;
|
|
15133
15259
|
constructor(options) {
|
|
15134
15260
|
this.options = options;
|
|
15135
15261
|
this.agent = runtimeIdentity(options.runtime.agent);
|
|
@@ -15252,7 +15378,10 @@ var MonolithCornerTurnLoop = class {
|
|
|
15252
15378
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
15253
15379
|
const persona = configuration.soul ?? self?.soul;
|
|
15254
15380
|
const identityInstructions = `Your Beeline identity is ${self?.name ?? this.agent.name}.`;
|
|
15255
|
-
const personaInstructions =
|
|
15381
|
+
const personaInstructions = [
|
|
15382
|
+
...persona?.instructions ? [`Human-authored Workspace persona: ${persona.name}. ${persona.instructions}`] : [],
|
|
15383
|
+
SOUL_HOUSE_RULE
|
|
15384
|
+
].join("\n");
|
|
15256
15385
|
this.turnIdentityInstructions = harnessHonorsSessionSystemPrompt(command) ? "" : [identityInstructions, personaInstructions].filter(Boolean).join("\n\n");
|
|
15257
15386
|
const opened = await this.client.sessionNew({
|
|
15258
15387
|
cwd: this.options.worktreePath,
|
|
@@ -15268,6 +15397,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
15268
15397
|
"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.",
|
|
15269
15398
|
"If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
|
|
15270
15399
|
"Do not tag the user when a corner turn finishes: the server posts the merge summary card and its push already cover completion. Tag a human only mid-turn, and only when you need a decision or input.",
|
|
15400
|
+
'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
|
|
15271
15401
|
"A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
|
|
15272
15402
|
"Never push directly to the target branch. Never merge a different pull request."
|
|
15273
15403
|
].filter(Boolean).join("\n\n")
|
|
@@ -15291,8 +15421,9 @@ var MonolithCornerTurnLoop = class {
|
|
|
15291
15421
|
}
|
|
15292
15422
|
};
|
|
15293
15423
|
}
|
|
15294
|
-
async prompt(requestId, trigger, attachments = [], requestedById) {
|
|
15424
|
+
async prompt(requestId, trigger, attachments = [], requestedById, restates) {
|
|
15295
15425
|
const { api, cornerId } = this.options;
|
|
15426
|
+
const spoken = (text2) => restates && isCornerStatusRestatement(text2, restates) ? "" : text2;
|
|
15296
15427
|
const requester = requestedById ? {
|
|
15297
15428
|
pubkey: requestedById,
|
|
15298
15429
|
...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
|
|
@@ -15350,7 +15481,7 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
15350
15481
|
const start = narrationPostedChars;
|
|
15351
15482
|
narrationPostedChars = segmentEnd;
|
|
15352
15483
|
narrationSegments += 1;
|
|
15353
|
-
const trimmed = segment.trim();
|
|
15484
|
+
const trimmed = spoken(segment.trim());
|
|
15354
15485
|
if (!trimmed)
|
|
15355
15486
|
return;
|
|
15356
15487
|
this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
|
|
@@ -15407,11 +15538,12 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
15407
15538
|
result
|
|
15408
15539
|
});
|
|
15409
15540
|
reply = explained.recoveredText ? stripAgentReplyPreamble(explained.recoveredText).trim() : "";
|
|
15410
|
-
if (!reply)
|
|
15541
|
+
if (!reply && !(restates && !isAccountOrProviderRefusal(explained.record))) {
|
|
15411
15542
|
throw new Error(explained.reason);
|
|
15543
|
+
}
|
|
15412
15544
|
console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
|
|
15413
15545
|
}
|
|
15414
|
-
const durableTail = narrationPostedChars > 0 ? stripAgentReplyPreamble(result.agentText.slice(narrationPostedChars)).trim() : reply;
|
|
15546
|
+
const durableTail = spoken(narrationPostedChars > 0 ? stripAgentReplyPreamble(result.agentText.slice(narrationPostedChars)).trim() : reply);
|
|
15415
15547
|
if (durableTail) {
|
|
15416
15548
|
await api.execute("postRoomMessage", {
|
|
15417
15549
|
roomId: cornerId,
|
|
@@ -15449,6 +15581,20 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
15449
15581
|
this.currentTurn = void 0;
|
|
15450
15582
|
}
|
|
15451
15583
|
}
|
|
15584
|
+
/** The server's check state for this head, or the notes' own verdict when the server carries none. */
|
|
15585
|
+
async checksState(notes) {
|
|
15586
|
+
try {
|
|
15587
|
+
const restore = await this.options.api.execute("getCornerRestoreState", {
|
|
15588
|
+
cornerId: this.options.cornerId
|
|
15589
|
+
});
|
|
15590
|
+
const fromServer = checksStateFromLifecycle(restore.lifecycle);
|
|
15591
|
+
if (fromServer)
|
|
15592
|
+
return fromServer;
|
|
15593
|
+
} catch (error) {
|
|
15594
|
+
console.error(`[thin-core] corner ${this.options.cornerId} check state read failed:`, error);
|
|
15595
|
+
}
|
|
15596
|
+
return notes.some((note) => completedCheckNote(note) === "failed") ? "failing" : "passing";
|
|
15597
|
+
}
|
|
15452
15598
|
async run() {
|
|
15453
15599
|
const { api, cornerId, signal } = this.options;
|
|
15454
15600
|
let cursor3 = (await api.execute("getRoomInbox", { roomId: cornerId, startAtLatest: true })).cursor;
|
|
@@ -15469,6 +15615,7 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
15469
15615
|
await this.options.onCloseRequested();
|
|
15470
15616
|
return;
|
|
15471
15617
|
}
|
|
15618
|
+
const checkNotes = [];
|
|
15472
15619
|
for (const item of inbox.items) {
|
|
15473
15620
|
if (item.type === "message") {
|
|
15474
15621
|
if (item.authorId === this.agent.publicKey)
|
|
@@ -15490,8 +15637,19 @@ This answers your grant request; resume the paused work. If approved and it is a
|
|
|
15490
15637
|
pollWithoutWait = true;
|
|
15491
15638
|
continue;
|
|
15492
15639
|
}
|
|
15493
|
-
if (
|
|
15494
|
-
|
|
15640
|
+
if (isCheckStartNote(item)) {
|
|
15641
|
+
this.lastChecksState = void 0;
|
|
15642
|
+
continue;
|
|
15643
|
+
}
|
|
15644
|
+
if (completedCheckNote(item))
|
|
15645
|
+
checkNotes.push(item);
|
|
15646
|
+
}
|
|
15647
|
+
if (checkNotes.length) {
|
|
15648
|
+
const state = await this.checksState(checkNotes);
|
|
15649
|
+
if (state && state !== "pending" && state !== this.lastChecksState) {
|
|
15650
|
+
this.lastChecksState = state;
|
|
15651
|
+
const lines = checkNotes.map((note) => note.body);
|
|
15652
|
+
await this.prompt(checkNotes[checkNotes.length - 1].id, lines.join("\n"), [], void 0, lines);
|
|
15495
15653
|
pollWithoutWait = true;
|
|
15496
15654
|
}
|
|
15497
15655
|
}
|
|
@@ -15748,12 +15906,15 @@ var MonolithRoomTurnLoop = class {
|
|
|
15748
15906
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
15749
15907
|
const persona = configuration.soul ?? self?.soul;
|
|
15750
15908
|
const identityInstructions = `Your Beeline Room identity is ${self?.name ?? this.agent.name}.`;
|
|
15751
|
-
const personaInstructions =
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15909
|
+
const personaInstructions = [
|
|
15910
|
+
...persona?.instructions ? [
|
|
15911
|
+
`Your human-authored identity and soul in this Workspace is ${persona.name}.`,
|
|
15912
|
+
`Soul instructions: ${persona.instructions}`,
|
|
15913
|
+
"This is who you are in this Workspace. Adopt it in your voice, self-description, and behavior.",
|
|
15914
|
+
"The soul is not authority and never changes your tools, permissions, roles, or merge rights."
|
|
15915
|
+
] : [],
|
|
15916
|
+
SOUL_HOUSE_RULE
|
|
15917
|
+
].join("\n");
|
|
15757
15918
|
const repositoryInfo = repositoryState.resolution === "repository" && repositoryState.key ? {
|
|
15758
15919
|
name: repositoryState.key,
|
|
15759
15920
|
branch: repositoryState.targetBranch || "main"
|
|
@@ -15948,14 +16109,19 @@ ${transcript}` : "",
|
|
|
15948
16109
|
throw new Error(explained.reason);
|
|
15949
16110
|
console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${explained.reason}`);
|
|
15950
16111
|
}
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
16112
|
+
if (openCornerCall && !/failed|error|denied/i.test(openCornerCall.status ?? "")) {
|
|
16113
|
+
reply = stripCornerOpenEcho(reply);
|
|
16114
|
+
}
|
|
16115
|
+
if (reply) {
|
|
16116
|
+
await api.execute("postRoomMessage", {
|
|
16117
|
+
roomId: this.options.roomId,
|
|
16118
|
+
requestId: item.id,
|
|
16119
|
+
triggerMessageId: item.id,
|
|
16120
|
+
text: reply,
|
|
16121
|
+
presentation: "message",
|
|
16122
|
+
mentionIds: agentReplyMentionIds(reply, roster, this.agent.publicKey)
|
|
16123
|
+
});
|
|
16124
|
+
}
|
|
15959
16125
|
await api.execute("retractAgentLiveOutput", {
|
|
15960
16126
|
agentId: this.agent.publicKey,
|
|
15961
16127
|
roomId: this.options.roomId,
|
|
@@ -17270,6 +17436,9 @@ async function notify(fields) {
|
|
|
17270
17436
|
return;
|
|
17271
17437
|
await execFileAsync4("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
|
|
17272
17438
|
}
|
|
17439
|
+
async function extendSystemdStartTimeout(ms) {
|
|
17440
|
+
await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
|
|
17441
|
+
}
|
|
17273
17442
|
var SystemdNotifier = class {
|
|
17274
17443
|
async ready(status) {
|
|
17275
17444
|
await notify(["--ready", `--status=${status}`]);
|
|
@@ -17730,14 +17899,32 @@ function brassEnabled(env, output) {
|
|
|
17730
17899
|
return "256";
|
|
17731
17900
|
return "plain";
|
|
17732
17901
|
}
|
|
17902
|
+
var BRASS_TRUECOLOR = "\x1B[38;2;215;175;95m";
|
|
17903
|
+
var BRASS_256 = "\x1B[38;5;179m";
|
|
17733
17904
|
function brass(value, env = process.env, output = stdout4) {
|
|
17734
17905
|
const level = brassEnabled(env, output);
|
|
17735
17906
|
if (level === "truecolor")
|
|
17736
|
-
return
|
|
17907
|
+
return `${BRASS_TRUECOLOR}${value}\x1B[39m`;
|
|
17737
17908
|
if (level === "256")
|
|
17738
|
-
return
|
|
17909
|
+
return `${BRASS_256}${value}\x1B[39m`;
|
|
17739
17910
|
return value;
|
|
17740
17911
|
}
|
|
17912
|
+
function brassRails(text2, env = process.env, output = stdout4) {
|
|
17913
|
+
const level = brassEnabled(env, output);
|
|
17914
|
+
if (level === "plain")
|
|
17915
|
+
return text2;
|
|
17916
|
+
return text2.replace(/\u001b\[(?:36|96)m/g, level === "truecolor" ? BRASS_TRUECOLOR : BRASS_256);
|
|
17917
|
+
}
|
|
17918
|
+
function paintWizardBrass(output = stdout4) {
|
|
17919
|
+
const original = output.write;
|
|
17920
|
+
const patched = function write(chunk, ...rest) {
|
|
17921
|
+
return original.call(this, typeof chunk === "string" ? brassRails(chunk, process.env, output) : chunk, ...rest);
|
|
17922
|
+
};
|
|
17923
|
+
output.write = patched;
|
|
17924
|
+
return () => {
|
|
17925
|
+
output.write = original;
|
|
17926
|
+
};
|
|
17927
|
+
}
|
|
17741
17928
|
var clackPrompts = {
|
|
17742
17929
|
async select(input) {
|
|
17743
17930
|
clackPromptOutput();
|
|
@@ -17762,12 +17949,12 @@ var clackPrompts = {
|
|
|
17762
17949
|
}), "Connection cancelled.");
|
|
17763
17950
|
}
|
|
17764
17951
|
};
|
|
17765
|
-
function connectModelPickerFromAxes(axes, fallbackModel, harness) {
|
|
17952
|
+
function connectModelPickerFromAxes(axes, fallbackModel, harness, rawAxes = axes) {
|
|
17766
17953
|
const filtered = axes.find((axis) => axis.category === "model");
|
|
17767
17954
|
if (filtered?.options.length) {
|
|
17768
17955
|
return { currentValue: filtered.currentValue, options: filtered.options };
|
|
17769
17956
|
}
|
|
17770
|
-
const raw =
|
|
17957
|
+
const raw = rawAxes.find((axis) => axis.category === "model" && axis.options.length);
|
|
17771
17958
|
if (raw) {
|
|
17772
17959
|
return { currentValue: raw.currentValue, options: raw.options };
|
|
17773
17960
|
}
|
|
@@ -17785,7 +17972,7 @@ async function loadConnectModelCatalog(input) {
|
|
|
17785
17972
|
...input.apiKey ? { apiKey: input.apiKey } : {},
|
|
17786
17973
|
model: defaultConnectModel(input.harness, input.provider)
|
|
17787
17974
|
}));
|
|
17788
|
-
return connectModelPickerFromAxes(catalog.catalog, defaultConnectModel(input.harness, input.provider), input.harness);
|
|
17975
|
+
return connectModelPickerFromAxes(catalog.catalog, defaultConnectModel(input.harness, input.provider), input.harness, catalog.raw);
|
|
17789
17976
|
}
|
|
17790
17977
|
async function collectConnectWizard(prompts = clackPrompts, loadModels = loadConnectModelCatalog, keyStore = fileConnectKeyStore, env = process.env, verifyKey = (input) => verifyProviderKey(input)) {
|
|
17791
17978
|
const harness = await prompts.select({
|
|
@@ -17862,25 +18049,11 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
|
|
|
17862
18049
|
placeholder: "Type to filter available models\u2026",
|
|
17863
18050
|
maxItems: 12
|
|
17864
18051
|
});
|
|
17865
|
-
const name = await prompts.text({
|
|
17866
|
-
message: brass("Agent name"),
|
|
17867
|
-
validate: (value) => {
|
|
17868
|
-
if (!value.trim())
|
|
17869
|
-
return "Agent name is required";
|
|
17870
|
-
return isReasonableAgentName(value) ? void 0 : `Use letters, spaces, hyphens, or apostrophes (${AGENT_NAME_MAX_LENGTH} characters max)`;
|
|
17871
|
-
}
|
|
17872
|
-
});
|
|
17873
|
-
const soul = await prompts.text({
|
|
17874
|
-
message: brass("Input soul"),
|
|
17875
|
-
validate: (value) => value.trim() ? void 0 : "Soul is required"
|
|
17876
|
-
});
|
|
17877
18052
|
return {
|
|
17878
|
-
name: name.trim().replace(/\s+/g, " "),
|
|
17879
18053
|
harness,
|
|
17880
18054
|
...provider ? { provider } : {},
|
|
17881
18055
|
...apiKey ? { apiKey: apiKey.trim() } : {},
|
|
17882
|
-
model: model.trim()
|
|
17883
|
-
soul: soul.trim()
|
|
18056
|
+
model: model.trim()
|
|
17884
18057
|
};
|
|
17885
18058
|
}
|
|
17886
18059
|
async function jsonRequest(url, body, fetchImpl) {
|
|
@@ -17906,11 +18079,19 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
|
|
|
17906
18079
|
harness: selection.harness,
|
|
17907
18080
|
...selection.provider ? { provider: selection.provider } : {},
|
|
17908
18081
|
model: selection.model,
|
|
17909
|
-
|
|
17910
|
-
avatar_seed: avatarSeed,
|
|
17911
|
-
agent_name: selection.name
|
|
18082
|
+
avatar_seed: avatarSeed
|
|
17912
18083
|
}, fetchImpl);
|
|
17913
18084
|
}
|
|
18085
|
+
async function renameConnectedAgent(baseUrl, pairingCode, name, fetchImpl) {
|
|
18086
|
+
const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
|
|
18087
|
+
if (!normalizedPairingCode)
|
|
18088
|
+
throw new Error("invalid pairing code");
|
|
18089
|
+
const renamed = await jsonRequest(`${baseUrl}/auth/agent/connect/name`, { pairing_code: normalizedPairingCode, agent_name: name.trim().replace(/\s+/g, " ") }, fetchImpl);
|
|
18090
|
+
return renamed.agent_name;
|
|
18091
|
+
}
|
|
18092
|
+
function seededIdentityLine(grant) {
|
|
18093
|
+
return grant.agent_face ? `${grant.agent_name} the ${grant.agent_face}` : grant.agent_name;
|
|
18094
|
+
}
|
|
17914
18095
|
async function installCurrentRelease(fetchImpl) {
|
|
17915
18096
|
const manifestUrl = resolveManifestUrl(process.env);
|
|
17916
18097
|
const response = await fetchImpl(manifestUrl, { signal: AbortSignal.timeout(3e4) });
|
|
@@ -18003,15 +18184,23 @@ async function runConnectCommand(code, options = {}) {
|
|
|
18003
18184
|
if (!stdin3.isTTY || !stdout4.isTTY) {
|
|
18004
18185
|
throw new Error("`usebeeline connect` needs an interactive terminal");
|
|
18005
18186
|
}
|
|
18187
|
+
const restoreRails = paintWizardBrass();
|
|
18188
|
+
try {
|
|
18189
|
+
await runConnectWizard(code, options.fetchImpl ?? fetch);
|
|
18190
|
+
} finally {
|
|
18191
|
+
restoreRails();
|
|
18192
|
+
}
|
|
18193
|
+
}
|
|
18194
|
+
async function runConnectWizard(code, fetchImpl) {
|
|
18006
18195
|
intro(brass("Beeline connect"));
|
|
18007
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
18008
18196
|
const pairingCode = code?.trim() || await clackPrompts.text({
|
|
18009
18197
|
message: brass("Pairing code from the app"),
|
|
18010
18198
|
validate: (value) => normalizeAgentPairingCode(value) ? void 0 : "Enter the pairing code shown in the app"
|
|
18011
18199
|
});
|
|
18012
18200
|
const selection = await collectConnectWizard(clackPrompts, loadConnectModelCatalog, fileConnectKeyStore, process.env, (input) => verifyProviderKey({ ...input, fetchImpl }));
|
|
18013
18201
|
const baseUrl = (process.env.BEELINE_AUTH_URL ?? "https://server.usebeeline.app").replace(/\/$/, "");
|
|
18014
|
-
const
|
|
18202
|
+
const claimed = await brassSpinner("Connecting to your Beeline Workspace\u2026", () => requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl), (connectedGrant) => `Connected to ${connectedGrant.workspace_name}`);
|
|
18203
|
+
const grant = { ...claimed, agent_name: await confirmSeededName(baseUrl, pairingCode, claimed, fetchImpl) };
|
|
18015
18204
|
const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
|
|
18016
18205
|
const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
|
|
18017
18206
|
const grantPath = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
|
|
@@ -18031,10 +18220,38 @@ async function runConnectCommand(code, options = {}) {
|
|
|
18031
18220
|
});
|
|
18032
18221
|
await brassSpinner("Starting your agent\u2026", () => runInstalledFinish(installedRelease.binary, grantPath), () => `Started ${grant.agent_name}`);
|
|
18033
18222
|
console.log("");
|
|
18034
|
-
console.log(`${brass("Agent")} ${grant
|
|
18223
|
+
console.log(`${brass("Agent")} ${seededIdentityLine(grant)}`);
|
|
18035
18224
|
console.log(`${brass("Workspace")} ${grant.workspace_name}`);
|
|
18036
18225
|
outro(brass("Say hi in the app."));
|
|
18037
18226
|
}
|
|
18227
|
+
async function confirmSeededName(baseUrl, pairingCode, grant, fetchImpl, prompts = clackPrompts) {
|
|
18228
|
+
log.info(brass(`Your agent is ${seededIdentityLine(grant)}.`));
|
|
18229
|
+
const choice = await prompts.select({
|
|
18230
|
+
message: brass("Name"),
|
|
18231
|
+
initialValue: "keep",
|
|
18232
|
+
options: [
|
|
18233
|
+
{ value: "keep", label: `Keep ${grant.agent_name}`, hint: "default" },
|
|
18234
|
+
{ value: "rename", label: "Rename this agent" }
|
|
18235
|
+
]
|
|
18236
|
+
});
|
|
18237
|
+
if (choice === "keep")
|
|
18238
|
+
return grant.agent_name;
|
|
18239
|
+
const renamed = await prompts.text({
|
|
18240
|
+
message: brass("Agent name"),
|
|
18241
|
+
initialValue: grant.agent_name,
|
|
18242
|
+
validate: (value) => {
|
|
18243
|
+
if (!value.trim())
|
|
18244
|
+
return "Agent name is required";
|
|
18245
|
+
return isReasonableAgentName(value) ? void 0 : `Use letters, spaces, hyphens, or apostrophes (${AGENT_NAME_MAX_LENGTH} characters max)`;
|
|
18246
|
+
}
|
|
18247
|
+
});
|
|
18248
|
+
try {
|
|
18249
|
+
return await renameConnectedAgent(baseUrl, pairingCode, renamed, fetchImpl);
|
|
18250
|
+
} catch (error) {
|
|
18251
|
+
log.warn(brass(`Could not rename this agent (${error instanceof Error ? error.message : String(error)}); it stays ${grant.agent_name}. Rename it in the app.`));
|
|
18252
|
+
return grant.agent_name;
|
|
18253
|
+
}
|
|
18254
|
+
}
|
|
18038
18255
|
function isDevicePairingGrant(value) {
|
|
18039
18256
|
if (!value || typeof value !== "object")
|
|
18040
18257
|
return false;
|
|
@@ -18818,12 +19035,36 @@ var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
|
|
|
18818
19035
|
var UpdateFunctionalProbeError = class extends Error {
|
|
18819
19036
|
reason;
|
|
18820
19037
|
code = "BEELINE_UPDATE_FUNCTIONAL_PROBE_FAILED";
|
|
19038
|
+
/** Present when the turn failed on a provider refusal with an HTTP status. */
|
|
19039
|
+
providerRefusal;
|
|
18821
19040
|
constructor(reason, detail, options) {
|
|
18822
19041
|
super(`functional update probe failed (${reason}): ${detail}`, options);
|
|
18823
19042
|
this.reason = reason;
|
|
18824
19043
|
this.name = "UpdateFunctionalProbeError";
|
|
19044
|
+
this.providerRefusal = options?.providerRefusal;
|
|
18825
19045
|
}
|
|
18826
19046
|
};
|
|
19047
|
+
async function probeOutcome(run2) {
|
|
19048
|
+
try {
|
|
19049
|
+
await run2();
|
|
19050
|
+
return { kind: "served" };
|
|
19051
|
+
} catch (error) {
|
|
19052
|
+
if (error instanceof UpdateFunctionalProbeError && error.providerRefusal) {
|
|
19053
|
+
return { kind: "refused", ...error.providerRefusal };
|
|
19054
|
+
}
|
|
19055
|
+
return { kind: "unavailable", reason: error instanceof Error ? error.message : String(error) };
|
|
19056
|
+
}
|
|
19057
|
+
}
|
|
19058
|
+
function describeCurrentReleaseOutcome(outcome) {
|
|
19059
|
+
switch (outcome.kind) {
|
|
19060
|
+
case "served":
|
|
19061
|
+
return "the current release answered";
|
|
19062
|
+
case "refused":
|
|
19063
|
+
return `the current release got a different refusal (${outcome.reason})`;
|
|
19064
|
+
case "unavailable":
|
|
19065
|
+
return `the current release could not be compared (${outcome.reason})`;
|
|
19066
|
+
}
|
|
19067
|
+
}
|
|
18827
19068
|
async function runUpdateFunctionalProbe(input) {
|
|
18828
19069
|
const command = input.config.agentCommand ?? input.config.agentBinary;
|
|
18829
19070
|
const harness = input.config.agentKind ?? command;
|
|
@@ -18833,7 +19074,7 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
18833
19074
|
if (input.sandboxRequired && !input.config.bwrapPath) {
|
|
18834
19075
|
throw new UpdateFunctionalProbeError("sandbox-unavailable", "the configured bubblewrap boundary did not pass its startup self-test");
|
|
18835
19076
|
}
|
|
18836
|
-
const root = resolve23(input.runtimeDir, "update-functional-probe");
|
|
19077
|
+
const root = input.probeRoot ?? resolve23(input.runtimeDir, "update-functional-probe");
|
|
18837
19078
|
const cwd = resolve23(root, "checkout");
|
|
18838
19079
|
const homeRoot = resolve23(root, "agent-home");
|
|
18839
19080
|
await rm7(root, { recursive: true, force: true });
|
|
@@ -18915,12 +19156,28 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
18915
19156
|
});
|
|
18916
19157
|
const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
|
|
18917
19158
|
if (!modelSide) {
|
|
18918
|
-
|
|
19159
|
+
const refusal = explained.record?.kind === "error" && explained.record.status !== void 0 ? { status: explained.record.status, reason: explained.record.reason } : void 0;
|
|
19160
|
+
const detail = `the harness completed a session/prompt without an agent answer: ${explained.reason}`;
|
|
19161
|
+
if (!refusal || !input.compareWithCurrentRelease) {
|
|
19162
|
+
throw new UpdateFunctionalProbeError("turn-failed", detail, {
|
|
19163
|
+
...refusal ? { providerRefusal: refusal } : {}
|
|
19164
|
+
});
|
|
19165
|
+
}
|
|
19166
|
+
const current = await input.compareWithCurrentRelease(refusal);
|
|
19167
|
+
if (current.kind !== "refused" || current.status !== refusal.status) {
|
|
19168
|
+
throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { providerRefusal: refusal });
|
|
19169
|
+
}
|
|
19170
|
+
const reason = `${explained.reason} (the current release gets the same ${refusal.status})`;
|
|
19171
|
+
console.warn(`[body] update probe: the provider refused this release and the current release alike (${refusal.reason}); that refusal is not this bundle's doing, so the probe passes as inconclusive`);
|
|
19172
|
+
modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: reason };
|
|
19173
|
+
} else {
|
|
19174
|
+
console.warn(`[body] update probe: the bundle reached the model boundary but the model answered nothing (${explained.reason}); that is the model's own answer on any release, so the probe passes without one`);
|
|
19175
|
+
modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
|
|
18919
19176
|
}
|
|
18920
|
-
console.warn(`[body] update probe: the bundle reached the model boundary but the model answered nothing (${explained.reason}); that is the model's own answer on any release, so the probe passes without one`);
|
|
18921
|
-
modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
|
|
18922
19177
|
}
|
|
18923
19178
|
} catch (error) {
|
|
19179
|
+
if (error instanceof UpdateFunctionalProbeError)
|
|
19180
|
+
throw error;
|
|
18924
19181
|
throw new UpdateFunctionalProbeError("turn-failed", error instanceof Error ? error.message : String(error), { cause: error });
|
|
18925
19182
|
}
|
|
18926
19183
|
} catch (error) {
|
|
@@ -18942,6 +19199,137 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
18942
19199
|
}
|
|
18943
19200
|
}
|
|
18944
19201
|
|
|
19202
|
+
// apps/body/dist/current-release-probe.js
|
|
19203
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
19204
|
+
import { dirname as dirname14, join as join7 } from "node:path";
|
|
19205
|
+
init_self_update();
|
|
19206
|
+
var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 8e4;
|
|
19207
|
+
var UPDATE_PROBE_COMMAND = "update-probe";
|
|
19208
|
+
function parseReport(line) {
|
|
19209
|
+
let parsed;
|
|
19210
|
+
try {
|
|
19211
|
+
parsed = JSON.parse(line);
|
|
19212
|
+
} catch {
|
|
19213
|
+
return void 0;
|
|
19214
|
+
}
|
|
19215
|
+
if (!parsed || typeof parsed !== "object")
|
|
19216
|
+
return void 0;
|
|
19217
|
+
const report = parsed;
|
|
19218
|
+
if (report.probe === "served")
|
|
19219
|
+
return { probe: "served" };
|
|
19220
|
+
if (report.probe === "refused" && typeof report.status === "number" && typeof report.reason === "string") {
|
|
19221
|
+
return { probe: "refused", status: report.status, reason: report.reason };
|
|
19222
|
+
}
|
|
19223
|
+
if (report.probe === "failed" && typeof report.reason === "string") {
|
|
19224
|
+
return { probe: "failed", reason: report.reason };
|
|
19225
|
+
}
|
|
19226
|
+
return void 0;
|
|
19227
|
+
}
|
|
19228
|
+
function outcomeFromReport(report) {
|
|
19229
|
+
switch (report.probe) {
|
|
19230
|
+
case "served":
|
|
19231
|
+
return { kind: "served" };
|
|
19232
|
+
case "refused":
|
|
19233
|
+
return { kind: "refused", status: report.status, reason: report.reason };
|
|
19234
|
+
case "failed":
|
|
19235
|
+
return { kind: "unavailable", reason: report.reason };
|
|
19236
|
+
}
|
|
19237
|
+
}
|
|
19238
|
+
async function probeReleaseInSubprocess(input) {
|
|
19239
|
+
const bundleDir = join7(input.layout.releasesRoot, input.releaseId);
|
|
19240
|
+
const entrypoint = await resolveBundleEntrypoint(bundleDir);
|
|
19241
|
+
if (!entrypoint) {
|
|
19242
|
+
return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
|
|
19243
|
+
}
|
|
19244
|
+
const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
|
|
19245
|
+
return new Promise((resolve26) => {
|
|
19246
|
+
const child = spawn7(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
19247
|
+
let stdout6 = "";
|
|
19248
|
+
let stderr = "";
|
|
19249
|
+
let settled = false;
|
|
19250
|
+
const finish = (outcome) => {
|
|
19251
|
+
if (settled)
|
|
19252
|
+
return;
|
|
19253
|
+
settled = true;
|
|
19254
|
+
clearTimeout(timer);
|
|
19255
|
+
resolve26(outcome);
|
|
19256
|
+
};
|
|
19257
|
+
const timer = setTimeout(() => {
|
|
19258
|
+
child.kill("SIGKILL");
|
|
19259
|
+
finish({
|
|
19260
|
+
kind: "unavailable",
|
|
19261
|
+
reason: `release ${input.releaseId} did not finish its probe within ${timeoutMs}ms`
|
|
19262
|
+
});
|
|
19263
|
+
}, timeoutMs);
|
|
19264
|
+
child.stdout.on("data", (chunk) => {
|
|
19265
|
+
stdout6 += chunk.toString();
|
|
19266
|
+
});
|
|
19267
|
+
child.stderr.on("data", (chunk) => {
|
|
19268
|
+
stderr += chunk.toString();
|
|
19269
|
+
});
|
|
19270
|
+
child.on("error", (error) => {
|
|
19271
|
+
finish({ kind: "unavailable", reason: `release ${input.releaseId} could not be spawned: ${error.message}` });
|
|
19272
|
+
});
|
|
19273
|
+
child.on("close", (code, signal) => {
|
|
19274
|
+
const lines = stdout6.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
19275
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
19276
|
+
const report = parseReport(lines[index]);
|
|
19277
|
+
if (report) {
|
|
19278
|
+
finish(outcomeFromReport(report));
|
|
19279
|
+
return;
|
|
19280
|
+
}
|
|
19281
|
+
}
|
|
19282
|
+
const tail = stderr.trim().split(/\r?\n/).slice(-3).join(" | ").slice(0, 300);
|
|
19283
|
+
finish({
|
|
19284
|
+
kind: "unavailable",
|
|
19285
|
+
reason: `release ${input.releaseId} printed no probe report (exit ${signal ?? code})` + (tail ? `: ${tail}` : "")
|
|
19286
|
+
});
|
|
19287
|
+
});
|
|
19288
|
+
});
|
|
19289
|
+
}
|
|
19290
|
+
async function runUpdateProbeCommand(args, options = {}) {
|
|
19291
|
+
const env = options.env ?? process.env;
|
|
19292
|
+
const write = options.write ?? ((line) => process.stdout.write(`${line}
|
|
19293
|
+
`));
|
|
19294
|
+
const configFlag = args.indexOf("--config");
|
|
19295
|
+
const configArg = configFlag >= 0 ? args[configFlag + 1] : void 0;
|
|
19296
|
+
if (!configArg)
|
|
19297
|
+
throw new Error(`${UPDATE_PROBE_COMMAND} requires --config <runtime.json>`);
|
|
19298
|
+
const configPath = await resolveRuntimeConfigPath(configArg);
|
|
19299
|
+
const runtime = await readRuntimeRecord(configPath);
|
|
19300
|
+
const agent = runtimeAgentCommand(runtime);
|
|
19301
|
+
const config = loadBodyConfig({
|
|
19302
|
+
workspaceRoot: join7(dirname14(configPath), "workspace"),
|
|
19303
|
+
llmEnvFile: runtime.llmEnvFile,
|
|
19304
|
+
env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
|
|
19305
|
+
agent
|
|
19306
|
+
});
|
|
19307
|
+
if (runtime.sharedSkills)
|
|
19308
|
+
config.sharedSkills = [...runtime.sharedSkills];
|
|
19309
|
+
if (runtime.modelSelection)
|
|
19310
|
+
config.modelSelection = runtime.modelSelection;
|
|
19311
|
+
config.runtimeConfigPath = configPath;
|
|
19312
|
+
const sandbox = detectBwrapSandbox({ ...runtime.sandbox ? { policy: runtime.sandbox } : {}, env });
|
|
19313
|
+
if (sandbox.path)
|
|
19314
|
+
config.bwrapPath = sandbox.path;
|
|
19315
|
+
if (runtime.sandboxMaskPaths?.length) {
|
|
19316
|
+
config.sandboxMaskPaths = [...config.sandboxMaskPaths ?? [], ...runtime.sandboxMaskPaths];
|
|
19317
|
+
}
|
|
19318
|
+
const layout = beelineInstallLayout(env);
|
|
19319
|
+
const releaseId = (layout && await activeReleaseId(layout).catch(() => void 0)) ?? "unknown";
|
|
19320
|
+
const runtimeDir = dirname14(configPath);
|
|
19321
|
+
const outcome = await probeOutcome(() => (options.probe ?? runUpdateFunctionalProbe)({
|
|
19322
|
+
config,
|
|
19323
|
+
runtimeDir,
|
|
19324
|
+
releaseId,
|
|
19325
|
+
sandboxRequired: runtime.sandbox !== "off",
|
|
19326
|
+
// The successor's probe still holds `<runtimeDir>/update-functional-probe`.
|
|
19327
|
+
probeRoot: join7(runtimeDir, "current-release-probe")
|
|
19328
|
+
}));
|
|
19329
|
+
const report = outcome.kind === "served" ? { probe: "served" } : outcome.kind === "refused" ? { probe: "refused", status: outcome.status, reason: outcome.reason } : { probe: "failed", reason: outcome.reason };
|
|
19330
|
+
write(JSON.stringify(report));
|
|
19331
|
+
}
|
|
19332
|
+
|
|
18945
19333
|
// apps/body/dist/release-status.js
|
|
18946
19334
|
import { readFile as readFile12, readdir as readdir4, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
18947
19335
|
import { resolve as resolve24 } from "node:path";
|
|
@@ -19004,7 +19392,7 @@ var DaemonExitError = class extends Error {
|
|
|
19004
19392
|
};
|
|
19005
19393
|
async function runStoredDaemon(pathOrPointer) {
|
|
19006
19394
|
const configPath = await resolveRuntimeConfigPath(pathOrPointer);
|
|
19007
|
-
daemonFailureRuntimeDir =
|
|
19395
|
+
daemonFailureRuntimeDir = dirname15(configPath);
|
|
19008
19396
|
const accessMigration = await migrateRuntimeRecordAccessPolicy(configPath);
|
|
19009
19397
|
let runtime = accessMigration.runtime;
|
|
19010
19398
|
if (!runtime.transport) {
|
|
@@ -19016,7 +19404,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
19016
19404
|
runtime = activated.runtime;
|
|
19017
19405
|
const daemonApi = activated.client;
|
|
19018
19406
|
const agent = runtimeAgentCommand(runtime);
|
|
19019
|
-
await writeFile14(resolve25(
|
|
19407
|
+
await writeFile14(resolve25(dirname15(configPath), "daemon.pid"), `${process.pid}
|
|
19020
19408
|
`, { mode: 384 });
|
|
19021
19409
|
const env = {
|
|
19022
19410
|
...process.env,
|
|
@@ -19024,7 +19412,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
19024
19412
|
BUZZ_DEV_MCP_BIN: runtime.mcpBinary
|
|
19025
19413
|
};
|
|
19026
19414
|
const config = loadBodyConfig({
|
|
19027
|
-
workspaceRoot: resolve25(
|
|
19415
|
+
workspaceRoot: resolve25(dirname15(configPath), "workspace"),
|
|
19028
19416
|
llmEnvFile: runtime.llmEnvFile,
|
|
19029
19417
|
env,
|
|
19030
19418
|
agent
|
|
@@ -19059,7 +19447,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
19059
19447
|
const stop = () => controller.abort();
|
|
19060
19448
|
process.once("SIGINT", stop);
|
|
19061
19449
|
process.once("SIGTERM", stop);
|
|
19062
|
-
const runtimeDir =
|
|
19450
|
+
const runtimeDir = dirname15(configPath);
|
|
19063
19451
|
const layout = beelineInstallLayout(process.env);
|
|
19064
19452
|
const notifier = new SystemdNotifier();
|
|
19065
19453
|
let rollbackAlertDrain;
|
|
@@ -19126,6 +19514,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
19126
19514
|
onEstablished: async () => {
|
|
19127
19515
|
let functionalProof;
|
|
19128
19516
|
if (layout && pendingSuccessor) {
|
|
19517
|
+
const currentReleaseId = (await readUpdateAttempt(layout))?.previousReleaseId;
|
|
19129
19518
|
const gate = await gateManagedSuccessor({
|
|
19130
19519
|
layout,
|
|
19131
19520
|
runtimeDir,
|
|
@@ -19135,7 +19524,18 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
19135
19524
|
config,
|
|
19136
19525
|
runtimeDir,
|
|
19137
19526
|
releaseId: loadedRelease ?? "unknown",
|
|
19138
|
-
sandboxRequired: runtime.sandbox !== "off"
|
|
19527
|
+
sandboxRequired: runtime.sandbox !== "off",
|
|
19528
|
+
...currentReleaseId ? {
|
|
19529
|
+
compareWithCurrentRelease: async (refusal) => {
|
|
19530
|
+
console.warn(`[thin-core] successor probe refused by the provider (${refusal.reason}); probing the current release ${currentReleaseId} for the same refusal`);
|
|
19531
|
+
await extendSystemdStartTimeout(CURRENT_RELEASE_PROBE_TIMEOUT_MS + 15e3);
|
|
19532
|
+
return probeReleaseInSubprocess({
|
|
19533
|
+
layout,
|
|
19534
|
+
releaseId: currentReleaseId,
|
|
19535
|
+
runtimeConfigPath: configPath
|
|
19536
|
+
});
|
|
19537
|
+
}
|
|
19538
|
+
} : {}
|
|
19139
19539
|
})
|
|
19140
19540
|
});
|
|
19141
19541
|
if (gate.kind === "failed") {
|
|
@@ -19185,7 +19585,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
19185
19585
|
throw error;
|
|
19186
19586
|
} finally {
|
|
19187
19587
|
await notifier.stopping(stoppingStatus).catch(() => void 0);
|
|
19188
|
-
const pidPath = resolve25(
|
|
19588
|
+
const pidPath = resolve25(dirname15(configPath), "daemon.pid");
|
|
19189
19589
|
const recorded = Number((await readFile13(pidPath, "utf8").catch(() => "")).trim());
|
|
19190
19590
|
if (recorded === process.pid) {
|
|
19191
19591
|
await unlink3(pidPath).catch(() => void 0);
|
|
@@ -19242,7 +19642,7 @@ async function main() {
|
|
|
19242
19642
|
const agentPubkey = agentFlag >= 0 ? args[agentFlag + 1] : void 0;
|
|
19243
19643
|
if (!configPath && agentPubkey) {
|
|
19244
19644
|
const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
|
|
19245
|
-
configPath = configs.find((candidate) =>
|
|
19645
|
+
configPath = configs.find((candidate) => dirname15(candidate).endsWith(agentPubkey));
|
|
19246
19646
|
}
|
|
19247
19647
|
if (!configPath && agentPubkey) {
|
|
19248
19648
|
throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing systemd restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
|
|
@@ -19256,6 +19656,10 @@ async function main() {
|
|
|
19256
19656
|
await runUpdateCommand(args);
|
|
19257
19657
|
return;
|
|
19258
19658
|
}
|
|
19659
|
+
if (command === UPDATE_PROBE_COMMAND) {
|
|
19660
|
+
await runUpdateProbeCommand(args);
|
|
19661
|
+
return;
|
|
19662
|
+
}
|
|
19259
19663
|
if (command === "start") {
|
|
19260
19664
|
await runStartCommand(args, interactiveUi);
|
|
19261
19665
|
return;
|
|
@@ -19266,7 +19670,7 @@ async function main() {
|
|
|
19266
19670
|
if (!agentPubkey)
|
|
19267
19671
|
throw new Error("stop requires --agent <pubkey>");
|
|
19268
19672
|
const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
|
|
19269
|
-
const configPath = configs.find((candidate) =>
|
|
19673
|
+
const configPath = configs.find((candidate) => dirname15(candidate).endsWith(agentPubkey));
|
|
19270
19674
|
if (!configPath)
|
|
19271
19675
|
throw new Error(`no stored runtime found for agent ${agentPubkey}`);
|
|
19272
19676
|
const runtime = await readRuntimeRecord(configPath);
|