usebeeline 0.1.21 → 0.1.23
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 +172 -44
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -13722,7 +13722,38 @@ function squireApprovalFromMcp(request, response) {
|
|
|
13722
13722
|
if (!link2)
|
|
13723
13723
|
return void 0;
|
|
13724
13724
|
const args = record(params?.arguments) ?? {};
|
|
13725
|
-
|
|
13725
|
+
const expiresAt = approvalExpiryIn(response.result);
|
|
13726
|
+
return {
|
|
13727
|
+
tool,
|
|
13728
|
+
...squireApprovalCopy(tool, args),
|
|
13729
|
+
...link2,
|
|
13730
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
13731
|
+
};
|
|
13732
|
+
}
|
|
13733
|
+
function approvalExpiryIn(value, depth = 0) {
|
|
13734
|
+
if (depth > 6)
|
|
13735
|
+
return void 0;
|
|
13736
|
+
if (typeof value === "string") {
|
|
13737
|
+
if (value.length > 2e4 || !/^[{[]/.test(value.trim()))
|
|
13738
|
+
return void 0;
|
|
13739
|
+
try {
|
|
13740
|
+
return approvalExpiryIn(JSON.parse(value), depth + 1);
|
|
13741
|
+
} catch {
|
|
13742
|
+
return void 0;
|
|
13743
|
+
}
|
|
13744
|
+
}
|
|
13745
|
+
if (Array.isArray(value))
|
|
13746
|
+
return value.map((entry) => approvalExpiryIn(entry, depth + 1)).find((expiry) => expiry !== void 0);
|
|
13747
|
+
const item = record(value);
|
|
13748
|
+
if (!item)
|
|
13749
|
+
return void 0;
|
|
13750
|
+
const raw = item.expires_at ?? item.expiresAt;
|
|
13751
|
+
if (typeof raw === "string") {
|
|
13752
|
+
const parsed = Date.parse(raw);
|
|
13753
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
13754
|
+
return parsed;
|
|
13755
|
+
}
|
|
13756
|
+
return Object.values(item).map((entry) => approvalExpiryIn(entry, depth + 1)).find((expiry) => expiry !== void 0);
|
|
13726
13757
|
}
|
|
13727
13758
|
function squireLoginWallIn(value, depth = 0) {
|
|
13728
13759
|
if (depth > 6)
|
|
@@ -16691,6 +16722,32 @@ function defaultSquireMcpClient(scope) {
|
|
|
16691
16722
|
return new StdioSquireMcpClient({ scope });
|
|
16692
16723
|
}
|
|
16693
16724
|
|
|
16725
|
+
// apps/body/dist/squire-session-registry.js
|
|
16726
|
+
var owners = /* @__PURE__ */ new Map();
|
|
16727
|
+
function registerSquireSession(sessionId, owner) {
|
|
16728
|
+
if (owners.get(sessionId)?.client === owner.client)
|
|
16729
|
+
return;
|
|
16730
|
+
owners.set(sessionId, { ...owner, scheduleIds: /* @__PURE__ */ new Set() });
|
|
16731
|
+
}
|
|
16732
|
+
function forgetSquireSessions(sessionIds2) {
|
|
16733
|
+
for (const sessionId of sessionIds2)
|
|
16734
|
+
owners.delete(sessionId);
|
|
16735
|
+
}
|
|
16736
|
+
function squireSessionOwner(sessionId) {
|
|
16737
|
+
return owners.get(sessionId);
|
|
16738
|
+
}
|
|
16739
|
+
function tagSquireSessionSchedule(sessionId, scheduleId) {
|
|
16740
|
+
owners.get(sessionId)?.scheduleIds.add(scheduleId);
|
|
16741
|
+
}
|
|
16742
|
+
function squireSessionInheritable(sessionId, agentId, conversationId, scheduleId) {
|
|
16743
|
+
const owner = owners.get(sessionId);
|
|
16744
|
+
if (!owner || owner.agentId !== agentId)
|
|
16745
|
+
return false;
|
|
16746
|
+
if (owner.scheduleIds.has(scheduleId))
|
|
16747
|
+
return true;
|
|
16748
|
+
return owner.scheduleIds.size === 0 && owner.conversationId === conversationId;
|
|
16749
|
+
}
|
|
16750
|
+
|
|
16694
16751
|
// apps/body/dist/squire-task-relay.js
|
|
16695
16752
|
var SQUIRE_TASK_IDLE_LEASE_MS = 15 * 6e4;
|
|
16696
16753
|
function sessionIds(value, depth = 0) {
|
|
@@ -16765,6 +16822,8 @@ var SquireTaskRelay = class {
|
|
|
16765
16822
|
url;
|
|
16766
16823
|
token = randomUUID3();
|
|
16767
16824
|
active;
|
|
16825
|
+
/** The schedule a `schedule` command is a run of, if any. */
|
|
16826
|
+
activeScheduleId;
|
|
16768
16827
|
task;
|
|
16769
16828
|
leaseTimer;
|
|
16770
16829
|
approvalRequestId;
|
|
@@ -16833,6 +16892,7 @@ var SquireTaskRelay = class {
|
|
|
16833
16892
|
taskId: command.rootCommandId,
|
|
16834
16893
|
generationId
|
|
16835
16894
|
};
|
|
16895
|
+
this.activeScheduleId = command.scheduleId ?? command.source?.systemEvent?.scheduleId;
|
|
16836
16896
|
if (this.task) {
|
|
16837
16897
|
this.task.taskId = command.rootCommandId;
|
|
16838
16898
|
this.task.lastRequestId = command.turnRequestId;
|
|
@@ -16925,6 +16985,7 @@ var SquireTaskRelay = class {
|
|
|
16925
16985
|
exitCode: code,
|
|
16926
16986
|
sessionId: redactedSessionId(task.sessions.values().next().value)
|
|
16927
16987
|
});
|
|
16988
|
+
forgetSquireSessions(task.sessions);
|
|
16928
16989
|
task.sessions.clear();
|
|
16929
16990
|
task.pendingApprovals.clear();
|
|
16930
16991
|
}
|
|
@@ -16932,6 +16993,38 @@ var SquireTaskRelay = class {
|
|
|
16932
16993
|
this.task = task;
|
|
16933
16994
|
return task;
|
|
16934
16995
|
}
|
|
16996
|
+
/**
|
|
16997
|
+
* The client that may drive the requested sessions. Own sessions use this
|
|
16998
|
+
* relay's connection. A schedule run may borrow a live session from its
|
|
16999
|
+
* creating conversation or an earlier run of the same schedule, and the call
|
|
17000
|
+
* then runs on that session's owning connection. Anything else returns this
|
|
17001
|
+
* relay's client so the ordinary ownership refusal fires.
|
|
17002
|
+
*/
|
|
17003
|
+
inheritedCallClient(task, borrowed, requestedCount) {
|
|
17004
|
+
if (!borrowed.length)
|
|
17005
|
+
return task.client;
|
|
17006
|
+
if (borrowed.length !== requestedCount)
|
|
17007
|
+
return task.client;
|
|
17008
|
+
const scheduleId = this.activeScheduleId;
|
|
17009
|
+
if (!scheduleId)
|
|
17010
|
+
return task.client;
|
|
17011
|
+
const owner = squireSessionOwner(borrowed[0]);
|
|
17012
|
+
if (!owner || !borrowed.every((id) => squireSessionOwner(id)?.client === owner.client && squireSessionInheritable(id, this.agentId, this.roomId, scheduleId)))
|
|
17013
|
+
return task.client;
|
|
17014
|
+
return owner.client;
|
|
17015
|
+
}
|
|
17016
|
+
/**
|
|
17017
|
+
* Record every session this connection holds, so another conversation can
|
|
17018
|
+
* find the owning client when a schedule it created runs there. The owning
|
|
17019
|
+
* relay stays the one that issues the call.
|
|
17020
|
+
*/
|
|
17021
|
+
rememberSessions(client, ids, scheduleId) {
|
|
17022
|
+
for (const id of ids) {
|
|
17023
|
+
registerSquireSession(id, { agentId: this.agentId, conversationId: this.roomId, client });
|
|
17024
|
+
if (scheduleId)
|
|
17025
|
+
tagSquireSessionSchedule(id, scheduleId);
|
|
17026
|
+
}
|
|
17027
|
+
}
|
|
16935
17028
|
retire(reason) {
|
|
16936
17029
|
if (this.leaseTimer)
|
|
16937
17030
|
clearTimeout(this.leaseTimer);
|
|
@@ -16943,6 +17036,7 @@ var SquireTaskRelay = class {
|
|
|
16943
17036
|
sessionId: redactedSessionId(this.task.sessions.values().next().value)
|
|
16944
17037
|
});
|
|
16945
17038
|
this.task.client.close();
|
|
17039
|
+
forgetSquireSessions(this.task.sessions);
|
|
16946
17040
|
this.task.sessions.clear();
|
|
16947
17041
|
this.task.pendingApprovals.clear();
|
|
16948
17042
|
this.task = void 0;
|
|
@@ -16985,11 +17079,15 @@ var SquireTaskRelay = class {
|
|
|
16985
17079
|
this.log("call-end", task, { method: input.method, source: "cached" });
|
|
16986
17080
|
return task.tools;
|
|
16987
17081
|
}
|
|
16988
|
-
|
|
17082
|
+
const borrowed = input.method === "tools/call" ? requestedIds.filter((id) => !task.sessions.has(id)) : [];
|
|
17083
|
+
let callClient = this.inheritedCallClient(task, borrowed, requestedIds.length);
|
|
17084
|
+
const usingOwnConnection = callClient === task.client;
|
|
17085
|
+
if (input.method === "tools/call" && usingOwnConnection && task.dead && toolName2 !== "operate_start") {
|
|
16989
17086
|
this.log("call-refused", task, { reason: "connection-died" });
|
|
16990
17087
|
throw new Error("Squire MCP connection died and its browser session is gone; call operate_start");
|
|
16991
17088
|
}
|
|
16992
|
-
|
|
17089
|
+
const scheduleId = this.activeScheduleId;
|
|
17090
|
+
if (input.method === "tools/call" && (!safeToolName || usingOwnConnection && requestedIds.some((id) => !task.sessions.has(id)) || scheduleId && requestedIds.some((id) => !squireSessionInheritable(id, this.agentId, this.roomId, scheduleId)))) {
|
|
16993
17091
|
this.log("call-refused", task, { reason: "session-not-owned" });
|
|
16994
17092
|
throw new Error("Squire browser session is no longer owned by this conversation; call operate_start");
|
|
16995
17093
|
}
|
|
@@ -17001,16 +17099,17 @@ var SquireTaskRelay = class {
|
|
|
17001
17099
|
this.log("call-refused", task, { reason: "resource-denied" });
|
|
17002
17100
|
throw new Error("Squire call is not authorized for this task");
|
|
17003
17101
|
}
|
|
17004
|
-
if (task.dead) {
|
|
17102
|
+
if (usingOwnConnection && task.dead) {
|
|
17005
17103
|
if (toolName2 !== "operate_start" || requestedIds.length) {
|
|
17006
17104
|
this.log("call-refused", task, { reason: "restart-required" });
|
|
17007
17105
|
throw new Error("Squire MCP connection died; call operate_start for a fresh browser session");
|
|
17008
17106
|
}
|
|
17009
17107
|
this.retire("restart-required");
|
|
17010
17108
|
task = this.connection(active.taskId);
|
|
17109
|
+
callClient = task.client;
|
|
17011
17110
|
}
|
|
17012
17111
|
try {
|
|
17013
|
-
const result = await
|
|
17112
|
+
const result = await callClient.requestMcp(input.method, input.params ?? {});
|
|
17014
17113
|
if (this.closed || this.task !== task || active && (this.active?.taskId !== active.taskId || this.active?.requestId !== active.requestId))
|
|
17015
17114
|
throw new Error("Squire task ended while the call was running");
|
|
17016
17115
|
if (input.method === "tools/list")
|
|
@@ -17020,6 +17119,7 @@ var SquireTaskRelay = class {
|
|
|
17020
17119
|
if (staleLeaseResult(result)) {
|
|
17021
17120
|
for (const id of requestedIds)
|
|
17022
17121
|
task.sessions.delete(id);
|
|
17122
|
+
forgetSquireSessions(requestedIds);
|
|
17023
17123
|
this.releasePendingApprovals(task, requestedIds);
|
|
17024
17124
|
this.log("call-error", task, {
|
|
17025
17125
|
method: input.method,
|
|
@@ -17034,11 +17134,17 @@ var SquireTaskRelay = class {
|
|
|
17034
17134
|
if (safeToolName === "operate_finish" && !(result && typeof result === "object" && result.isError === true)) {
|
|
17035
17135
|
for (const id of requestedIds)
|
|
17036
17136
|
task.sessions.delete(id);
|
|
17137
|
+
forgetSquireSessions(requestedIds);
|
|
17037
17138
|
this.releasePendingApprovals(task, requestedIds);
|
|
17038
|
-
} else {
|
|
17039
|
-
|
|
17139
|
+
} else if (usingOwnConnection) {
|
|
17140
|
+
const created = sessionIds(result);
|
|
17141
|
+
for (const id of created)
|
|
17040
17142
|
task.sessions.add(id);
|
|
17143
|
+
this.rememberSessions(callClient, created, this.activeScheduleId);
|
|
17041
17144
|
}
|
|
17145
|
+
if (this.activeScheduleId)
|
|
17146
|
+
for (const id of requestedIds)
|
|
17147
|
+
tagSquireSessionSchedule(id, this.activeScheduleId);
|
|
17042
17148
|
const approval = input.method === "tools/call" && squireApprovalFromMcp({ id: 1, method: input.method, params: input.params }, { id: 1, result });
|
|
17043
17149
|
if (approval) {
|
|
17044
17150
|
const ids = requestedIds.length ? requestedIds : [...task.sessions];
|
|
@@ -17715,7 +17821,7 @@ async function refresh(state, transport) {
|
|
|
17715
17821
|
};
|
|
17716
17822
|
}
|
|
17717
17823
|
var REGISTRY_MCP_APPROVAL_REFUSAL = "Resource access requires approval from its owner for this requester, or the resource is unavailable.";
|
|
17718
|
-
var REGISTRY_MCP_UNAVAILABLE = "Registry MCP provider is unavailable; reconnect it in
|
|
17824
|
+
var REGISTRY_MCP_UNAVAILABLE = "Registry MCP provider is unavailable; reconnect it in Connectors.";
|
|
17719
17825
|
var RegistryMcpRefusal = class extends Error {
|
|
17720
17826
|
constructor() {
|
|
17721
17827
|
super(REGISTRY_MCP_APPROVAL_REFUSAL);
|
|
@@ -18160,7 +18266,7 @@ function classifyTurnSilence(reason, reasonKind) {
|
|
|
18160
18266
|
if (isContextOverflowFault(text2)) {
|
|
18161
18267
|
return { kind: "context-overflow" };
|
|
18162
18268
|
}
|
|
18163
|
-
if (/\bresource_limit: ACP session exceeded its \d+ MiB memory limit\b/i.test(text2)) {
|
|
18269
|
+
if (/\bresource_limit: ACP session (?:exceeded|likely hit) its \d+ MiB memory limit\b/i.test(text2)) {
|
|
18164
18270
|
return { kind: "resource-limit" };
|
|
18165
18271
|
}
|
|
18166
18272
|
if (/\b402\b/.test(text2) || /usage limit/i.test(text2) || /more credits/i.test(text2) || /quota/i.test(text2) || /allowance is spent/i.test(text2)) {
|
|
@@ -18192,6 +18298,7 @@ function isStandingWorkspaceConfigurationFault(text2) {
|
|
|
18192
18298
|
}
|
|
18193
18299
|
|
|
18194
18300
|
// packages/api-contract/dist/institutional-memory.js
|
|
18301
|
+
var WORKSPACE_CORE_SKILL_SLUG = "workspace-core";
|
|
18195
18302
|
var WORKSPACE_SKILL_MARKDOWN_MAX_BYTES = 32 * 1024;
|
|
18196
18303
|
|
|
18197
18304
|
// packages/api-contract/dist/workflow-contracts.js
|
|
@@ -29641,7 +29748,9 @@ var AcpResourceScope = class {
|
|
|
29641
29748
|
`--unit=${this.unit}`,
|
|
29642
29749
|
`--property=MemoryMax=${this.memoryMaxBytes}`,
|
|
29643
29750
|
"--property=MemorySwapMax=0",
|
|
29644
|
-
|
|
29751
|
+
// Let the kernel kill one offending process. `kill` sets
|
|
29752
|
+
// memory.oom.group=1 and ends the ACP runtime with the command.
|
|
29753
|
+
"--property=OOMPolicy=continue",
|
|
29645
29754
|
"--property=KillMode=control-group",
|
|
29646
29755
|
"--property=TimeoutStopSec=1s",
|
|
29647
29756
|
...owner ? [`--property=BindsTo=${owner}`, `--property=After=${owner}`] : [],
|
|
@@ -29654,15 +29763,6 @@ var AcpResourceScope = class {
|
|
|
29654
29763
|
env
|
|
29655
29764
|
};
|
|
29656
29765
|
}
|
|
29657
|
-
/** An OOM result survives process exit until reset-failed unloads the unit. */
|
|
29658
|
-
async exceededMemoryLimit() {
|
|
29659
|
-
try {
|
|
29660
|
-
const { stdout: stdout6 } = await execFileAsync5("systemctl", ["--user", "show", this.unit, "--property=Result", "--value"], { timeout: 5e3 });
|
|
29661
|
-
return stdout6.trim() === "oom-kill";
|
|
29662
|
-
} catch {
|
|
29663
|
-
return false;
|
|
29664
|
-
}
|
|
29665
|
-
}
|
|
29666
29766
|
async stop() {
|
|
29667
29767
|
return this.#stop ??= (async () => {
|
|
29668
29768
|
try {
|
|
@@ -30008,7 +30108,7 @@ var AcpRuntimeExitedError = class extends Error {
|
|
|
30008
30108
|
var AcpResourceLimitError = class extends Error {
|
|
30009
30109
|
cause = "resource_limit";
|
|
30010
30110
|
constructor(memoryMaxBytes) {
|
|
30011
|
-
super(`resource_limit: ACP session
|
|
30111
|
+
super(`resource_limit: ACP session likely hit its ${Math.floor(memoryMaxBytes / 1024 / 1024)} MiB memory limit (exit 137 or signal 9)`);
|
|
30012
30112
|
this.name = "AcpResourceLimitError";
|
|
30013
30113
|
}
|
|
30014
30114
|
};
|
|
@@ -30414,9 +30514,9 @@ ${String(error)}`);
|
|
|
30414
30514
|
}
|
|
30415
30515
|
/**
|
|
30416
30516
|
* One child exit: reject every in-flight request so the turn fails and is
|
|
30417
|
-
* reported instead of hanging, then publish the exit. A scoped
|
|
30418
|
-
*
|
|
30419
|
-
*
|
|
30517
|
+
* reported instead of hanging, then publish the exit. A scoped SIGKILL/137
|
|
30518
|
+
* is a likely memory-limit hit, while an unscoped SIGKILL uses the daemon
|
|
30519
|
+
* cgroup's OOM counter to distinguish it from a deliberate kill.
|
|
30420
30520
|
*/
|
|
30421
30521
|
async handleChildExit(code, signal) {
|
|
30422
30522
|
const child = this.child;
|
|
@@ -30424,9 +30524,9 @@ ${String(error)}`);
|
|
|
30424
30524
|
for (const p of this.pending.values())
|
|
30425
30525
|
this.clearTimer(p);
|
|
30426
30526
|
const scope = this.resourceScope;
|
|
30427
|
-
const
|
|
30527
|
+
const memoryLimitLikely = Boolean(scope && !this.stopping && (signal === "SIGKILL" || code === 137));
|
|
30428
30528
|
let oomKilled = false;
|
|
30429
|
-
if (!
|
|
30529
|
+
if (!scope && signal === "SIGKILL" && !this.stopping) {
|
|
30430
30530
|
let diagnosticTimer;
|
|
30431
30531
|
try {
|
|
30432
30532
|
oomKilled = await Promise.race([
|
|
@@ -30444,7 +30544,7 @@ ${String(error)}`);
|
|
|
30444
30544
|
const stderrSuffix = meaningful ? `: ${meaningful}` : "";
|
|
30445
30545
|
for (const [, p] of this.pending) {
|
|
30446
30546
|
this.clearTimer(p);
|
|
30447
|
-
p.reject(
|
|
30547
|
+
p.reject(memoryLimitLikely ? new AcpResourceLimitError(scope.memoryMaxBytes) : new AcpRuntimeExitedError(`${exitText}${stderrSuffix}`));
|
|
30448
30548
|
}
|
|
30449
30549
|
this.pending.clear();
|
|
30450
30550
|
this.activeRunIds.clear();
|
|
@@ -32455,29 +32555,29 @@ These mechanics apply in Rooms and corners. ${BEELINE_ROOM_MECHANICS}
|
|
|
32455
32555
|
|
|
32456
32556
|
Follow your own owner first, then this Workspace's owner and admins, then members. A higher-tier instruction overrides a lower-tier hold. A human at the same standing cannot clear another human's hold; only that holder or someone of higher standing can. Never tell a higher-tier human that a lower-tier hold binds them. Reason from the conversation. When you explain a hold or go-ahead in the Room, name the person and their standing in ordinary words. Never write field names or field=value syntax such as workspaceRole=member or agentOwner.
|
|
32457
32557
|
|
|
32458
|
-
## Tools and the
|
|
32558
|
+
## Tools and the Connectors
|
|
32459
32559
|
|
|
32460
32560
|
A **tool** is something you can use once a human adds it; a **key** is the credential that tool holds for that human. You spend a key through the mounted connector and never see the credential itself.
|
|
32461
32561
|
|
|
32462
|
-
The tools this build knows, what each is for, and which the owner of this machine already has are one call away: beeline-agent
|
|
32562
|
+
The tools this build knows, what each is for, and which the owner of this machine already has are one call away: beeline-agent connectors_status. That read is the Connectors page for the machine and owner you actually run on, not the person who asked. Connected apps are listed once per app with a stable ID. Their sign-in and credentials stay with the server. Trusty Squire signs up and signs in to any website hands-off for the person; its vault holds keys when needed. operate_drive drives a whole flow, and flat steps (operate_click, operate_type, operate_select, operate_press, operate_scroll, operate_wait, operate_navigate, operate_read_inbox, operate_extract, and the rest of its flat operate_* actions) handle one action at a time. Tailscale installs its CLI on the selected helper and opens Tailscale's own browser sign-in; once connected, use the CLI for tailnet resources and tailscale file cp or tailscale file get for Taildrop. The wallet is created only from the Connectors page. When you cannot reach a tool, say: I can't reach X on this machine because Y; to fix it, Z. Give one explanation, then take or offer Z.
|
|
32463
32563
|
|
|
32464
32564
|
**Inspect your own host before answering.** When a question is about a system on the machine you run on, inspect its live state with the tools you have: processes, logs, local endpoints or service status. Say what you checked. Memory and Room history are not evidence of current state.
|
|
32465
32565
|
|
|
32466
|
-
**Use available tools before asking the person.** Before asking a person for a code, a status or a file, call
|
|
32566
|
+
**Use available tools before asking the person.** Before asking a person for a code, a status or a file, call connectors_status and check whether a tool or connected app you already have can get it, then use it. This includes an emailed verification code in an already-connected inbox. Ask only when none can, and say why. The key-ownership and grant rules still apply.
|
|
32467
32567
|
|
|
32468
|
-
**Offer the tool at the moment you need it.** When the work in front of you needs a tool the person does not have, do not send them to a settings page and do not stop at naming it. Call
|
|
32568
|
+
**Offer the tool at the moment you need it.** When the work in front of you needs a tool the person does not have, do not send them to a settings page and do not stop at naming it. Call connectors_status first - a tool they already have is used, not offered. In a Room or DM, call offer_connector with the connectorType and one short reason: a card appears there, spoken by you, addressed to the person you are answering, with one action. Connector offers are unavailable in corners; use a Room or DM for those. When the person wants Squire on a different Google account (or its signed-in account is the wrong one), call offer_connector with connectorType trusty-squire and intent "switch" - a plain connect keeps the account it already has. Your turn pauses on the card - say in prose what you are waiting for and end the turn; you are woken when it is added, and then you carry on.
|
|
32469
32569
|
|
|
32470
32570
|
**Research before you offer, and say so.** Never offer a tool you cannot describe. If someone asks you to install or add something you do not already know - by name, by purpose, or by what it will hold - say plainly that you are looking it up first, find out what it is and what it does with credentials, and state what you learned in your reply BEFORE the card appears. A person reading "Add X?" must already have read, in your own words, what X is. Refusing to act blind is part of being trusted with keys.
|
|
32471
32571
|
|
|
32472
32572
|
An offer is setup, never authority: it does not replace a grant, write permission, target-branch confirmation, or the merge gate, and it never needs a raw credential in chat. You never pair a tool yourself and never ask anyone for a key value; once a tool is added, provisioning happens inside it and receipts reach the person through the tool's own status message.
|
|
32473
32573
|
|
|
32474
|
-
Keys belong to the human who provisioned them. Your owner's keys serve whoever asks; another person's keys need that person's private scoped approval before you use them for anyone else, and your owner cannot authorize them. Use the connector without exposing or sharing raw keys. The
|
|
32574
|
+
Keys belong to the human who provisioned them. Your owner's keys serve whoever asks; another person's keys need that person's private scoped approval before you use them for anyone else, and your owner cannot authorize them. Use the connector without exposing or sharing raw keys. The Connectors page remains the place a person manages tools and keys by hand (Settings \u2192 Connectors); you point there to MANAGE what exists, not to add what you need.
|
|
32475
32575
|
|
|
32476
32576
|
## Where you run
|
|
32477
32577
|
|
|
32478
32578
|
Each agent runs on one owner's machine, with that owner's keys, hosts, and network. Agents in one Room can differ in what they reach. Memory is shared across all machines.
|
|
32479
32579
|
|
|
32480
|
-
- Check your own capabilities with
|
|
32580
|
+
- Check your own capabilities with connectors_status and your tools. Never infer another agent's.
|
|
32481
32581
|
- A failure saved from another machine is not proof here. Try it first.
|
|
32482
32582
|
- If you cannot do X here, say "I can't do X on this machine because Y", then hand off to an agent that memory shows has done X, or ask the requester who has access.
|
|
32483
32583
|
|
|
@@ -32494,16 +32594,16 @@ You keep memory yourself. No background reviewer exists: what you do not keep cu
|
|
|
32494
32594
|
| Workflows | Roles and steps for a run across agents | save_workflow; run with start_workflow |
|
|
32495
32595
|
| History | Every message in the Workspace | Nothing; read with search_history |
|
|
32496
32596
|
|
|
32497
|
-
- **Look up.**
|
|
32597
|
+
- **Look up.** Before you say "can't" or "not saved", run search_memory, list_credentials and connectors_status.
|
|
32498
32598
|
- **Use.** Call report_memory_used for what your answer used. When a saved item fails, fix it in the same turn.
|
|
32499
32599
|
- **Save.** Put each fact in one store only. Never save "X is impossible". Save the specific failure and the machine: "On Niglet's host, the highroll Fly token returns 403."
|
|
32500
32600
|
- **Prune.** Rewrite \`workspace-core\` whole and keep it at most 2,200 characters. An agent-saved Workspace fact that nobody uses expires in the weekly pass. Replace a procedure or workflow by saving its slug again; remove it with delete_skill.
|
|
32501
32601
|
|
|
32502
32602
|
## Connect an app
|
|
32503
32603
|
|
|
32504
|
-
When a request needs an app or service, call beeline-agent \`
|
|
32604
|
+
When a request needs an app or service, call beeline-agent \`connectors_status\` first. For a connected app via composio, use its ID with \`list_app_tools\`, then call \`execute_app_tool\` with the selected tool slug and arguments. For an app via squire-api, call its API with \`use_credential\` (service = the app key); via squire-browser, use the \`operate_*\` tools; via registry-mcp, use its mounted MCP tools. The server executes against that person\u2019s account and decides permission on every call. If tool discovery reports an error, follow its cause; \`needs_connection\` means the account must be reconnected, and \`needs_permission\` means wait for the connected person's decision. Do not switch paths after denial or provider unavailability. No app token, provider key, or sign-in URL belongs in chat or a model prompt.
|
|
32505
32605
|
|
|
32506
|
-
For an app that is not connected, call \`connect_app\` with its name, one short reason, and a request-specific \`continuation\`: one sentence stating what you will do right after sign-in. Do not include a link, credential, account identifier, or private data in that sentence. The server chooses the route and returns \`next\`. In a corner, call it from that corner so the sign-in card and connection wake return there. A managed OAuth app presents a sign-in card to its owner; end the turn and wait for the connection wake.
|
|
32606
|
+
For an app that is not connected, call \`connect_app\` with its name, one short reason, and a request-specific \`continuation\`: one sentence stating what you will do right after sign-in. Do not include a link, credential, account identifier, or private data in that sentence. The server chooses the route and returns \`next\`. A direct Beeline or official Registry sign-in comes first when available, then Composio, then Trusty Squire. In a corner, call it from that corner so the sign-in card and connection wake return there. A managed OAuth app presents a sign-in card to its owner; end the turn and wait for the connection wake. A provider outage is unavailable, not permission to change routes. After the person connects, read Connectors again and use the app ID. Every use is authorized as \`app:<key>\` and recorded once.
|
|
32507
32607
|
|
|
32508
32608
|
## Showing a mock
|
|
32509
32609
|
|
|
@@ -41663,12 +41763,15 @@ Only when the brief calls for repository changes, follow the PR procedure below.
|
|
|
41663
41763
|
Before the PR, demonstrate Y: run the built app or affected service and perform X. If no interactive surface is reachable, run the narrowest test or script exercising the exact user path and printing Y. An inner-function unit test, log line or code read is not a demonstration.
|
|
41664
41764
|
The PR body MUST have ## Reproduced and ## Demonstrated. Cite Reproduction <id> in both, with steps, the wrong result and the now-passing result. In ## Demonstrated, when none was obtained, state that plainly ("not obtained") and show the regression; for feature work say "not a defect report".
|
|
41665
41765
|
No unrequested features, flags, compatibility shims or refactors.`;
|
|
41666
|
-
var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer.
|
|
41667
|
-
var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "
|
|
41766
|
+
var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. Your PASS is approve_merge on the current green PR head, which your turn prompt names. Call it when that head is ready to merge. Never merge a pull request you review.";
|
|
41767
|
+
var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "The current PR head is not green, so approve_merge on it fails now. The server wakes you when checks pass on the current head.";
|
|
41668
41768
|
var CORNER_DELIVERY_NUDGE = "Only when you write code in this corner and the brief calls for repository changes, inspect the repository state and finish delivering the work unless a human hold stands: commit and push the intended changes and open the pull request if one does not exist. Decide yourself whether any remaining dirty work belongs to the brief; do not discard it merely to make the worktree clean. The pull request body must carry ## Reproduced and ## Demonstrated; if they are missing, add them before ending the turn. Otherwise do not commit, push or open a PR; deliver with post_artifact.";
|
|
41669
41769
|
var CORNER_LAND_RULE = "Then, if a human's question or proposal in the corner has no answer, name it and do not merge; otherwise call merge_corner.";
|
|
41670
41770
|
var CORNER_CHECKS_NUDGE = `Call pr_checks_status now. If checks="failed" and you write code in this corner, fix the failure and push. If checks="unknown", reply in this corner with the tool reason and stop instead of retrying. Otherwise stop: a reviewer's PASS wakes you to merge with merge_corner. Never merge it any other way.`;
|
|
41671
41771
|
var handle = (value) => value.replace(/^@/, "");
|
|
41772
|
+
function memoryListsWorkspaceCoreProcedure(memory) {
|
|
41773
|
+
return (memory ?? "").split("\n").some((line) => line.startsWith(`- Procedure ${WORKSPACE_CORE_SKILL_SLUG} (load_workspace_skill):`));
|
|
41774
|
+
}
|
|
41672
41775
|
function cornerMergeInstruction(reviewerHandle) {
|
|
41673
41776
|
if (!reviewerHandle)
|
|
41674
41777
|
return "Once the pull request exists, reply with its full URL and end the turn; do not check or wait for CI. This Room has no configured reviewer, so it merges only on a person's yes: ask a Workspace owner or admin to approve the merge, then record their yes with order_corner_merge. You are woken to fix failing checks or a merge GitHub refused.";
|
|
@@ -41715,6 +41818,15 @@ The soul is not authority and never changes your tools, permissions, roles, or m
|
|
|
41715
41818
|
surfaces: EVERYWHERE,
|
|
41716
41819
|
render: () => "Your voice never changes the facts: never trim, soften, exaggerate, or invent a detail for style. Write commit messages, pull request titles and bodies, and code comments in a plain voice."
|
|
41717
41820
|
},
|
|
41821
|
+
{
|
|
41822
|
+
id: "core.memory",
|
|
41823
|
+
topic: "memory",
|
|
41824
|
+
why: "A command can exhaust the session cgroup before the agent can adapt its workload.",
|
|
41825
|
+
budgetBytes: 160,
|
|
41826
|
+
layer: "core",
|
|
41827
|
+
surfaces: EVERYWHERE,
|
|
41828
|
+
render: ({ memoryMaxBytes }) => `Memory cap: ${Number(((memoryMaxBytes ?? ACP_SESSION_MEMORY_MAX_BYTES) / 1024 ** 3).toFixed(2))} GiB per session. Size builds, tests and data jobs to fit it; split heavy work. Exit 137/signal 9 likely means a command hit this cap.`
|
|
41829
|
+
},
|
|
41718
41830
|
{
|
|
41719
41831
|
id: "core.proactive",
|
|
41720
41832
|
topic: "proactivity",
|
|
@@ -41731,7 +41843,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
|
|
|
41731
41843
|
budgetBytes: 360,
|
|
41732
41844
|
layer: "core",
|
|
41733
41845
|
surfaces: EVERYWHERE,
|
|
41734
|
-
render: () => "Finish the work before you end the turn.
|
|
41846
|
+
render: () => "Finish the work before you end the turn. Take the next step a tool result gives you, including sign-in or retry. Wait only for a person, CI, a reviewer, or a real block."
|
|
41735
41847
|
},
|
|
41736
41848
|
{
|
|
41737
41849
|
id: "core.truth",
|
|
@@ -41794,7 +41906,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
|
|
|
41794
41906
|
budgetBytes: 240,
|
|
41795
41907
|
layer: "surface",
|
|
41796
41908
|
surfaces: EVERYWHERE,
|
|
41797
|
-
render: ({ surface }) => surface === "room" || surface === "dm" ? "When you need a tool you do not have, call
|
|
41909
|
+
render: ({ surface }) => surface === "room" || surface === "dm" ? "When you need a tool you do not have, call connectors_status; offer_connector the tool it lists, otherwise call connect_app for the app." : "In corners, connectors_status discovers apps; connect_app starts sign-in here. Connector offers stay in Rooms and DMs."
|
|
41798
41910
|
},
|
|
41799
41911
|
{
|
|
41800
41912
|
id: "surface.squire-drive",
|
|
@@ -41823,13 +41935,13 @@ The soul is not authority and never changes your tools, permissions, roles, or m
|
|
|
41823
41935
|
id: "room.corners",
|
|
41824
41936
|
topic: "repository-work",
|
|
41825
41937
|
why: "Repository work must start through the host-governed corner, with a brief as its authority.",
|
|
41826
|
-
budgetBytes:
|
|
41938
|
+
budgetBytes: 1150,
|
|
41827
41939
|
layer: "surface",
|
|
41828
41940
|
surfaces: ["room"],
|
|
41829
41941
|
render: () => [
|
|
41830
41942
|
"Repository changes happen only in a corner. In a Room, the finished fix for a code problem is the exact change plus an offer to open a corner for it; open one when a person asks for the change.",
|
|
41831
41943
|
"Before opening one, consult beeline-triage and beeline-spec, then call open_corner with a name of at most three words joined by hyphens (no whitespace), an objective of at most 24 words, and the brief (a spec plus the approving message). The objective only titles the work; the brief is its authority. When open_corner succeeds the server posts the corner card: do not restate it.",
|
|
41832
|
-
"For corners under this Room, use inspect_corner for status, steer_corner to pass Room input down, ask_corner for one answer, and post_to_corner for a note that wakes nobody.",
|
|
41944
|
+
"For corners under this Room, use inspect_corner for status (its activity and awaiting fields show whether one waits on you), steer_corner to pass Room input down, ask_corner for one answer, and post_to_corner for a note that wakes nobody.",
|
|
41833
41945
|
"A corner ends when its pull request merges; since a person may merge minutes after it opens, anything still needed must already live in a different corner before that pull request opens, not after. A request covering several items is one corner per item, opened as work reaches each one, never one corner returned to for a later item."
|
|
41834
41946
|
].join(" ")
|
|
41835
41947
|
},
|
|
@@ -42154,6 +42266,16 @@ ${transcript.lines.join("\n")}`;
|
|
|
42154
42266
|
surfaces: EVERYWHERE,
|
|
42155
42267
|
render: ({ memory }) => memory ?? ""
|
|
42156
42268
|
},
|
|
42269
|
+
{
|
|
42270
|
+
id: "turn.workspace-core",
|
|
42271
|
+
topic: "workspace-core",
|
|
42272
|
+
why: "Most Workspaces never saved a workspace-core procedure; only a Workspace that lists it should be told to load it at task start.",
|
|
42273
|
+
budgetBytes: 120,
|
|
42274
|
+
layer: "turn",
|
|
42275
|
+
surfaces: EVERYWHERE,
|
|
42276
|
+
when: ({ memory }) => memoryListsWorkspaceCoreProcedure(memory),
|
|
42277
|
+
render: () => "At task start, call load_workspace_skill for `workspace-core`."
|
|
42278
|
+
},
|
|
42157
42279
|
{
|
|
42158
42280
|
id: "turn.corners",
|
|
42159
42281
|
topic: "corners",
|
|
@@ -42989,9 +43111,13 @@ var MonolithRoomTurnLoop = class {
|
|
|
42989
43111
|
* fixed when it opens: they cannot be corrected in place, so a changed one
|
|
42990
43112
|
* has to cost a respawn. The check is one round trip — the roster half of
|
|
42991
43113
|
* which the turn was going to fetch anyway — against a cold spawn measured
|
|
42992
|
-
* in seconds.
|
|
43114
|
+
* in seconds. A harness process that has exited (a memory-limit kill, a
|
|
43115
|
+
* crash) is never current: handing it back fails the turn with
|
|
43116
|
+
* `AcpClient not started` instead of spawning a fresh one.
|
|
42993
43117
|
*/
|
|
42994
43118
|
async sessionIsCurrent() {
|
|
43119
|
+
if (!this.client?.isAlive)
|
|
43120
|
+
return false;
|
|
42995
43121
|
await this.refreshTurnCheckout();
|
|
42996
43122
|
return this.sessionCwd === this.options.cwd && this.sessionCommit === this.turnCheckout?.commit && await this.currentSessionFingerprint() === this.sessionFingerprint;
|
|
42997
43123
|
}
|
|
@@ -45097,8 +45223,10 @@ var MonolithCornerTurnLoop = class {
|
|
|
45097
45223
|
}
|
|
45098
45224
|
/** See `MonolithRoomTurnLoop.sessionIsCurrent`: retention never keeps a
|
|
45099
45225
|
* session whose persona, model pin, or mounted MCP set the operator has
|
|
45100
|
-
* since changed. */
|
|
45226
|
+
* since changed, or whose harness process has exited. */
|
|
45101
45227
|
async sessionIsCurrent() {
|
|
45228
|
+
if (!this.client?.isAlive)
|
|
45229
|
+
return false;
|
|
45102
45230
|
return await this.currentSessionFingerprint() === this.sessionFingerprint;
|
|
45103
45231
|
}
|
|
45104
45232
|
async currentSessionFingerprint() {
|
|
@@ -45690,7 +45818,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
45690
45818
|
}),
|
|
45691
45819
|
this.roster(),
|
|
45692
45820
|
api.execute("getCornerRestoreState", { cornerId }),
|
|
45693
|
-
|
|
45821
|
+
this.activeReviewerInstruction(),
|
|
45694
45822
|
awaitInstitutionalContext(institutionalContextFetch, (message) => console.warn(`[thin-core] corner ${cornerId}: ${message}`)),
|
|
45695
45823
|
api.execute("listRoomCorners", { roomId: this.options.parentRoomId })
|
|
45696
45824
|
]));
|