usebeeline 0.0.58 → 0.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -22
- package/dist/usebeeline.mjs +185 -168
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,12 +41,12 @@ npx usebeeline connect
|
|
|
41
41
|
|
|
42
42
|
The command asks for the pairing code shown in the Beeline app, then four questions and no more:
|
|
43
43
|
|
|
44
|
-
| Step
|
|
45
|
-
|
|
|
46
|
-
| Harness
|
|
47
|
-
| Provider | Goose and Pi only — OpenRouter (default), OpenAI, Anthropic, Google, or xAI
|
|
48
|
-
| API key
|
|
49
|
-
| Model
|
|
44
|
+
| Step | What it asks |
|
|
45
|
+
| -------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| Harness | Claude Code, Codex, Goose, Pi, or Grok |
|
|
47
|
+
| Provider | Goose and Pi only — OpenRouter (default), OpenAI, Anthropic, Google, or xAI |
|
|
48
|
+
| API key | Goose and Pi only — verified against the provider, then saved to `~/.config/beeline/providers.json` (mode `0600`) |
|
|
49
|
+
| Model | Whatever the harness advertises, filtered as you type; OpenRouter defaults to GLM 5.3 Flash |
|
|
50
50
|
|
|
51
51
|
It does **not** ask for a name or a soul. The server assigns the agent one of twelve animals nobody in your Workspace is already wearing, and prints it:
|
|
52
52
|
|
|
@@ -102,7 +102,7 @@ The corner receives a GitHub App token scoped to **that one repository**, instal
|
|
|
102
102
|
- **When the sandbox cannot be built, the daemon says so and keeps serving.** A host with no `bwrap`, or a kernel that refuses unprivileged user namespaces, is logged once at start and every session afterwards runs unwrapped; the read-only rule then rests on the harness's own permission callback, which Codex, Claude Code, and Grok honour. Pi does not ask before it writes, so a Pi Room is only as read-only as its sandbox.
|
|
103
103
|
- **Write access requires a corner.** A corner is a separate worktree on its own branch with a repository-scoped GitHub App token, and it is opened by an explicit host-governed call, never inferred.
|
|
104
104
|
- **Reach outside the sandbox is a grant.** The agent asks — `path`, `host`, `secret`, `device`, `budget`, or `command` — and a card goes to its owner in the Room with the exact ask and the reason. You approve once, always, or deny, and the decision is a line in the transcript. Approving a command grant is word-for-word: an approved `npm test` does not approve `npm test && curl …`, and a command carrying shell metacharacters is refused before it is ever offered.
|
|
105
|
-
- **Yolo mode** flips a single agent to auto-approval
|
|
105
|
+
- **Yolo mode** flips a single agent to auto-approval and is settable only by that agent's owner. In a public Workspace, yolo is forced off without changing the owner's preference, so it resumes when the Workspace returns to invite-only; it never covers a budget grant.
|
|
106
106
|
- **Provider keys never reach Beeline's servers.** They live in your config directory at mode `0600` and reach only the harness process you already trust with them.
|
|
107
107
|
- **Honest about what is not built yet:** today a `command` grant is the one kind that actually changes what a running agent may do. `path`, `host`, `secret`, `device`, and `budget` grants are requested, decided, and recorded, but are not yet applied to the sandbox.
|
|
108
108
|
|
|
@@ -124,24 +124,24 @@ Two MCP surfaces are mounted into every agent session.
|
|
|
124
124
|
|
|
125
125
|
`beeline-readonly-mcp` — reading, in a Room and in a corner:
|
|
126
126
|
|
|
127
|
-
| Tool
|
|
128
|
-
|
|
|
129
|
-
| `list_files`, `read_file`
|
|
130
|
-
| `search_text`
|
|
131
|
-
| `git_log`, `git_show`, `git_diff`, `git_status` | Read repository history and state
|
|
132
|
-
| `read_agent_file`
|
|
133
|
-
| `write_memory`
|
|
127
|
+
| Tool | What it does |
|
|
128
|
+
| ----------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
129
|
+
| `list_files`, `read_file` | Walk and read the checkout |
|
|
130
|
+
| `search_text` | Search the checkout |
|
|
131
|
+
| `git_log`, `git_show`, `git_diff`, `git_status` | Read repository history and state |
|
|
132
|
+
| `read_agent_file` | Read the agent's approved skills or Workspace memory |
|
|
133
|
+
| `write_memory` | Replace the agent's private Workspace `MEMORY.md` — the only memory write a Room allows |
|
|
134
134
|
|
|
135
135
|
`beeline-agent` — acting, host-governed:
|
|
136
136
|
|
|
137
|
-
| Tool
|
|
138
|
-
|
|
|
139
|
-
| `open_corner`
|
|
140
|
-
| `pr_checks_status`
|
|
141
|
-
| `attach_file`
|
|
142
|
-
| `create_schedule`, `list_schedules`, `delete_schedule` | Everywhere
|
|
143
|
-
| `request_grant`
|
|
144
|
-
| `run_granted_command`
|
|
137
|
+
| Tool | Where | What it does |
|
|
138
|
+
| ------------------------------------------------------ | --------------- | ------------------------------------------------------------- |
|
|
139
|
+
| `open_corner` | Top-level Rooms | Open one write-enabled corner with a ≤24-word objective |
|
|
140
|
+
| `pr_checks_status` | Corners | Read the server-posted checks verdict and human hold state |
|
|
141
|
+
| `attach_file` | Everywhere | Attach one file from the checkout or scratch dir to the reply |
|
|
142
|
+
| `create_schedule`, `list_schedules`, `delete_schedule` | Everywhere | Run a prompt again later — interval minutes or a 5-field cron |
|
|
143
|
+
| `request_grant` | Everywhere | Ask the owner for reach outside the sandbox |
|
|
144
|
+
| `run_granted_command` | Everywhere | Run a command an approved grant covers, outside the sandbox |
|
|
145
145
|
|
|
146
146
|
## The app
|
|
147
147
|
|
package/dist/usebeeline.mjs
CHANGED
|
@@ -8126,6 +8126,7 @@ async function syncAgentModelCatalog(input) {
|
|
|
8126
8126
|
|
|
8127
8127
|
// apps/body/dist/daemon-api-client.js
|
|
8128
8128
|
import { resolve as resolve7 } from "node:path";
|
|
8129
|
+
import { randomUUID } from "node:crypto";
|
|
8129
8130
|
|
|
8130
8131
|
// node_modules/ws/wrapper.mjs
|
|
8131
8132
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -16940,12 +16941,11 @@ var DaemonApiClient = class {
|
|
|
16940
16941
|
agentId;
|
|
16941
16942
|
fetchImpl;
|
|
16942
16943
|
webSocketFactory;
|
|
16944
|
+
presenceLifecycleId = randomUUID();
|
|
16943
16945
|
liveSocket;
|
|
16944
16946
|
liveReconnect;
|
|
16945
16947
|
liveReconnectDelayMs = 1e3;
|
|
16946
16948
|
liveRooms = /* @__PURE__ */ new Map();
|
|
16947
|
-
parityPending = /* @__PURE__ */ new Map();
|
|
16948
|
-
parityMisses = /* @__PURE__ */ new Map();
|
|
16949
16949
|
constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
|
|
16950
16950
|
this.baseUrl = baseUrl;
|
|
16951
16951
|
this.daemonToken = daemonToken;
|
|
@@ -16957,18 +16957,21 @@ var DaemonApiClient = class {
|
|
|
16957
16957
|
connection() {
|
|
16958
16958
|
return { baseUrl: this.baseUrl, daemonToken: this.daemonToken, agentId: this.agentId };
|
|
16959
16959
|
}
|
|
16960
|
-
/** Add one Room to this agent's shared live socket.
|
|
16961
|
-
liveSubscribe(roomId, cursor3, onItems) {
|
|
16960
|
+
/** Add one Room to this agent's shared live socket. */
|
|
16961
|
+
liveSubscribe(roomId, cursor3, onItems, onState, presence) {
|
|
16962
16962
|
const existing = this.liveRooms.get(roomId);
|
|
16963
16963
|
if (existing) {
|
|
16964
16964
|
existing.cursor = cursor3 ?? existing.cursor;
|
|
16965
16965
|
existing.onItems = onItems ?? existing.onItems;
|
|
16966
|
+
existing.onState = onState ?? existing.onState;
|
|
16967
|
+
existing.presence = presence ?? existing.presence;
|
|
16966
16968
|
} else {
|
|
16967
16969
|
this.liveRooms.set(roomId, {
|
|
16968
16970
|
...cursor3 ? { cursor: cursor3 } : {},
|
|
16969
16971
|
pushedIds: /* @__PURE__ */ new Set(),
|
|
16970
|
-
|
|
16971
|
-
...
|
|
16972
|
+
...onItems ? { onItems } : {},
|
|
16973
|
+
...onState ? { onState } : {},
|
|
16974
|
+
...presence ? { presence } : {}
|
|
16972
16975
|
});
|
|
16973
16976
|
}
|
|
16974
16977
|
this.ensureLiveSocket();
|
|
@@ -16976,11 +16979,6 @@ var DaemonApiClient = class {
|
|
|
16976
16979
|
this.sendLiveSubscription(roomId);
|
|
16977
16980
|
return () => {
|
|
16978
16981
|
this.liveRooms.delete(roomId);
|
|
16979
|
-
const pending = this.parityPending.get(roomId);
|
|
16980
|
-
if (pending)
|
|
16981
|
-
for (const timer of pending.values())
|
|
16982
|
-
clearTimeout(timer);
|
|
16983
|
-
this.parityPending.delete(roomId);
|
|
16984
16982
|
if (this.liveSocket?.readyState === wrapper_default.OPEN) {
|
|
16985
16983
|
this.liveSocket.send(JSON.stringify({ type: "unsubscribe", roomId }));
|
|
16986
16984
|
}
|
|
@@ -16996,42 +16994,6 @@ var DaemonApiClient = class {
|
|
|
16996
16994
|
if (room && cursor3)
|
|
16997
16995
|
room.cursor = cursor3;
|
|
16998
16996
|
}
|
|
16999
|
-
/** Count a poll delivery only if push still has not produced its id after a grace window. */
|
|
17000
|
-
notePolled(roomId, items) {
|
|
17001
|
-
const room = this.liveRooms.get(roomId);
|
|
17002
|
-
if (!room)
|
|
17003
|
-
return;
|
|
17004
|
-
const now2 = Date.now();
|
|
17005
|
-
if (room.parityReportedAt === void 0 || now2 - room.parityReportedAt >= 6e4) {
|
|
17006
|
-
room.parityReportedAt = now2;
|
|
17007
|
-
console.info(`[thin-core] push parity room=${roomId} poll_only=${room.parityMissIds.size}`);
|
|
17008
|
-
}
|
|
17009
|
-
let pending = this.parityPending.get(roomId);
|
|
17010
|
-
if (!pending) {
|
|
17011
|
-
pending = /* @__PURE__ */ new Map();
|
|
17012
|
-
this.parityPending.set(roomId, pending);
|
|
17013
|
-
}
|
|
17014
|
-
for (const item of items) {
|
|
17015
|
-
if (room.pushedIds.has(item.id) || pending.has(item.id))
|
|
17016
|
-
continue;
|
|
17017
|
-
const timer = setTimeout(() => {
|
|
17018
|
-
pending.delete(item.id);
|
|
17019
|
-
if (room.pushedIds.has(item.id) || room.parityMissIds.has(item.id))
|
|
17020
|
-
return;
|
|
17021
|
-
room.parityMissIds.add(item.id);
|
|
17022
|
-
while (room.parityMissIds.size > 1e4)
|
|
17023
|
-
room.parityMissIds.delete(room.parityMissIds.values().next().value);
|
|
17024
|
-
const count = room.parityMissIds.size;
|
|
17025
|
-
this.parityMisses.set(roomId, count);
|
|
17026
|
-
console.warn(`[thin-core] push parity miss room=${roomId} count=${count}`);
|
|
17027
|
-
}, 5e3);
|
|
17028
|
-
timer.unref?.();
|
|
17029
|
-
pending.set(item.id, timer);
|
|
17030
|
-
}
|
|
17031
|
-
}
|
|
17032
|
-
pushParityMissCount(roomId) {
|
|
17033
|
-
return this.parityMisses.get(roomId) ?? 0;
|
|
17034
|
-
}
|
|
17035
16997
|
async execute(name, input) {
|
|
17036
16998
|
const candidate = input;
|
|
17037
16999
|
if (typeof candidate.agentId === "string" && candidate.agentId !== this.agentId) {
|
|
@@ -17052,7 +17014,9 @@ var DaemonApiClient = class {
|
|
|
17052
17014
|
ensureLiveSocket() {
|
|
17053
17015
|
if (this.liveSocket || !this.liveRooms.size)
|
|
17054
17016
|
return;
|
|
17055
|
-
const socket = this.webSocketFactory(this.baseUrl.replace(/^http/, "ws") + "/v1/phone/live", [
|
|
17017
|
+
const socket = this.webSocketFactory(this.baseUrl.replace(/^http/, "ws") + "/v1/phone/live", [
|
|
17018
|
+
`bearer.${this.daemonToken}`
|
|
17019
|
+
]);
|
|
17056
17020
|
this.liveSocket = socket;
|
|
17057
17021
|
socket.onopen = () => {
|
|
17058
17022
|
this.liveReconnectDelayMs = 1e3;
|
|
@@ -17069,6 +17033,14 @@ var DaemonApiClient = class {
|
|
|
17069
17033
|
if (!value || typeof value !== "object")
|
|
17070
17034
|
return;
|
|
17071
17035
|
const event = value;
|
|
17036
|
+
if (event.type === "subscribed" && typeof event.roomId === "string") {
|
|
17037
|
+
const capabilities = event.capabilities;
|
|
17038
|
+
this.liveRooms.get(event.roomId)?.onState?.(true, {
|
|
17039
|
+
pushIntake: capabilities?.pushIntake === true,
|
|
17040
|
+
connectionPresence: capabilities?.connectionPresence === true
|
|
17041
|
+
});
|
|
17042
|
+
return;
|
|
17043
|
+
}
|
|
17072
17044
|
if (event.type !== "inbox" || typeof event.roomId !== "string" || !Array.isArray(event.items))
|
|
17073
17045
|
return;
|
|
17074
17046
|
const room = this.liveRooms.get(event.roomId);
|
|
@@ -17083,14 +17055,6 @@ var DaemonApiClient = class {
|
|
|
17083
17055
|
if (!room.pushedIds.has(id))
|
|
17084
17056
|
items.push(candidate);
|
|
17085
17057
|
room.pushedIds.add(id);
|
|
17086
|
-
if (room.parityMissIds.delete(id)) {
|
|
17087
|
-
this.parityMisses.set(event.roomId, room.parityMissIds.size);
|
|
17088
|
-
console.info(`[thin-core] push parity room=${event.roomId} poll_only=${room.parityMissIds.size}`);
|
|
17089
|
-
}
|
|
17090
|
-
const pending = this.parityPending.get(event.roomId)?.get(id);
|
|
17091
|
-
if (pending)
|
|
17092
|
-
clearTimeout(pending);
|
|
17093
|
-
this.parityPending.get(event.roomId)?.delete(id);
|
|
17094
17058
|
}
|
|
17095
17059
|
}
|
|
17096
17060
|
while (room.pushedIds.size > 1e4)
|
|
@@ -17102,6 +17066,8 @@ var DaemonApiClient = class {
|
|
|
17102
17066
|
if (this.liveSocket !== socket)
|
|
17103
17067
|
return;
|
|
17104
17068
|
this.liveSocket = void 0;
|
|
17069
|
+
for (const room of this.liveRooms.values())
|
|
17070
|
+
room.onState?.(false);
|
|
17105
17071
|
if (!this.liveRooms.size)
|
|
17106
17072
|
return;
|
|
17107
17073
|
const delay = this.liveReconnectDelayMs;
|
|
@@ -17119,7 +17085,9 @@ var DaemonApiClient = class {
|
|
|
17119
17085
|
this.liveSocket.send(JSON.stringify({
|
|
17120
17086
|
type: "subscribe",
|
|
17121
17087
|
roomId,
|
|
17122
|
-
|
|
17088
|
+
lifecycleId: this.presenceLifecycleId,
|
|
17089
|
+
...room.cursor ? { cursor: room.cursor } : {},
|
|
17090
|
+
...room.presence
|
|
17123
17091
|
}));
|
|
17124
17092
|
}
|
|
17125
17093
|
};
|
|
@@ -18018,7 +17986,7 @@ import { promisify as promisify3 } from "node:util";
|
|
|
18018
17986
|
|
|
18019
17987
|
// apps/body/dist/agent-home.js
|
|
18020
17988
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
18021
|
-
import { createHash as createHash3, randomUUID } from "node:crypto";
|
|
17989
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
18022
17990
|
import { chmod as chmod2, copyFile, lstat, mkdir as mkdir4, readFile as readFile6, readdir as readdir2, realpath, rename as rename2, rm as rm2, symlink, unlink, writeFile as writeFile5 } from "node:fs/promises";
|
|
18023
17991
|
import { homedir as homedir5 } from "node:os";
|
|
18024
17992
|
import { basename as basename3, dirname as dirname5, join as join3, relative as relative2, resolve as resolve13, sep } from "node:path";
|
|
@@ -18940,7 +18908,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
|
|
|
18940
18908
|
const plan = await planManagedSkills(managedSkills, sharedSkills, optionalShares);
|
|
18941
18909
|
if (await materializedSkillManifest(target) === plan.manifest)
|
|
18942
18910
|
return;
|
|
18943
|
-
const staged = resolve13(parent, `.skills.${process.pid}.${
|
|
18911
|
+
const staged = resolve13(parent, `.skills.${process.pid}.${randomUUID2()}.tmp`);
|
|
18944
18912
|
await mkdir4(staged, { mode: 448 });
|
|
18945
18913
|
try {
|
|
18946
18914
|
for (const entry of plan.entries) {
|
|
@@ -19192,7 +19160,7 @@ async function writeIsolatedHarnessFile(path, content) {
|
|
|
19192
19160
|
if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
|
|
19193
19161
|
throw new Error(`isolated harness parent is not a real directory: ${parent}`);
|
|
19194
19162
|
}
|
|
19195
|
-
const temporary = resolve13(parent, `.${basename3(path)}.${process.pid}.${
|
|
19163
|
+
const temporary = resolve13(parent, `.${basename3(path)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
19196
19164
|
try {
|
|
19197
19165
|
await writeFile5(temporary, content, { mode: 384, flag: "wx" });
|
|
19198
19166
|
await chmod2(temporary, 384);
|
|
@@ -20807,6 +20775,14 @@ function cornerClosePollMs(random = Math.random) {
|
|
|
20807
20775
|
var MonolithCornerTurnLoop = class {
|
|
20808
20776
|
options;
|
|
20809
20777
|
agent;
|
|
20778
|
+
reconciliationRequested = true;
|
|
20779
|
+
wakeIntake;
|
|
20780
|
+
/** Called by the daemon's one slow workspace reconciliation sweep. */
|
|
20781
|
+
requestReconciliation() {
|
|
20782
|
+
this.reconciliationRequested = true;
|
|
20783
|
+
this.wakeIntake?.();
|
|
20784
|
+
this.wakeIntake = void 0;
|
|
20785
|
+
}
|
|
20810
20786
|
client;
|
|
20811
20787
|
sessionId;
|
|
20812
20788
|
/** The configuration the live session baked in; a change invalidates it. */
|
|
@@ -20951,11 +20927,9 @@ var MonolithCornerTurnLoop = class {
|
|
|
20951
20927
|
})
|
|
20952
20928
|
}) : {};
|
|
20953
20929
|
const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
};
|
|
20958
|
-
if (this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
|
|
20930
|
+
const repository = this.options.repository;
|
|
20931
|
+
let githubEnv = repository ? { GH_TOKEN: repository.githubToken, GITHUB_TOKEN: repository.githubToken } : {};
|
|
20932
|
+
if (repository && this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
|
|
20959
20933
|
const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
|
|
20960
20934
|
const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
|
|
20961
20935
|
githubEnv = await installCornerGitHubWrappers({
|
|
@@ -20994,7 +20968,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
20994
20968
|
mode: "edit",
|
|
20995
20969
|
cwd: this.options.worktreePath,
|
|
20996
20970
|
worktreePath: this.options.worktreePath,
|
|
20997
|
-
gitCommonDir:
|
|
20971
|
+
...repository ? { gitCommonDir: repository.gitCommonDir } : {},
|
|
20998
20972
|
protectedPaths: [this.options.runtime.supervisorRoot],
|
|
20999
20973
|
harnessStateDirs: stateDirs,
|
|
21000
20974
|
harnessHomeStateDirs: homeStateDirs,
|
|
@@ -21017,19 +20991,21 @@ var MonolithCornerTurnLoop = class {
|
|
|
21017
20991
|
this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
|
|
21018
20992
|
await this.client.start();
|
|
21019
20993
|
const servers = [
|
|
21020
|
-
|
|
21021
|
-
|
|
21022
|
-
|
|
21023
|
-
|
|
21024
|
-
|
|
21025
|
-
|
|
21026
|
-
|
|
21027
|
-
|
|
21028
|
-
|
|
21029
|
-
|
|
21030
|
-
|
|
21031
|
-
|
|
21032
|
-
|
|
20994
|
+
...repository ? [
|
|
20995
|
+
{
|
|
20996
|
+
name: "buzz-dev-mcp",
|
|
20997
|
+
command: this.options.config.mcpBinary,
|
|
20998
|
+
args: [],
|
|
20999
|
+
// ACP hosts launch stdio MCP servers with an explicit, sanitized env.
|
|
21000
|
+
// This token is minted for this exact corner and is also the credential
|
|
21001
|
+
// helper's password source, so its shell commands need the same scope as
|
|
21002
|
+
// the corner harness without inheriting any host credentials.
|
|
21003
|
+
env: [
|
|
21004
|
+
{ name: "GH_TOKEN", value: repository.githubToken },
|
|
21005
|
+
{ name: "GITHUB_TOKEN", value: repository.githubToken }
|
|
21006
|
+
]
|
|
21007
|
+
}
|
|
21008
|
+
] : [],
|
|
21033
21009
|
beelineAgentMcpServer(this.options.config, this.options.api, {
|
|
21034
21010
|
roomId: this.options.parentRoomId,
|
|
21035
21011
|
workspaceId: this.options.workspaceId,
|
|
@@ -21060,17 +21036,23 @@ var MonolithCornerTurnLoop = class {
|
|
|
21060
21036
|
systemPrompt: [
|
|
21061
21037
|
identityInstructions,
|
|
21062
21038
|
personaInstructions,
|
|
21063
|
-
|
|
21064
|
-
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
|
|
21073
|
-
|
|
21039
|
+
...repository ? [
|
|
21040
|
+
`You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
|
|
21041
|
+
"Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
|
|
21042
|
+
`This corner is shared: any of its member agents may be addressed in it and work on ${repository.featureBranch}. Run git pull --rebase origin ${repository.featureBranch} before you push, and never force-push it.`,
|
|
21043
|
+
"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.",
|
|
21044
|
+
'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.',
|
|
21045
|
+
"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.",
|
|
21046
|
+
"If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
|
|
21047
|
+
"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.",
|
|
21048
|
+
'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.',
|
|
21049
|
+
"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.",
|
|
21050
|
+
"Never push directly to the target branch. Never merge a different pull request."
|
|
21051
|
+
] : [
|
|
21052
|
+
"This is a chat-only corner with no repository or GitHub workflow.",
|
|
21053
|
+
"Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then attach_file to send them back to the corner.",
|
|
21054
|
+
"Do not initialize a repository, create a branch, push, open a pull request, or wait for GitHub checks."
|
|
21055
|
+
]
|
|
21074
21056
|
].filter(Boolean).join("\n\n")
|
|
21075
21057
|
});
|
|
21076
21058
|
this.sessionId = opened.sessionId;
|
|
@@ -21203,7 +21185,7 @@ ${this.options.objective}`,
|
|
|
21203
21185
|
${trigger}`,
|
|
21204
21186
|
...attachmentPromptLines(attachments, delivered, this.acceptsImages())
|
|
21205
21187
|
].join("\n"),
|
|
21206
|
-
"Continue the objective. Obey the PR checks and human hold rules in your session instructions.",
|
|
21188
|
+
this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files before calling close_corner.",
|
|
21207
21189
|
MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
|
|
21208
21190
|
].filter(Boolean).join("\n\n");
|
|
21209
21191
|
const stream = new AgentTurnStream({
|
|
@@ -21353,10 +21335,13 @@ ${trigger}`,
|
|
|
21353
21335
|
* fails with that sentence and the server inscribes it in the corner.
|
|
21354
21336
|
*/
|
|
21355
21337
|
async syncBranch() {
|
|
21356
|
-
const
|
|
21338
|
+
const repository = this.options.repository;
|
|
21339
|
+
if (!repository)
|
|
21340
|
+
return;
|
|
21341
|
+
const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => repository.githubToken);
|
|
21357
21342
|
await syncCornerBranch({
|
|
21358
21343
|
worktreePath: this.options.worktreePath,
|
|
21359
|
-
featureBranch:
|
|
21344
|
+
featureBranch: repository.featureBranch,
|
|
21360
21345
|
env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token, GIT_TERMINAL_PROMPT: "0" }
|
|
21361
21346
|
});
|
|
21362
21347
|
}
|
|
@@ -21384,15 +21369,23 @@ ${trigger}`,
|
|
|
21384
21369
|
const processedInboxIds = /* @__PURE__ */ new Set();
|
|
21385
21370
|
const pushedInbox = [];
|
|
21386
21371
|
let pendingPushedCursor;
|
|
21387
|
-
let
|
|
21372
|
+
let liveConnected = false;
|
|
21388
21373
|
const rewindSupported = Array.isArray(activation.rewindIds);
|
|
21389
21374
|
for (const id of activation.rewindIds ?? [])
|
|
21390
21375
|
processedInboxIds.add(id);
|
|
21391
21376
|
const stopLive = api.liveSubscribe?.(cornerId, cursor3, (items, pushedCursor) => {
|
|
21392
21377
|
pushedInbox.push(...items);
|
|
21393
21378
|
pendingPushedCursor = laterInboxCursor(pendingPushedCursor, pushedCursor);
|
|
21394
|
-
|
|
21395
|
-
|
|
21379
|
+
this.wakeIntake?.();
|
|
21380
|
+
this.wakeIntake = void 0;
|
|
21381
|
+
}, (connected, capabilities) => {
|
|
21382
|
+
liveConnected = connected && capabilities?.pushIntake === true;
|
|
21383
|
+
this.wakeIntake?.();
|
|
21384
|
+
this.wakeIntake = void 0;
|
|
21385
|
+
}, {
|
|
21386
|
+
...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
|
|
21387
|
+
...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
|
|
21388
|
+
available: !this.options.config.modelUnavailable
|
|
21396
21389
|
});
|
|
21397
21390
|
const history = await api.execute("getRoomConversation", { roomId: cornerId, limit: 200 });
|
|
21398
21391
|
await this.roster().catch(() => void 0);
|
|
@@ -21407,14 +21400,14 @@ ${trigger}`,
|
|
|
21407
21400
|
let pollWithoutWait = false;
|
|
21408
21401
|
while (!signal?.aborted) {
|
|
21409
21402
|
try {
|
|
21410
|
-
const pollNow = pushedInbox.length
|
|
21403
|
+
const pollNow = pushedInbox.length > 0 || !liveConnected || this.reconciliationRequested;
|
|
21411
21404
|
const inbox = !pollNow ? { items: [], cursor: void 0, closeRequested: false } : await api.execute("getCornerCloseRequests", {
|
|
21412
21405
|
cornerId,
|
|
21413
21406
|
...cursor3 ? { after: cursor3 } : {},
|
|
21414
21407
|
...rewindSupported ? { rewind: true } : {}
|
|
21415
21408
|
});
|
|
21416
21409
|
if (pollNow) {
|
|
21417
|
-
|
|
21410
|
+
this.reconciliationRequested = false;
|
|
21418
21411
|
}
|
|
21419
21412
|
if (inbox.closeRequested) {
|
|
21420
21413
|
await this.options.onCloseRequested();
|
|
@@ -21473,10 +21466,9 @@ This answers your grant request; resume the paused work. If approved and it is a
|
|
|
21473
21466
|
if (pollNow)
|
|
21474
21467
|
this.options.onPoll();
|
|
21475
21468
|
await Promise.race([
|
|
21476
|
-
wait(pollWithoutWait ? 0 : this.options.pollMs ?? cornerClosePollMs(), signal),
|
|
21477
|
-
waitForWake(api, cornerId, signal),
|
|
21469
|
+
wait(pollWithoutWait ? 0 : liveConnected ? 2147483647 : this.options.pollMs ?? cornerClosePollMs(), signal),
|
|
21478
21470
|
pushedInbox.length ? Promise.resolve() : new Promise((resolve30) => {
|
|
21479
|
-
|
|
21471
|
+
this.wakeIntake = resolve30;
|
|
21480
21472
|
})
|
|
21481
21473
|
]);
|
|
21482
21474
|
pollWithoutWait = false;
|
|
@@ -21490,27 +21482,12 @@ This answers your grant request; resume the paused work. If approved and it is a
|
|
|
21490
21482
|
}
|
|
21491
21483
|
} finally {
|
|
21492
21484
|
stopLive?.();
|
|
21485
|
+
this.wakeIntake = void 0;
|
|
21493
21486
|
this.options.grantRunner?.unregister(cornerId);
|
|
21494
21487
|
await this.options.scheduler.suspend(cornerId);
|
|
21495
21488
|
}
|
|
21496
21489
|
}
|
|
21497
21490
|
};
|
|
21498
|
-
async function waitForWake(api, cornerId, signal) {
|
|
21499
|
-
if (signal?.aborted)
|
|
21500
|
-
return;
|
|
21501
|
-
try {
|
|
21502
|
-
await api.execute("waitForCornerWake", { cornerId });
|
|
21503
|
-
} catch {
|
|
21504
|
-
await never(signal);
|
|
21505
|
-
}
|
|
21506
|
-
}
|
|
21507
|
-
async function never(signal) {
|
|
21508
|
-
if (signal?.aborted)
|
|
21509
|
-
return;
|
|
21510
|
-
await new Promise((resolveNever) => {
|
|
21511
|
-
signal?.addEventListener("abort", () => resolveNever(), { once: true });
|
|
21512
|
-
});
|
|
21513
|
-
}
|
|
21514
21491
|
async function wait(ms, signal) {
|
|
21515
21492
|
if (signal?.aborted)
|
|
21516
21493
|
return;
|
|
@@ -21658,6 +21635,14 @@ function agentReplyMentionIds(text2, roster, authorId) {
|
|
|
21658
21635
|
var MonolithRoomTurnLoop = class {
|
|
21659
21636
|
options;
|
|
21660
21637
|
agent;
|
|
21638
|
+
reconciliationRequested = true;
|
|
21639
|
+
wakeIntake;
|
|
21640
|
+
/** Called by the daemon's one slow workspace reconciliation sweep. */
|
|
21641
|
+
requestReconciliation() {
|
|
21642
|
+
this.reconciliationRequested = true;
|
|
21643
|
+
this.wakeIntake?.();
|
|
21644
|
+
this.wakeIntake = void 0;
|
|
21645
|
+
}
|
|
21661
21646
|
client;
|
|
21662
21647
|
sessionId;
|
|
21663
21648
|
/** The configuration the live session baked in; a change invalidates it. */
|
|
@@ -22251,25 +22236,11 @@ var MonolithRoomTurnLoop = class {
|
|
|
22251
22236
|
}
|
|
22252
22237
|
async run() {
|
|
22253
22238
|
const { api, roomId, signal } = this.options;
|
|
22254
|
-
const status = this.options.config.modelUnavailable ? "offline" : "online";
|
|
22255
|
-
const postPresence = async (presence) => {
|
|
22256
|
-
await api.execute("postAgentPresence", {
|
|
22257
|
-
agentId: this.agent.publicKey,
|
|
22258
|
-
roomId,
|
|
22259
|
-
status: presence,
|
|
22260
|
-
...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
|
|
22261
|
-
...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {}
|
|
22262
|
-
});
|
|
22263
|
-
this.options.health.presence(presence);
|
|
22264
|
-
};
|
|
22265
|
-
await postPresence(status);
|
|
22266
|
-
const heartbeat = setInterval(() => void postPresence(status).catch((error) => console.error(`[thin-core] monolith Room ${roomId} presence heartbeat failed:`, error)), 3e4);
|
|
22267
|
-
heartbeat.unref?.();
|
|
22268
22239
|
let cursor3;
|
|
22269
22240
|
const processedInboxIds = /* @__PURE__ */ new Set();
|
|
22270
22241
|
const pushedInbox = [];
|
|
22271
22242
|
let pendingPushedCursor;
|
|
22272
|
-
let
|
|
22243
|
+
let liveConnected = false;
|
|
22273
22244
|
let stopLive;
|
|
22274
22245
|
try {
|
|
22275
22246
|
const activation = await api.execute("getRoomInbox", { roomId, startAtLatest: true });
|
|
@@ -22280,15 +22251,24 @@ var MonolithRoomTurnLoop = class {
|
|
|
22280
22251
|
stopLive = api.liveSubscribe?.(roomId, cursor3, (items, pushedCursor) => {
|
|
22281
22252
|
pushedInbox.push(...items);
|
|
22282
22253
|
pendingPushedCursor = laterInboxCursor(pendingPushedCursor, pushedCursor);
|
|
22283
|
-
|
|
22284
|
-
|
|
22254
|
+
this.wakeIntake?.();
|
|
22255
|
+
this.wakeIntake = void 0;
|
|
22256
|
+
}, (connected, capabilities) => {
|
|
22257
|
+
liveConnected = connected && capabilities?.pushIntake === true;
|
|
22258
|
+
this.options.health.presence(connected && !this.options.config.modelUnavailable ? "online" : "offline");
|
|
22259
|
+
this.wakeIntake?.();
|
|
22260
|
+
this.wakeIntake = void 0;
|
|
22261
|
+
}, {
|
|
22262
|
+
...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
|
|
22263
|
+
...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
|
|
22264
|
+
available: !this.options.config.modelUnavailable
|
|
22285
22265
|
});
|
|
22286
22266
|
while (!signal?.aborted) {
|
|
22287
22267
|
try {
|
|
22288
22268
|
if (!this.activeTurn && this.queuedTurns.length) {
|
|
22289
22269
|
this.startPrompt(this.queuedTurns.shift());
|
|
22290
22270
|
}
|
|
22291
|
-
const pollNow = pushedInbox.length === 0;
|
|
22271
|
+
const pollNow = pushedInbox.length === 0 && (!liveConnected || this.reconciliationRequested);
|
|
22292
22272
|
const inbox = !pollNow ? { items: [], cursor: void 0 } : await api.execute("getRoomInbox", {
|
|
22293
22273
|
roomId,
|
|
22294
22274
|
...cursor3 ? { after: cursor3 } : {},
|
|
@@ -22296,7 +22276,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
22296
22276
|
limit: 200
|
|
22297
22277
|
});
|
|
22298
22278
|
if (pollNow) {
|
|
22299
|
-
|
|
22279
|
+
this.reconciliationRequested = false;
|
|
22300
22280
|
}
|
|
22301
22281
|
const delivered = [...pushedInbox.splice(0), ...inbox.items];
|
|
22302
22282
|
for (const item of delivered) {
|
|
@@ -22331,9 +22311,9 @@ var MonolithRoomTurnLoop = class {
|
|
|
22331
22311
|
this.options.health.poll();
|
|
22332
22312
|
if (!pushedInbox.length) {
|
|
22333
22313
|
await Promise.race([
|
|
22334
|
-
wait2(this.options.pollMs ?? 1e3, signal),
|
|
22314
|
+
wait2(liveConnected ? 2147483647 : this.options.pollMs ?? 1e3, signal),
|
|
22335
22315
|
new Promise((resolve30) => {
|
|
22336
|
-
|
|
22316
|
+
this.wakeIntake = resolve30;
|
|
22337
22317
|
})
|
|
22338
22318
|
]);
|
|
22339
22319
|
}
|
|
@@ -22347,13 +22327,12 @@ var MonolithRoomTurnLoop = class {
|
|
|
22347
22327
|
}
|
|
22348
22328
|
} finally {
|
|
22349
22329
|
stopLive?.();
|
|
22350
|
-
|
|
22330
|
+
this.wakeIntake = void 0;
|
|
22351
22331
|
this.options.grantRunner?.unregister(roomId);
|
|
22352
22332
|
if (this.activeTurn?.phase === "prompting" && this.client && this.sessionId) {
|
|
22353
22333
|
this.client.sessionCancel(this.sessionId);
|
|
22354
22334
|
}
|
|
22355
22335
|
await this.activeTurn?.promise;
|
|
22356
|
-
await postPresence("offline").catch((error) => console.error(`[thin-core] monolith Room ${roomId} offline presence failed:`, error));
|
|
22357
22336
|
await this.options.scheduler.suspend(roomId);
|
|
22358
22337
|
}
|
|
22359
22338
|
}
|
|
@@ -22844,7 +22823,14 @@ async function materializeCornerWorktree(input) {
|
|
|
22844
22823
|
"credential.https://github.com.helper",
|
|
22845
22824
|
"!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
|
|
22846
22825
|
]);
|
|
22847
|
-
await execFileAsync4("git", [
|
|
22826
|
+
await execFileAsync4("git", [
|
|
22827
|
+
"-C",
|
|
22828
|
+
path,
|
|
22829
|
+
"config",
|
|
22830
|
+
"--worktree",
|
|
22831
|
+
"user.name",
|
|
22832
|
+
input.committer.name
|
|
22833
|
+
]);
|
|
22848
22834
|
await execFileAsync4("git", [
|
|
22849
22835
|
"-C",
|
|
22850
22836
|
path,
|
|
@@ -22873,6 +22859,13 @@ async function mapWithConcurrency(values, limit, visit) {
|
|
|
22873
22859
|
}));
|
|
22874
22860
|
}
|
|
22875
22861
|
var execFileAsync4 = promisify4(execFile5);
|
|
22862
|
+
async function removeCornerScratchWorkspace(input) {
|
|
22863
|
+
const expected = resolve19(input.roomRoot, "scratch");
|
|
22864
|
+
if (resolve19(input.scratchPath) !== expected) {
|
|
22865
|
+
throw new Error(`refusing to remove scratch outside corner ${input.cornerId}`);
|
|
22866
|
+
}
|
|
22867
|
+
await rm4(expected, { recursive: true, force: true });
|
|
22868
|
+
}
|
|
22876
22869
|
var RoomRuntimeCoordinator = class {
|
|
22877
22870
|
configPath;
|
|
22878
22871
|
baseConfig;
|
|
@@ -23037,6 +23030,8 @@ var RoomRuntimeCoordinator = class {
|
|
|
23037
23030
|
await running.promise.catch(() => void 0);
|
|
23038
23031
|
if (running.worktree)
|
|
23039
23032
|
await this.reapCornerWorktree(running.worktree);
|
|
23033
|
+
else if (running.scratch)
|
|
23034
|
+
await this.reapCornerScratch(running.scratch);
|
|
23040
23035
|
}
|
|
23041
23036
|
await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
|
|
23042
23037
|
if (!this.running.has(roomId))
|
|
@@ -23046,6 +23041,8 @@ var RoomRuntimeCoordinator = class {
|
|
|
23046
23041
|
if (!this.running.has(corner.cornerId))
|
|
23047
23042
|
await this.startCorner(corner);
|
|
23048
23043
|
});
|
|
23044
|
+
for (const running of this.running.values())
|
|
23045
|
+
running.body.requestReconciliation();
|
|
23049
23046
|
return "member";
|
|
23050
23047
|
}
|
|
23051
23048
|
roomRecord(roomId) {
|
|
@@ -23069,8 +23066,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
23069
23066
|
return void 0;
|
|
23070
23067
|
}
|
|
23071
23068
|
}
|
|
23072
|
-
roomConfig(roomId) {
|
|
23073
|
-
const workspaceRoot = this.roomRoot(roomId);
|
|
23069
|
+
roomConfig(roomId, workspaceRoot = this.roomRoot(roomId)) {
|
|
23074
23070
|
const agentHomeRoot = this.roomAgentHomeRoot(workspaceRoot, true);
|
|
23075
23071
|
return {
|
|
23076
23072
|
...this.baseConfig,
|
|
@@ -23174,7 +23170,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
23174
23170
|
return;
|
|
23175
23171
|
this.startingCorners.add(corner.cornerId);
|
|
23176
23172
|
try {
|
|
23177
|
-
const [restore, repository, conversation
|
|
23173
|
+
const [restore, repository, conversation] = await Promise.all([
|
|
23178
23174
|
this.options.daemonApi.execute("getCornerRestoreState", { cornerId: corner.cornerId }),
|
|
23179
23175
|
this.options.daemonApi.execute("getRoomRepositoryState", {
|
|
23180
23176
|
roomId: corner.parentRoomId
|
|
@@ -23186,28 +23182,35 @@ var RoomRuntimeCoordinator = class {
|
|
|
23186
23182
|
roomId: corner.cornerId,
|
|
23187
23183
|
limit: 200,
|
|
23188
23184
|
window: "earliest"
|
|
23189
|
-
}),
|
|
23190
|
-
this.options.daemonApi.execute("getRoomGitHubToken", {
|
|
23191
|
-
roomId: corner.parentRoomId
|
|
23192
23185
|
})
|
|
23193
23186
|
]);
|
|
23194
|
-
if (repository.resolution
|
|
23195
|
-
throw new Error("corner parent Room
|
|
23187
|
+
if (repository.resolution === "unverified") {
|
|
23188
|
+
throw new Error("corner parent Room repository state is not verified yet");
|
|
23189
|
+
}
|
|
23190
|
+
if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
|
|
23191
|
+
throw new Error("corner parent Room has an incomplete repository binding");
|
|
23196
23192
|
}
|
|
23197
23193
|
const objective = conversation.items.find((item) => item.type === "message")?.body.trim();
|
|
23198
23194
|
if (!objective)
|
|
23199
23195
|
throw new Error("corner has no durable objective post");
|
|
23200
|
-
const
|
|
23201
|
-
const
|
|
23202
|
-
const
|
|
23196
|
+
const repositoryBacked = repository.resolution === "repository";
|
|
23197
|
+
const targetBranch = repositoryBacked ? repository.targetBranch || "main" : void 0;
|
|
23198
|
+
const featureBranch = repositoryBacked ? restore.featureBranch ?? `feature/corner-${corner.cornerId.replaceAll("-", "").slice(0, 12)}` : void 0;
|
|
23199
|
+
const granted = repositoryBacked ? await this.options.daemonApi.execute("getRoomGitHubToken", {
|
|
23200
|
+
roomId: corner.parentRoomId
|
|
23201
|
+
}) : void 0;
|
|
23202
|
+
const worktree = repositoryBacked ? await this.materializeCornerWorktree({
|
|
23203
23203
|
cornerId: corner.cornerId,
|
|
23204
23204
|
remote: repository.remote,
|
|
23205
23205
|
targetBranch,
|
|
23206
23206
|
featureBranch,
|
|
23207
23207
|
token: granted.token
|
|
23208
|
-
});
|
|
23208
|
+
}) : void 0;
|
|
23209
|
+
const workspacePath = worktree?.path ?? resolve19(this.roomRoot(corner.cornerId), "scratch");
|
|
23210
|
+
if (!worktree)
|
|
23211
|
+
await mkdir11(workspacePath, { recursive: true, mode: 448 });
|
|
23209
23212
|
const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
|
|
23210
|
-
if (shouldPostInitialCornerWorkingState(restore, isOpener)) {
|
|
23213
|
+
if (worktree && shouldPostInitialCornerWorkingState(restore, isOpener)) {
|
|
23211
23214
|
await this.options.daemonApi.execute("postCornerRemoteState", {
|
|
23212
23215
|
cornerId: corner.cornerId,
|
|
23213
23216
|
branch: featureBranch,
|
|
@@ -23226,23 +23229,27 @@ var RoomRuntimeCoordinator = class {
|
|
|
23226
23229
|
workspaceId: this.runtime.communityId,
|
|
23227
23230
|
...corner.openedBy ? { openedBy: corner.openedBy } : {},
|
|
23228
23231
|
objective,
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23232
|
+
worktreePath: workspacePath,
|
|
23233
|
+
...worktree ? {
|
|
23234
|
+
repository: {
|
|
23235
|
+
featureBranch,
|
|
23236
|
+
targetBranch,
|
|
23237
|
+
gitCommonDir: worktree.gitCommonDir,
|
|
23238
|
+
githubToken: granted.token
|
|
23239
|
+
}
|
|
23240
|
+
} : {},
|
|
23234
23241
|
runtime: this.runtime,
|
|
23235
|
-
config: this.roomConfig(corner.cornerId),
|
|
23242
|
+
config: this.roomConfig(corner.cornerId, worktree ? void 0 : workspacePath),
|
|
23236
23243
|
api: this.options.daemonApi,
|
|
23237
23244
|
scheduler: this.scheduler,
|
|
23238
23245
|
signal: controller.signal,
|
|
23239
23246
|
onPoll: () => this.notePoll(corner.cornerId),
|
|
23240
23247
|
onFailure: (retryInMs) => this.noteFailure(corner.cornerId, retryInMs),
|
|
23241
|
-
onCloseRequested: () => this.reapCornerWorktree({
|
|
23248
|
+
onCloseRequested: () => worktree ? this.reapCornerWorktree({
|
|
23242
23249
|
...worktree,
|
|
23243
23250
|
cornerId: corner.cornerId,
|
|
23244
23251
|
branch: featureBranch
|
|
23245
|
-
})
|
|
23252
|
+
}) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId })
|
|
23246
23253
|
});
|
|
23247
23254
|
const promise = loop.run().catch((error) => {
|
|
23248
23255
|
if (!controller.signal.aborted) {
|
|
@@ -23260,14 +23267,17 @@ var RoomRuntimeCoordinator = class {
|
|
|
23260
23267
|
lastPollAt: startedAt,
|
|
23261
23268
|
backoffUntil: 0,
|
|
23262
23269
|
recovering: false,
|
|
23263
|
-
worktree
|
|
23264
|
-
|
|
23265
|
-
|
|
23266
|
-
|
|
23267
|
-
|
|
23270
|
+
...worktree ? {
|
|
23271
|
+
worktree: {
|
|
23272
|
+
...worktree,
|
|
23273
|
+
cornerId: corner.cornerId,
|
|
23274
|
+
branch: featureBranch
|
|
23275
|
+
}
|
|
23276
|
+
} : {},
|
|
23277
|
+
...!worktree ? { scratch: { path: workspacePath, cornerId: corner.cornerId } } : {}
|
|
23268
23278
|
});
|
|
23269
23279
|
this.reportedCornerStartFailures.delete(corner.cornerId);
|
|
23270
|
-
console.log(`[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${
|
|
23280
|
+
console.log(worktree ? `[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${workspacePath}` : `[thin-core] serving chat-only corner ${corner.cornerId} at ${workspacePath}`);
|
|
23271
23281
|
} catch (error) {
|
|
23272
23282
|
console.error(`[thin-core] failed to start corner ${corner.cornerId}:`, error);
|
|
23273
23283
|
await this.reportCornerStartFailure(corner.cornerId, error);
|
|
@@ -23334,6 +23344,13 @@ var RoomRuntimeCoordinator = class {
|
|
|
23334
23344
|
checks: "unknown"
|
|
23335
23345
|
});
|
|
23336
23346
|
}
|
|
23347
|
+
async reapCornerScratch(scratch) {
|
|
23348
|
+
await removeCornerScratchWorkspace({
|
|
23349
|
+
cornerId: scratch.cornerId,
|
|
23350
|
+
roomRoot: this.roomRoot(scratch.cornerId),
|
|
23351
|
+
scratchPath: scratch.path
|
|
23352
|
+
});
|
|
23353
|
+
}
|
|
23337
23354
|
notePoll(roomId) {
|
|
23338
23355
|
const room = this.running.get(roomId);
|
|
23339
23356
|
if (!room)
|