usebeeline 0.0.109 → 0.0.111
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 +1 -1
- package/dist/usebeeline.mjs +169 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<p align="center"><strong>Team messaging for agents and humans.</strong></p>
|
|
10
10
|
<p align="center">One Room for your people and your coding agents. Talk it through, hand off the work, watch it merge.</p>
|
|
11
11
|
|
|
12
|
-
`usebeeline` connects **a coding agent you already run — Claude Code, Codex, Goose, Pi, or
|
|
12
|
+
`usebeeline` connects **a coding agent you already run — Claude Code, Codex, Goose, Pi, Grok, or Cursor — to a Room in the Beeline app on your phone**. One command on the machine where the agent lives, and it walks into the conversation as a member: it reads what your teammates actually said, answers when it is tagged, and takes work away when someone asks it to. In top-level Rooms and corners, it can also continue the conversation with the person it last addressed, without piling on from another agent. Nothing is retyped into a prompt box.
|
|
13
13
|
|
|
14
14
|
The agent stays on your machine. Your provider key stays on your machine. What crosses the wire is the conversation, and — when repository work starts — a pull request.
|
|
15
15
|
|
package/dist/usebeeline.mjs
CHANGED
|
@@ -6092,16 +6092,18 @@ var AGENT_KINDS = [
|
|
|
6092
6092
|
"goose",
|
|
6093
6093
|
"pi",
|
|
6094
6094
|
"grok",
|
|
6095
|
+
"cursor",
|
|
6095
6096
|
"reference",
|
|
6096
6097
|
"custom"
|
|
6097
6098
|
];
|
|
6098
|
-
var AUTO_DETECT_AGENT_KINDS = ["codex", "claude", "goose", "pi", "grok"];
|
|
6099
|
+
var AUTO_DETECT_AGENT_KINDS = ["codex", "claude", "goose", "pi", "grok", "cursor"];
|
|
6099
6100
|
var AGENT_EXECUTABLES = {
|
|
6100
6101
|
codex: "codex",
|
|
6101
6102
|
claude: "claude",
|
|
6102
6103
|
goose: "goose",
|
|
6103
6104
|
pi: "pi",
|
|
6104
|
-
grok: "grok"
|
|
6105
|
+
grok: "grok",
|
|
6106
|
+
cursor: "cursor-agent"
|
|
6105
6107
|
};
|
|
6106
6108
|
var ADAPTER_INSTALL_COMMANDS = {
|
|
6107
6109
|
codex: {
|
|
@@ -6115,6 +6117,10 @@ var ADAPTER_INSTALL_COMMANDS = {
|
|
|
6115
6117
|
pi: {
|
|
6116
6118
|
command: "npm",
|
|
6117
6119
|
args: ["install", "-g", "pi-acp"]
|
|
6120
|
+
},
|
|
6121
|
+
cursor: {
|
|
6122
|
+
command: "npm",
|
|
6123
|
+
args: ["install", "-g", "cursor-agent-acp"]
|
|
6118
6124
|
}
|
|
6119
6125
|
};
|
|
6120
6126
|
function adapterInstallHint(kind) {
|
|
@@ -6295,6 +6301,11 @@ function resolveAgentCommand(opts) {
|
|
|
6295
6301
|
const command2 = requireExecutable("pi-acp", env, cwd, `Pi needs an ACP adapter. Install it with \`${adapterInstallHint("pi")}\`, then retry with \`--agent pi\`.`);
|
|
6296
6302
|
return { kind: typedKind, command: command2, args: [] };
|
|
6297
6303
|
}
|
|
6304
|
+
if (typedKind === "cursor") {
|
|
6305
|
+
requireExecutable("cursor-agent", env, cwd, "Cursor Agent CLI not found. Install it from https://cursor.com/docs/cli, then retry with `--agent cursor`.");
|
|
6306
|
+
const command2 = requireExecutable("cursor-agent-acp", env, cwd, `Cursor ACP adapter not found. Install it with \`${adapterInstallHint("cursor")}\`, then retry with \`--agent cursor\`.`);
|
|
6307
|
+
return { kind: typedKind, command: command2, args: [] };
|
|
6308
|
+
}
|
|
6298
6309
|
if (typedKind === "grok") {
|
|
6299
6310
|
const command2 = requireExecutable("grok", env, cwd, "Grok CLI not found. Install it with `curl -fsSL https://x.ai/cli/install.sh | bash`, then retry with `--agent grok`.");
|
|
6300
6311
|
return { kind: typedKind, command: command2, args: ["agent", "stdio"] };
|
|
@@ -6762,6 +6773,13 @@ var PROFILES = [
|
|
|
6762
6773
|
enforcement: "allowlisted",
|
|
6763
6774
|
note: "buzz-agent mounts only the MCP servers the daemon passes; it has no operator-global tool config"
|
|
6764
6775
|
}
|
|
6776
|
+
},
|
|
6777
|
+
{
|
|
6778
|
+
match: /(^|[/\\])cursor-agent-acp(\.[a-z]+)?$/i,
|
|
6779
|
+
profile: {
|
|
6780
|
+
enforcement: "config-isolated",
|
|
6781
|
+
note: "cursor-agent-acp bridges cursor-agent to ACP; cursor-agent loads MCP servers from ~/.cursor/mcp.json, so an isolated CURSOR_HOME scopes the session"
|
|
6782
|
+
}
|
|
6765
6783
|
}
|
|
6766
6784
|
];
|
|
6767
6785
|
function sessionToolScopeMeta(agentCommand) {
|
|
@@ -18025,6 +18043,8 @@ var DaemonApiClient = class {
|
|
|
18025
18043
|
liveReconnect;
|
|
18026
18044
|
liveReconnectDelayMs = 1e3;
|
|
18027
18045
|
liveRooms = /* @__PURE__ */ new Map();
|
|
18046
|
+
roomsChangedListener;
|
|
18047
|
+
configChangedListener;
|
|
18028
18048
|
constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
|
|
18029
18049
|
this.baseUrl = baseUrl;
|
|
18030
18050
|
this.daemonToken = daemonToken;
|
|
@@ -18071,6 +18091,18 @@ var DaemonApiClient = class {
|
|
|
18071
18091
|
}
|
|
18072
18092
|
};
|
|
18073
18093
|
}
|
|
18094
|
+
/** Register the one listener invoked when the server reports this agent's
|
|
18095
|
+
* Room/corner memberships changed — the wake that discovers a freshly
|
|
18096
|
+
* created Room without waiting for the reconciliation heartbeat. */
|
|
18097
|
+
setRoomsChangedListener(listener) {
|
|
18098
|
+
this.roomsChangedListener = listener;
|
|
18099
|
+
}
|
|
18100
|
+
/** Register the one listener invoked when the server reports the agent's
|
|
18101
|
+
* model/effort selection changed — the wake that hot-restarts every
|
|
18102
|
+
* retained session (agent-wide; the event names no single Room). */
|
|
18103
|
+
setConfigChangedListener(listener) {
|
|
18104
|
+
this.configChangedListener = listener;
|
|
18105
|
+
}
|
|
18074
18106
|
updateLiveCursor(roomId, cursor3) {
|
|
18075
18107
|
const room = this.liveRooms.get(roomId);
|
|
18076
18108
|
if (room && cursor3)
|
|
@@ -18104,6 +18136,7 @@ var DaemonApiClient = class {
|
|
|
18104
18136
|
this.liveReconnectDelayMs = 1e3;
|
|
18105
18137
|
for (const roomId of this.liveRooms.keys())
|
|
18106
18138
|
this.sendLiveSubscription(roomId);
|
|
18139
|
+
this.roomsChangedListener?.();
|
|
18107
18140
|
};
|
|
18108
18141
|
socket.onmessage = (message) => {
|
|
18109
18142
|
let value;
|
|
@@ -18115,6 +18148,14 @@ var DaemonApiClient = class {
|
|
|
18115
18148
|
if (!value || typeof value !== "object")
|
|
18116
18149
|
return;
|
|
18117
18150
|
const event = value;
|
|
18151
|
+
if (event.type === "rooms-changed") {
|
|
18152
|
+
this.roomsChangedListener?.();
|
|
18153
|
+
return;
|
|
18154
|
+
}
|
|
18155
|
+
if (event.type === "config-changed") {
|
|
18156
|
+
this.configChangedListener?.();
|
|
18157
|
+
return;
|
|
18158
|
+
}
|
|
18118
18159
|
if (event.type === "subscribed" && typeof event.roomId === "string") {
|
|
18119
18160
|
const capabilities = event.capabilities;
|
|
18120
18161
|
this.liveRooms.get(event.roomId)?.onState?.(true, {
|
|
@@ -18498,6 +18539,10 @@ var HARNESS_HOME_STATE_DIRS = [
|
|
|
18498
18539
|
{
|
|
18499
18540
|
match: /(^|[/\\])grok(\.[a-z]+)?$/i,
|
|
18500
18541
|
dirs: [".grok"]
|
|
18542
|
+
},
|
|
18543
|
+
{
|
|
18544
|
+
match: /(^|[/\\])cursor-agent-acp(\.[a-z]+)?$/i,
|
|
18545
|
+
dirs: [".cursor"]
|
|
18501
18546
|
}
|
|
18502
18547
|
];
|
|
18503
18548
|
function harnessHomeStateDirs(agentCommand, home = homedir3()) {
|
|
@@ -19369,6 +19414,7 @@ function isAgentPairingCode(value) {
|
|
|
19369
19414
|
|
|
19370
19415
|
// apps/body/dist/beeline-skill.js
|
|
19371
19416
|
var USING_BEELINE_SKILL_NAME = "using-beeline";
|
|
19417
|
+
var BEELINE_TRIAGE_SKILL_NAME = "beeline-triage";
|
|
19372
19418
|
var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
|
|
19373
19419
|
function isConfiguredReviewer(agentHandle, reviewerHandle) {
|
|
19374
19420
|
return Boolean(agentHandle && reviewerHandle && agentHandle.replace(/^@/, "") === reviewerHandle.replace(/^@/, ""));
|
|
@@ -19382,9 +19428,10 @@ var BEELINE_ROOM_CAPABILITIES = [
|
|
|
19382
19428
|
"Files and photos people share are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline); never fetch the reference URL.",
|
|
19383
19429
|
"To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
|
|
19384
19430
|
"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.",
|
|
19385
|
-
`To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want (${SERVER_EVENT_KINDS.join(", ")}); each one then wakes you for a turn. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. grant-decided carries the grant id and status and resumes the turn that asked for the grant.`,
|
|
19431
|
+
`To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want (${SERVER_EVENT_KINDS.join(", ")}); each one then wakes you for a turn. Subscriptions are per Room and cover every way the event lands: joining a Room you subscribed to wakes you, and so does a person arriving in the Workspace when that arrival projects into this Room - subscribe to joined in an onboarding Room and every newcomer wakes you, exactly like a greeter. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. grant-decided carries the grant id and status and resumes the turn that asked for the grant.`,
|
|
19386
19432
|
"To state something that happened so the Room and other agents can act on it, call beeline-agent emit_event with your own agent:<slug> kind, one sentence, and optionally the agent members to wake. Chains of events are bounded and a refused emit posts nothing.",
|
|
19387
19433
|
"When repository work is needed, you MUST call beeline-agent open_corner with a name of at most three words - it titles the corner everywhere - and a complete objective of no more than 24 words. The host-governed call is the only way to start write work.",
|
|
19434
|
+
"Before emitting `Proposed corner:` or calling open_corner, consult the release-versioned beeline-triage skill and follow it. An unclear request requires a question; a warranted-work or desirability warning does not block the proposal or corner.",
|
|
19388
19435
|
"Never open a corner from your own reading of a person's ask. For every ask, first reply on one line `Proposed corner: <name> \u2014 <objective>`, using the exact title and objective you would pass to open_corner, then stop and wait. If one message contains several asks, list one numbered `Proposed corner:` line per ask; `go on 1 and 3` opens exactly those objectives and leaves the others proposed. A person's `go`, `yes`, or equivalent opens exactly the proposed corner; if they edit it, their edited text is the objective. Skip this ceremony only when the message itself explicitly commands a corner and states its scope, such as `open a corner and do X` or `go build X in a corner`; open that scope immediately.",
|
|
19389
19436
|
"Agreement is not action: never merely acknowledge an ask. Reply with a proposed corner, a question, or a line beginning `parked:` with the reason.",
|
|
19390
19437
|
"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.",
|
|
@@ -19476,6 +19523,11 @@ Follow these steps in order. Do not skip or reorder them.
|
|
|
19476
19523
|
|
|
19477
19524
|
## 2. P0 - OBJECTIVE FULFILLED, DEMONSTRATED
|
|
19478
19525
|
|
|
19526
|
+
Before judging the implementation, independently repeat the two judgment legs from request triage:
|
|
19527
|
+
|
|
19528
|
+
- **Work warranted:** For a bug, reproduce the reported behavior on the target branch. For another request, establish the unmet user need from the request and current product. Search current code, history, issues, and open or recently merged pull requests for work that already resolves or supersedes it. Treat title similarity only as a candidate, not proof of duplication. FAIL confirmed duplicate or obsolete work.
|
|
19529
|
+
- **Desirable:** Check repository-owned goals, invariants, architecture, and established product behavior. Require a concrete user benefit, the smallest coherent solution, and no unapproved scope. FAIL a confirmed conflict or an unsupported product judgment; put merely plausible concerns below as non-blocking findings.
|
|
19530
|
+
|
|
19479
19531
|
- Quote the corner objective verbatim.
|
|
19480
19532
|
- Derive its end-user story in one sentence: \`a user who does X sees Y\`.
|
|
19481
19533
|
- Make Y happen against the built PR head: run the app or affected service and perform X.
|
|
@@ -19519,6 +19571,8 @@ Follow these steps in order. Do not skip or reorder them.
|
|
|
19519
19571
|
|
|
19520
19572
|
\`objective quoted:\`
|
|
19521
19573
|
\`user story:\`
|
|
19574
|
+
\`work warranted evidence:\`
|
|
19575
|
+
\`desirability evidence:\`
|
|
19522
19576
|
\`how Y was demonstrated (or FAIL):\`
|
|
19523
19577
|
\`commands run + results:\`
|
|
19524
19578
|
\`critical findings (block):\`
|
|
@@ -19533,6 +19587,49 @@ Then take exactly one action:
|
|
|
19533
19587
|
- Approving is your last step as reviewer. The author merges it; you never do, and nothing merges it automatically.
|
|
19534
19588
|
`;
|
|
19535
19589
|
}
|
|
19590
|
+
function beelineTriageSkillMarkdown(releaseId) {
|
|
19591
|
+
return `---
|
|
19592
|
+
name: beeline-triage
|
|
19593
|
+
description: Clarify and assess a user request before proposing or opening a Beeline corner. Use immediately before emitting Proposed corner or calling open_corner.
|
|
19594
|
+
---
|
|
19595
|
+
|
|
19596
|
+
<!-- beeline-release: ${releaseId} -->
|
|
19597
|
+
|
|
19598
|
+
# Beeline request triage
|
|
19599
|
+
|
|
19600
|
+
Run these checks before proposing or opening a corner.
|
|
19601
|
+
|
|
19602
|
+
## 1. Is it clear?
|
|
19603
|
+
|
|
19604
|
+
- Rewrite the request as a concrete outcome and acceptance criteria.
|
|
19605
|
+
- State material exclusions needed to prevent unrequested work.
|
|
19606
|
+
- Keep the corner objective complete and within 24 words.
|
|
19607
|
+
- If an ambiguity could materially change the outcome, ask one focused question instead of proposing or opening the corner.
|
|
19608
|
+
|
|
19609
|
+
## 2. Is work warranted?
|
|
19610
|
+
|
|
19611
|
+
- For a bug, try to reproduce the exact user-visible behavior and record the evidence.
|
|
19612
|
+
- For another request, identify the unmet user need and the current behavior that does not meet it.
|
|
19613
|
+
- Search current code, history, issues, and open or recently merged pull requests for released or unreleased work that resolves or supersedes the request.
|
|
19614
|
+
- Treat similar titles as candidates. Confirm behavior and scope before calling work duplicate.
|
|
19615
|
+
- If the need cannot be established, reproduction fails, or other work may obviate it, warn; do not block.
|
|
19616
|
+
|
|
19617
|
+
## 3. Is it desirable?
|
|
19618
|
+
|
|
19619
|
+
- Compare the request with repository-owned goals, invariants, architecture, and established product behavior.
|
|
19620
|
+
- Look for concrete user benefit, the smallest coherent solution, and ongoing maintenance cost.
|
|
19621
|
+
- Do not invent product strategy. If the evidence is absent or conflicting, warn; do not block.
|
|
19622
|
+
|
|
19623
|
+
## Output
|
|
19624
|
+
|
|
19625
|
+
When proposing work, emit the ordinary \`Proposed corner: <name> \u2014 <objective>\` line. Add only applicable warnings on following lines:
|
|
19626
|
+
|
|
19627
|
+
\`Triage warning \u2014 warranted: <evidence-backed reason>\`
|
|
19628
|
+
\`Triage warning \u2014 desirable: <evidence-backed reason>\`
|
|
19629
|
+
|
|
19630
|
+
Do not emit a warning merely because evidence is incomplete when the repository offers no practical way to obtain it. Never describe a warning as approval or rejection. Warnings inform the user and implementer; they do not block work.
|
|
19631
|
+
`;
|
|
19632
|
+
}
|
|
19536
19633
|
|
|
19537
19634
|
// apps/body/dist/external-mcp-capabilities.js
|
|
19538
19635
|
var SQUIRE_MCP_VERSION = "latest";
|
|
@@ -20127,11 +20224,15 @@ var SHARED_CREDENTIALS = [
|
|
|
20127
20224
|
// Grok relocates ~/.grok via GROK_HOME; auth.json holds its login (same
|
|
20128
20225
|
// shape as codex).
|
|
20129
20226
|
{ dir: "grok", source: ".grok/auth.json", target: "auth.json" },
|
|
20130
|
-
{ dir: "pi", source: ".pi/agent/auth.json", target: "auth.json" }
|
|
20227
|
+
{ dir: "pi", source: ".pi/agent/auth.json", target: "auth.json" },
|
|
20228
|
+
// Cursor CLI stores auth state under ~/.cursor/; cursor-agent-acp
|
|
20229
|
+
// reads CURSOR_HOME to relocate the data directory.
|
|
20230
|
+
{ dir: "cursor", source: ".cursor/agent-cli-state.json", target: "agent-cli-state.json" }
|
|
20131
20231
|
];
|
|
20132
20232
|
var GOOSE_SHARED_CONFIG_FILES = ["config.yaml", "secrets.yaml"];
|
|
20133
20233
|
var BEELINE_DEFAULT_SKILL_NAMES = [
|
|
20134
20234
|
BEELINE_REVIEW_SKILL_NAME,
|
|
20235
|
+
BEELINE_TRIAGE_SKILL_NAME,
|
|
20135
20236
|
USING_BEELINE_SKILL_NAME
|
|
20136
20237
|
];
|
|
20137
20238
|
var OPERATOR_SKILL_SOURCE_DIRS = [
|
|
@@ -20140,7 +20241,7 @@ var OPERATOR_SKILL_SOURCE_DIRS = [
|
|
|
20140
20241
|
".codex/skills",
|
|
20141
20242
|
".pi/agent/skills"
|
|
20142
20243
|
];
|
|
20143
|
-
var AGENT_SKILL_DIRS = ["claude", "codex", "grok", "pi"];
|
|
20244
|
+
var AGENT_SKILL_DIRS = ["claude", "codex", "grok", "pi", "cursor"];
|
|
20144
20245
|
function agentSkillDir(kind) {
|
|
20145
20246
|
return AGENT_SKILL_DIRS.includes(kind ?? "") ? kind : "codex";
|
|
20146
20247
|
}
|
|
@@ -20168,6 +20269,7 @@ var HOME_SUBDIRS = [
|
|
|
20168
20269
|
"goose",
|
|
20169
20270
|
"grok",
|
|
20170
20271
|
"pi",
|
|
20272
|
+
"cursor",
|
|
20171
20273
|
"state",
|
|
20172
20274
|
"cache",
|
|
20173
20275
|
"tmp"
|
|
@@ -20213,6 +20315,7 @@ async function prepareRoomAgentHome(input) {
|
|
|
20213
20315
|
async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting, isReviewer) {
|
|
20214
20316
|
const managedSkills = [
|
|
20215
20317
|
{ name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
|
|
20318
|
+
{ name: BEELINE_TRIAGE_SKILL_NAME, content: beelineTriageSkillMarkdown(skillReleaseId) },
|
|
20216
20319
|
...isReviewer ? [{ name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }] : []
|
|
20217
20320
|
];
|
|
20218
20321
|
const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
|
|
@@ -20622,6 +20725,7 @@ function roomAgentHomeEnv(root) {
|
|
|
20622
20725
|
CODEX_HOME: resolve13(resolved, "codex"),
|
|
20623
20726
|
GOOSE_PATH_ROOT: resolve13(resolved, "goose"),
|
|
20624
20727
|
GROK_HOME: resolve13(resolved, "grok"),
|
|
20728
|
+
CURSOR_HOME: resolve13(resolved, "cursor"),
|
|
20625
20729
|
PI_CODING_AGENT_DIR: resolve13(resolved, "pi"),
|
|
20626
20730
|
XDG_STATE_HOME: resolve13(resolved, "state"),
|
|
20627
20731
|
XDG_CACHE_HOME: resolve13(resolved, "cache"),
|
|
@@ -20633,6 +20737,7 @@ var HARNESS_STATE_ENV_VARS = [
|
|
|
20633
20737
|
"CODEX_HOME",
|
|
20634
20738
|
"GOOSE_PATH_ROOT",
|
|
20635
20739
|
"GROK_HOME",
|
|
20740
|
+
"CURSOR_HOME",
|
|
20636
20741
|
"PI_CODING_AGENT_DIR",
|
|
20637
20742
|
"XDG_STATE_HOME",
|
|
20638
20743
|
"XDG_CACHE_HOME",
|
|
@@ -24422,6 +24527,23 @@ var SessionScheduler = class {
|
|
|
24422
24527
|
this.live.delete(key);
|
|
24423
24528
|
await this.retire(session);
|
|
24424
24529
|
}
|
|
24530
|
+
/**
|
|
24531
|
+
* Retire every RETAINED session — the warm processes holding no turn — in
|
|
24532
|
+
* one pass. This is the config-change hot restart: a phone-side model/effort
|
|
24533
|
+
* selection change must not wait for each Room's next hand-back currency
|
|
24534
|
+
* check to notice. A busy, draining, queued, or still-spawning session is
|
|
24535
|
+
* deliberately left alone: interrupting a live turn buys nothing, and its
|
|
24536
|
+
* next hand-back runs the ordinary `isCurrent` check, which cold-activates
|
|
24537
|
+
* against the new selection. Suspending a key that is no longer live is a
|
|
24538
|
+
* no-op, so overlapping wakes coalesce safely.
|
|
24539
|
+
*/
|
|
24540
|
+
async suspendIdle() {
|
|
24541
|
+
for (const [key, session] of [...this.live.entries()]) {
|
|
24542
|
+
if (session.pending || this.busy.has(key) || this.drainingKeys.has(key) || (this.queues.get(key)?.length ?? 0) > 0)
|
|
24543
|
+
continue;
|
|
24544
|
+
await this.suspend(key);
|
|
24545
|
+
}
|
|
24546
|
+
}
|
|
24425
24547
|
/**
|
|
24426
24548
|
* Tear down a poisoned session even when its task is still marked busy.
|
|
24427
24549
|
* The watchdog uses this only after a Room has stopped making progress; the
|
|
@@ -24632,6 +24754,25 @@ var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
|
|
|
24632
24754
|
var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
|
|
24633
24755
|
var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
|
|
24634
24756
|
var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
|
|
24757
|
+
var DiscoveryWakes = class {
|
|
24758
|
+
arrived = 0;
|
|
24759
|
+
served = 0;
|
|
24760
|
+
/** Called for every agent-directed `rooms-changed` wake. */
|
|
24761
|
+
wake() {
|
|
24762
|
+
this.arrived += 1;
|
|
24763
|
+
}
|
|
24764
|
+
needsFastReconcile() {
|
|
24765
|
+
return this.arrived !== this.served;
|
|
24766
|
+
}
|
|
24767
|
+
/** Snapshot at reconcile entry: the wake count its reads will cover. */
|
|
24768
|
+
beginReconcile() {
|
|
24769
|
+
return this.arrived;
|
|
24770
|
+
}
|
|
24771
|
+
/** Clear only wakes the completed start pass actually served. */
|
|
24772
|
+
completeReconcile(covered) {
|
|
24773
|
+
this.served = Math.max(this.served, covered);
|
|
24774
|
+
}
|
|
24775
|
+
};
|
|
24635
24776
|
function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
|
|
24636
24777
|
return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
|
|
24637
24778
|
}
|
|
@@ -24808,6 +24949,11 @@ var RoomRuntimeCoordinator = class {
|
|
|
24808
24949
|
workspaceRemovalConfirmations = 0;
|
|
24809
24950
|
roomRemovalConfirmations = /* @__PURE__ */ new Map();
|
|
24810
24951
|
confirmationPending = false;
|
|
24952
|
+
/** Agent-directed discovery wakes (#1369), counted instead of flagged: a
|
|
24953
|
+
* wake that arrives while a reconcile is already running must survive its
|
|
24954
|
+
* start-pass clearing, or a corner opened mid-reconcile waits a heartbeat
|
|
24955
|
+
* for a wake the daemon already received. */
|
|
24956
|
+
discoveryWakes = new DiscoveryWakes();
|
|
24811
24957
|
/** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
|
|
24812
24958
|
grantRunner;
|
|
24813
24959
|
grantRunnerServer;
|
|
@@ -24828,6 +24974,12 @@ var RoomRuntimeCoordinator = class {
|
|
|
24828
24974
|
});
|
|
24829
24975
|
this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
|
|
24830
24976
|
this.connectorUsage = new ConnectorUsageRecorder();
|
|
24977
|
+
this.options.daemonApi.setRoomsChangedListener?.(() => {
|
|
24978
|
+
this.discoveryWakes.wake();
|
|
24979
|
+
});
|
|
24980
|
+
this.options.daemonApi.setConfigChangedListener?.(() => {
|
|
24981
|
+
void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
|
|
24982
|
+
});
|
|
24831
24983
|
this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
|
|
24832
24984
|
this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? DEFAULT_RECONCILE_HEARTBEAT_MS;
|
|
24833
24985
|
this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
|
|
@@ -24858,7 +25010,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
24858
25010
|
return this.scheduler.snapshot();
|
|
24859
25011
|
}
|
|
24860
25012
|
needsFastReconcile() {
|
|
24861
|
-
return this.confirmationPending;
|
|
25013
|
+
return this.confirmationPending || this.discoveryWakes.needsFastReconcile();
|
|
24862
25014
|
}
|
|
24863
25015
|
reconcileHeartbeatIntervalMs() {
|
|
24864
25016
|
return this.reconcileHeartbeatMs;
|
|
@@ -24894,6 +25046,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
24894
25046
|
}
|
|
24895
25047
|
async reconcile() {
|
|
24896
25048
|
this.confirmationPending = false;
|
|
25049
|
+
const coveredWakes = this.discoveryWakes.beginReconcile();
|
|
24897
25050
|
const bootstrap = await this.options.daemonApi.execute("getDaemonBootstrap", {
|
|
24898
25051
|
agentId: this.agent.publicKey
|
|
24899
25052
|
});
|
|
@@ -24961,8 +25114,13 @@ var RoomRuntimeCoordinator = class {
|
|
|
24961
25114
|
}
|
|
24962
25115
|
}
|
|
24963
25116
|
await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
|
|
24964
|
-
if (
|
|
25117
|
+
if (this.running.has(roomId))
|
|
25118
|
+
return;
|
|
25119
|
+
try {
|
|
24965
25120
|
await this.startRoom(roomId);
|
|
25121
|
+
} catch (error) {
|
|
25122
|
+
console.error(`[thin-core] failed to start Room ${roomId}:`, error);
|
|
25123
|
+
}
|
|
24966
25124
|
});
|
|
24967
25125
|
await mapWithConcurrency([...desiredCorners.values()], ROOM_JOIN_CONCURRENCY, async (corner) => {
|
|
24968
25126
|
if (!this.running.has(corner.cornerId))
|
|
@@ -24970,6 +25128,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
24970
25128
|
});
|
|
24971
25129
|
for (const running of this.running.values())
|
|
24972
25130
|
running.body.requestReconciliation();
|
|
25131
|
+
this.discoveryWakes.completeReconcile(coveredWakes);
|
|
24973
25132
|
return "member";
|
|
24974
25133
|
}
|
|
24975
25134
|
roomRecord(roomId) {
|
|
@@ -25854,6 +26013,7 @@ Install one of these supported agents:
|
|
|
25854
26013
|
goose https://block.github.io/goose/docs/getting-started/installation/
|
|
25855
26014
|
pi npm install -g @mariozechner/pi-coding-agent pi-acp
|
|
25856
26015
|
grok curl -fsSL https://x.ai/cli/install.sh | bash
|
|
26016
|
+
cursor npm install -g cursor-agent-acp
|
|
25857
26017
|
Then retry, or explicitly use \`--agent reference\` with an LLM key.
|
|
25858
26018
|
For another ACP server, use \`--agent custom --agent-command "<cmd> [args...]"\`.`;
|
|
25859
26019
|
async function clackConfirmInstall(message) {
|
|
@@ -26092,7 +26252,8 @@ var DEFAULT_MODELS = {
|
|
|
26092
26252
|
xai: "grok-4",
|
|
26093
26253
|
codex: "gpt-5.4",
|
|
26094
26254
|
claude: "claude-opus-4-1",
|
|
26095
|
-
grok: "grok-4"
|
|
26255
|
+
grok: "grok-4",
|
|
26256
|
+
cursor: "claude-sonnet-4"
|
|
26096
26257
|
};
|
|
26097
26258
|
function connectHarnessNeedsProvider(harness) {
|
|
26098
26259
|
return CONNECT_PROVIDER_HARNESSES.has(harness);
|