usebeeline 0.1.18 → 0.1.19

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.
Files changed (2) hide show
  1. package/dist/usebeeline.mjs +80 -26
  2. package/package.json +1 -1
@@ -16730,26 +16730,26 @@ function staleLeaseResult(value) {
16730
16730
  return false;
16731
16731
  }
16732
16732
  }
16733
- function resultStatus(value, depth = 0) {
16733
+ function resultString(value, key, depth = 0) {
16734
16734
  if (depth > 8 || !value)
16735
16735
  return void 0;
16736
16736
  if (typeof value === "string") {
16737
16737
  if (value.length > 1e5 || !value.trimStart().startsWith("{"))
16738
16738
  return void 0;
16739
16739
  try {
16740
- return resultStatus(JSON.parse(value), depth + 1);
16740
+ return resultString(JSON.parse(value), key, depth + 1);
16741
16741
  } catch {
16742
16742
  return void 0;
16743
16743
  }
16744
16744
  }
16745
16745
  if (Array.isArray(value))
16746
- return value.map((item2) => resultStatus(item2, depth + 1)).find(Boolean);
16746
+ return value.map((item2) => resultString(item2, key, depth + 1)).find(Boolean);
16747
16747
  if (typeof value !== "object")
16748
16748
  return void 0;
16749
16749
  const item = value;
16750
- if (typeof item.status === "string")
16751
- return item.status;
16752
- return Object.values(item).map((entry) => resultStatus(entry, depth + 1)).find(Boolean);
16750
+ if (typeof item[key] === "string")
16751
+ return item[key];
16752
+ return Object.values(item).map((entry) => resultString(entry, key, depth + 1)).find(Boolean);
16753
16753
  }
16754
16754
  var SquireTaskRelay = class {
16755
16755
  agentId;
@@ -17040,21 +17040,29 @@ var SquireTaskRelay = class {
17040
17040
  if (approval) {
17041
17041
  const ids = requestedIds.length ? requestedIds : [...task.sessions];
17042
17042
  const approvalKey = createHash("sha256").update(approval.approvalId ?? approval.approvalUrl).digest("hex");
17043
- const status = resultStatus(result);
17043
+ const status = resultString(result, "status");
17044
17044
  const pending = status === "approval_pending" || status === "pending_approval";
17045
- if (pending && ids.length) {
17046
- task.pendingApprovals.set(approvalKey, {
17045
+ if (pending && (ids.length || approval.approvalId)) {
17046
+ clearTimeout(task.pendingApprovals.get(approvalKey)?.expiry);
17047
+ const entry = {
17047
17048
  requestId: active.requestId,
17048
17049
  sessionIds: new Set(ids),
17049
17050
  tool: approval.tool,
17050
17051
  title: approval.title,
17051
17052
  detail: approval.detail
17052
- });
17053
+ };
17054
+ const expiresAt = Date.parse(resultString(result, "expires_at") ?? "");
17055
+ if (approval.approvalId && Number.isFinite(expiresAt)) {
17056
+ entry.expiry = setTimeout(() => this.expireApproval(task, approvalKey, entry, approval.approvalId), Math.min(Math.max(expiresAt - Date.now(), 0), 2 ** 31 - 1));
17057
+ entry.expiry.unref?.();
17058
+ }
17059
+ task.pendingApprovals.set(approvalKey, entry);
17053
17060
  this.log("approval-pending", task, {
17054
17061
  tool: safeToolName ?? null,
17055
17062
  sessionId: redactedSessionId(ids[0])
17056
17063
  });
17057
17064
  } else if (!pending) {
17065
+ clearTimeout(task.pendingApprovals.get(approvalKey)?.expiry);
17058
17066
  task.pendingApprovals.delete(approvalKey);
17059
17067
  }
17060
17068
  }
@@ -17083,12 +17091,35 @@ var SquireTaskRelay = class {
17083
17091
  }
17084
17092
  releasePendingApprovals(task, sessionIdsToRelease) {
17085
17093
  for (const [id, approval] of task.pendingApprovals) {
17094
+ if (!approval.sessionIds.size)
17095
+ continue;
17086
17096
  for (const sessionId of sessionIdsToRelease)
17087
17097
  approval.sessionIds.delete(sessionId);
17088
- if (!approval.sessionIds.size)
17098
+ if (!approval.sessionIds.size) {
17099
+ clearTimeout(approval.expiry);
17089
17100
  task.pendingApprovals.delete(id);
17101
+ }
17090
17102
  }
17091
17103
  }
17104
+ /**
17105
+ * Squire stops watching an approval at its `expires_at` and sends nothing,
17106
+ * so the relay wakes the asking turn itself with `expired`.
17107
+ */
17108
+ expireApproval(task, approvalKey, entry, approvalId) {
17109
+ if (task.pendingApprovals.get(approvalKey) !== entry)
17110
+ return;
17111
+ task.pendingApprovals.delete(approvalKey);
17112
+ this.onApprovalDecided({
17113
+ requestId: entry.requestId,
17114
+ approvalId,
17115
+ status: "expired",
17116
+ tool: entry.tool,
17117
+ title: entry.title,
17118
+ detail: entry.detail
17119
+ });
17120
+ if (!this.active && !this.approvalRequestId && !task.pendingApprovals.size)
17121
+ this.scheduleIdle();
17122
+ }
17092
17123
  /**
17093
17124
  * Squire's own unsolicited decision on an approval this task is still
17094
17125
  * holding a connection open for. Matched back to the pending entry by the
@@ -17108,6 +17139,7 @@ var SquireTaskRelay = class {
17108
17139
  const pending = task.pendingApprovals.get(approvalKey);
17109
17140
  if (!pending)
17110
17141
  return;
17142
+ clearTimeout(pending.expiry);
17111
17143
  task.pendingApprovals.delete(approvalKey);
17112
17144
  this.onApprovalDecided({
17113
17145
  requestId: pending.requestId,
@@ -17990,7 +18022,7 @@ var SERVER_EVENT_KIND_DETAIL = {
17990
18022
  "check-failed": "fires when a corner's pull request checks turn red",
17991
18023
  "merged": "fires when a corner's pull request merges",
17992
18024
  "grant-decided": "carries the grant id and status and resumes the turn that asked for the grant",
17993
- "squire-approval-decided": "carries what was approved or denied and the Squire approval id, and resumes the turn that asked",
18025
+ "squire-approval-decided": "carries what was approved, denied, or left to expire and the Squire approval id, and resumes the turn that asked",
17994
18026
  "connector-offer-decided": "fires when a human accepts or declines an offered connector, or a reconnect ceremony finishes, and resumes the turn that offered it",
17995
18027
  "turn-cancelled": "fires when a running turn is stopped",
17996
18028
  "choice-answered": "fires when a human answers a posted choice, starting a new turn for the asking agent",
@@ -32306,7 +32338,7 @@ Stage | Recorder | Server checks
32306
32338
  intent, base, tests, docs, lint_types, publication, final_authorization | implementer | current revision/head
32307
32339
  review | configured reviewer records the review stage | current revision/head and reviewer identity
32308
32340
  ci | implementer | current revision/head; passed requires passing checks
32309
- The server merge gate is the authority: pr_checks_status reports mergeAllowed true for the current head; the server then merges that head. final_authorization grants nothing; do not wait for mergeAllowed.`;
32341
+ The server merge gate is the authority: pr_checks_status reports mergeAllowed true for the current head; merge_corner merges that head. final_authorization grants nothing; do not wait for mergeAllowed.`;
32310
32342
  function cornerHasRepositoryWork(brief) {
32311
32343
  if (!brief)
32312
32344
  return true;
@@ -32355,7 +32387,8 @@ No unrequested features, flags, compatibility shims or refactors.`;
32355
32387
  var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. The active turn prompt names the latest stable green PR head. Review and approve only that exact head; if no stable green head is named, end the turn without a verdict. Never merge yourself.";
32356
32388
  var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "There is no stable green PR head for this active reviewer turn. Do not review or call approve_merge. End this turn without a verdict; the next green transition will wake you.";
32357
32389
  var CORNER_DELIVERY_NUDGE = "Only when 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.";
32358
- var CORNER_YOLO_MERGE_NUDGE = 'Yolo is on. Call pr_checks_status now. If checks="failed", fix the failure and push. If checks="unknown", reply in this corner with the tool reason and stop instead of retrying. Otherwise stop: the server merges this pull request itself once mergeAllowed=true. Never merge it yourself.';
32390
+ 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.";
32391
+ var CORNER_YOLO_MERGE_NUDGE = `Yolo is on. Call pr_checks_status now. If checks="failed", 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.`;
32359
32392
  var UPGRADE_INTENT_RULE = "Call this on your own judgment when the work in this corner needs the repository; nobody has to ask. Call it while answering a human message in this corner, then write the brief in the restarted code session.";
32360
32393
  var CORNER_PLACEHOLDER_BRIEF_RULE = "This brief is the placeholder the upgrade wrote from the request. Read the code, write the real spec with revise_corner_brief, then post it in this corner and ask the corner's opener to approve it before editing. This is best effort: if the opener is away or the request was already explicit, use your judgment and proceed. When the opener replies, record their reply as the approval in the next revision.";
32361
32394
  function isPlaceholderCornerBrief(brief) {
@@ -32366,7 +32399,7 @@ function cornerMergeInstruction(yoloMode, reviewerHandle) {
32366
32399
  if (!reviewerHandle)
32367
32400
  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 the merge gate never opens for you: never merge; a person merges it.";
32368
32401
  const reviewer = `the reviewer (${handle(reviewerHandle)})`;
32369
- return yoloMode ? `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Green checks wake ${reviewer}; after its PASS the server merges the pull request itself. Never merge it yourself. You are woken only to fix failing checks, requested changes, or a merge GitHub refused; push the fix, and the new head needs green checks and a fresh PASS.` : `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Yolo is off, so the merge gate stays closed for you: never merge; a person merges it after ${reviewer} approves.`;
32402
+ return yoloMode ? `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Green checks wake ${reviewer}; its PASS wakes you to merge. ${CORNER_LAND_RULE} You are also woken to fix failing checks, requested changes, or a refused merge; a fixed head needs green checks and a fresh PASS.` : `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Yolo is off, so the merge gate stays closed for you: never merge; a person merges it after ${reviewer} approves.`;
32370
32403
  }
32371
32404
  function cornerReviewerInstruction(input) {
32372
32405
  if (!input.isReviewer || input.openedByAgent)
@@ -32374,12 +32407,12 @@ function cornerReviewerInstruction(input) {
32374
32407
  const author = input.authorHandle ? handle(input.authorHandle) : "author";
32375
32408
  const number = input.pullRequestNumber ?? "N";
32376
32409
  const headSha = input.headSha ?? "<head sha>";
32377
- return `Checks are green on PR #${number} at ${headSha}${input.briefRevision ? ` with assigned brief revision ${input.briefRevision}` : ""}. Review it now with the beeline-review skill against that exact head and current assigned brief. FAIL: call record_validation_stage with stage "review", status "failed", and headSha ${headSha}, then reply \`@${author}\` with the confirmed findings and the reviewed head ${headSha} to fix. PASS: call the approve_merge tool for ${headSha}${input.briefRevision ? ` with briefRevision=${input.briefRevision}` : ""}, then reply \`approved ${headSha}\` without tagging ${author}: the server merges it, and a tag would wake the author for nothing. Never merge yourself or tell the author to merge. Never say you are holding or waiting for checks.`;
32410
+ return `Checks are green on PR #${number} at ${headSha}${input.briefRevision ? ` with assigned brief revision ${input.briefRevision}` : ""}. Review it now with the beeline-review skill against that exact head and current assigned brief. FAIL: call record_validation_stage with stage "review", status "failed", and headSha ${headSha}, then reply \`@${author}\` with the confirmed findings and the reviewed head ${headSha} to fix. PASS: call the approve_merge tool for ${headSha}${input.briefRevision ? ` with briefRevision=${input.briefRevision}` : ""}, then reply \`approved ${headSha}\` without tagging ${author}: your PASS wakes the author to merge, so a tag adds nothing. A human's question or proposal in the corner discussion with no answer is a FAIL finding; name it. Never merge yourself or tell the author to merge. Never say you are holding or waiting for checks.`;
32378
32411
  }
32379
32412
  function cornerSelfReviewerInstruction(input) {
32380
32413
  if (!input.isReviewer || !input.openedByAgent)
32381
32414
  return void 0;
32382
- return "Once the pull request exists, reply with its full URL and end the turn. You are this Room's reviewer, so your own pull request needs no review: do not request one or tag any agent for review. The server merges it once checks are green, yolo is on, and no human hold stands; never merge it yourself. You are woken only to fix failing checks or a merge GitHub refused.";
32415
+ return "Once the pull request exists, reply with its full URL and end the turn. You are this Room's reviewer, so no other agent can review your pull request: do not request one or tag any agent for review. It merges only on a human's yes: ask a Workspace owner or admin to approve the merge, then record their yes with order_corner_merge. Never merge it any other way. You are woken to fix failing checks or a merge GitHub refused.";
32383
32416
  }
32384
32417
  function shellLine(shell) {
32385
32418
  if (!shell)
@@ -32730,6 +32763,16 @@ var TURN_SECTIONS = [
32730
32763
  when: ({ task }) => task.commandReason !== "schedule" && (["corner_check", "corner_merge_refused", "corner_merge_conflict"].includes(task.commandReason ?? "") || ["check-passed", "check-failed"].includes(task.outsideEvent?.kind ?? "")),
32731
32764
  render: () => 'This is a checks turn about CI or the merge gate. On it, say nothing unless you push a fix or report checks="unknown", and then use one short line. Never restate server check or merge notes.'
32732
32765
  },
32766
+ {
32767
+ id: "turn.land",
32768
+ topic: "land-turn",
32769
+ why: "A reviewer's PASS hands the merge to the implementer, who must not merge over an unanswered human question.",
32770
+ budgetBytes: 300,
32771
+ layer: "turn",
32772
+ surfaces: ["code-corner"],
32773
+ when: ({ task }) => task.commandReason === "corner_land",
32774
+ render: () => `The reviewer approved this pull request's current head. ${CORNER_LAND_RULE}`
32775
+ },
32733
32776
  {
32734
32777
  id: "turn.objective",
32735
32778
  topic: "objective",
@@ -32951,7 +32994,7 @@ Keys belong to the human who provisioned them. Your owner's keys serve whoever a
32951
32994
 
32952
32995
  ## Connect an app
32953
32996
 
32954
- When a request needs an app or service, call beeline-agent \`workbench_status\` first. For a connected app, use its ID with \`list_app_tools\`, then call \`execute_app_tool\` with the selected tool slug and arguments. 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.
32997
+ When a request needs an app or service, call beeline-agent \`workbench_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.
32955
32998
 
32956
32999
  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. If the provider reports managed OAuth unsupported, follow the server's Squire route and its instructions. A provider outage is unavailable, not permission to change routes. After the person connects, read Workbench again and use the app ID. Every use is authorized as \`app:<key>\` and recorded once.
32957
33000
 
@@ -33075,7 +33118,8 @@ Then take exactly one action:
33075
33118
 
33076
33119
  - FAIL: call \`record_validation_stage\` with stage \`review\`, status \`failed\`, and the reviewed head SHA, then reply \`@author\` with the confirmed findings and that reviewed head SHA to fix. The recorded review stage is how an implementer who has already fixed the findings can tell whether the review is stale.
33077
33120
  - PASS: call \`approve_merge\` with the reviewed head SHA and assigned briefRevision (omit the revision only for a legacy corner without a brief), then reply \`approved <reviewed sha>\` without tagging the author. Do not tell the author to merge.
33078
- - Approving is your last step as reviewer. The server squash-merges that exact head once checks are green, the worker's yolo is on, and no human hold stands. Neither you nor the author merges it.
33121
+ - A human's question or proposal in the corner discussion with no answer is a blocking finding: FAIL and name it.
33122
+ - Approving is your last step as reviewer. Your PASS wakes the author, who merges that exact head with \`merge_corner\` once checks are green, the worker's yolo is on, and no human hold stands. You never merge it yourself.
33079
33123
  `;
33080
33124
  }
33081
33125
  function beelineSpecSkillMarkdown(releaseId) {
@@ -42375,7 +42419,8 @@ function resumePrompt(item) {
42375
42419
  `This is Trusty Squire's answer to the approval you were waiting on: ${item.body}.`,
42376
42420
  "Your paused work resumes now.",
42377
42421
  "If it was approved, continue exactly where you left off with Trusty Squire; do not ask the person to approve again, and do not restart the task.",
42378
- "If it was denied, stop that Trusty Squire action and say plainly what you cannot do."
42422
+ "If it was denied, stop that Trusty Squire action and say plainly what you cannot do.",
42423
+ "If it got no answer, the approval expired; say so plainly, and ask again only if the person still wants it."
42379
42424
  ].join(" ");
42380
42425
  }
42381
42426
  if (item.systemEvent?.kind === "connector-offer-decided") {
@@ -43695,14 +43740,17 @@ function cornerGitHubCommandRefusal(launcher, argv, featureBranch, targetBranch,
43695
43740
  return { refused: true };
43696
43741
  const separator = raw.indexOf(":");
43697
43742
  const source = separator >= 0 ? raw.slice(0, separator) : raw;
43698
- const destination = separator >= 0 ? raw.slice(separator + 1) : source;
43743
+ const explicitDestination = separator >= 0 ? raw.slice(separator + 1) : void 0;
43699
43744
  if (!source || source === ":" || /(?:^|\/)refs\/tags\//.test(source))
43700
43745
  return { refused: true };
43701
- const resolved = resolvePushBranch(source === "HEAD" ? void 0 : source);
43746
+ const resolved = resolvePushBranch(source);
43702
43747
  if (resolved.tag)
43703
43748
  return { refused: true };
43704
- const branch = destination ? branchName(destination) : resolved.branch ? branchName(resolved.branch) : void 0;
43705
- return { branch, refused: destination.startsWith("refs/tags/") };
43749
+ const branch = explicitDestination ? branchName(explicitDestination) : resolved.branch ? branchName(resolved.branch) : void 0;
43750
+ return {
43751
+ branch,
43752
+ refused: explicitDestination !== void 0 && explicitDestination.startsWith("refs/tags/")
43753
+ };
43706
43754
  }) : [resolvePushBranch()];
43707
43755
  if (destinations.some((destination) => "refused" in destination && destination.refused === true || !destination.branch || branchName(destination.branch) !== featureBranch || branchName(destination.branch) === targetBranch))
43708
43756
  return refusal;
@@ -43731,6 +43779,12 @@ const gitContext = (() => {
43731
43779
  })();
43732
43780
  function git(args, options) { return spawnSync(config.gitCommand, gitContext.concat(args), options); }
43733
43781
  function resolvePushBranch(source) {
43782
+ if (source === 'HEAD') {
43783
+ // git push origin HEAD targets the branch checked out now; read it
43784
+ // directly and fail closed in a detached HEAD.
43785
+ const current = git(['symbolic-ref', '--short', 'HEAD'], { encoding: 'utf8' });
43786
+ return current.status === 0 ? { branch: current.stdout.trim() } : {};
43787
+ }
43734
43788
  if (source) {
43735
43789
  const tag = git(['show-ref', '--verify', '--quiet', 'refs/tags/' + source], { stdio: 'ignore' }).status === 0;
43736
43790
  return { branch: source, tag };
@@ -44462,7 +44516,7 @@ function filePaths(value, worktreePath) {
44462
44516
  visit(value);
44463
44517
  return [...paths];
44464
44518
  }
44465
- function resultStatus2(call) {
44519
+ function resultStatus(call) {
44466
44520
  const content = record4(call.content) ?? (typeof call.content === "string" ? (() => {
44467
44521
  try {
44468
44522
  return record4(JSON.parse(call.content));
@@ -44523,7 +44577,7 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
44523
44577
  kind: "tool",
44524
44578
  title: title.slice(0, 240),
44525
44579
  operation: operation.slice(0, 80),
44526
- status: resultStatus2(call),
44580
+ status: resultStatus(call),
44527
44581
  ...argumentsSummary,
44528
44582
  ...output ? { output } : {},
44529
44583
  ...requestedBy ? { requestedBy } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {