@sideboard-ai/core 0.1.288 → 0.1.290

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 (35) hide show
  1. package/dist/{agents-GZLTMGSH.js → agents-4BBCKITL.js} +4 -4
  2. package/dist/{agents-UQMHFZSM.js → agents-XAKSP653.js} +4 -4
  3. package/dist/{chunk-HNB2HTCV.js → chunk-2IXKTAIT.js} +1 -1
  4. package/dist/{chunk-442O6OXM.js → chunk-4VXWLY3L.js} +13 -12
  5. package/dist/{chunk-3W5SL5EW.js → chunk-5HPMKLJ7.js} +1 -1
  6. package/dist/{chunk-4NM72HJZ.js → chunk-G74TSBJ3.js} +4 -4
  7. package/dist/{chunk-WQSIUZEB.js → chunk-GBAACE3C.js} +4 -4
  8. package/dist/{chunk-2N77CLRQ.js → chunk-LUQ7MIVD.js} +3 -3
  9. package/dist/{chunk-IA4MPG7F.js → chunk-O4324MXE.js} +2 -2
  10. package/dist/{chunk-WW5XGEMY.js → chunk-QOUTSISS.js} +14 -13
  11. package/dist/{chunk-DBOFDEQ7.js → chunk-V3BSO2O6.js} +2 -2
  12. package/dist/{chunk-6NVQNTJO.js → chunk-VNCPRMIK.js} +3 -3
  13. package/dist/{conductor-MCU63Z5U.js → conductor-5MNGC25D.js} +4 -4
  14. package/dist/{conductor-ZLYZVIAV.js → conductor-UFJXJPTF.js} +4 -4
  15. package/dist/{coordinator-prompt-KHCXXDRO.js → coordinator-prompt-5Q66Y5KW.js} +3 -3
  16. package/dist/{coordinator-prompt-UV6THLMD.js → coordinator-prompt-BYPEW6DE.js} +3 -3
  17. package/dist/{global-workspace-GDHZSFP6.js → global-workspace-BTH2YGDZ.js} +3 -3
  18. package/dist/{global-workspace-AXTSYVAE.js → global-workspace-YKOYXSB2.js} +3 -3
  19. package/dist/index.cjs +695 -145
  20. package/dist/index.d.cts +18 -2
  21. package/dist/index.d.ts +18 -2
  22. package/dist/index.js +641 -102
  23. package/dist/mcp/run-stdio.cjs +2 -1
  24. package/dist/mcp/run-stdio.js +6 -6
  25. package/dist/{orchestrator-5ZBHR6BJ.js → orchestrator-EQSK2RMI.js} +6 -6
  26. package/dist/{orchestrator-SDH6W6ZL.js → orchestrator-GSOW3RPA.js} +6 -6
  27. package/dist/{thread-store-7XBOUQ5K.js → thread-store-DPKESDFR.js} +1 -1
  28. package/dist/{thread-store-ZQZDREVH.js → thread-store-X6S2GIR5.js} +1 -1
  29. package/dist/{workspaces-Q2R2XGO3.js → workspaces-ELO2YVTU.js} +3 -3
  30. package/dist/{workspaces-L2UR4KC2.js → workspaces-RFD6I4FM.js} +3 -3
  31. package/dist/{worktree-2GOK4VYT.js → worktree-ADVQ6X6C.js} +2 -2
  32. package/dist/{worktree-YEUTJ7MZ.js → worktree-TN7DZDJZ.js} +2 -2
  33. package/package.json +1 -1
  34. /package/dist/{chunk-N5UGX3JL.js → chunk-QB5MDOHL.js} +0 -0
  35. /package/dist/{chunk-JPH7PYSK.js → chunk-YRXV3E7J.js} +0 -0
package/dist/index.js CHANGED
@@ -186,7 +186,7 @@ import {
186
186
  worktreeBoardStatus,
187
187
  worktreeCleanupSettings,
188
188
  writeWorktreeFile
189
- } from "./chunk-WW5XGEMY.js";
189
+ } from "./chunk-QOUTSISS.js";
190
190
  import {
191
191
  LEGACY_PLAN_FILE_REL,
192
192
  PLAN_FILE_NAME,
@@ -281,7 +281,7 @@ import {
281
281
  userMcpNamesToDisable,
282
282
  worktreeMcpToolNames,
283
283
  writeInjectedMcpConfig
284
- } from "./chunk-6NVQNTJO.js";
284
+ } from "./chunk-VNCPRMIK.js";
285
285
  import {
286
286
  brightsyMcpServerName,
287
287
  connectBrightsyTeam,
@@ -408,7 +408,7 @@ import {
408
408
  workspaceAgentChatSummaries,
409
409
  worktreeRunPortsPath,
410
410
  wrapReviewSkillMarkdown
411
- } from "./chunk-WQSIUZEB.js";
411
+ } from "./chunk-GBAACE3C.js";
412
412
  import {
413
413
  STDERR_EVENT_MAX_CHARS,
414
414
  STDOUT_EVENT_MAX_CHARS,
@@ -608,7 +608,7 @@ import {
608
608
  stageBuffersAsAttachments,
609
609
  syncWorkspacesFromThreads,
610
610
  takenTeamSlugsForOrchestration
611
- } from "./chunk-DBOFDEQ7.js";
611
+ } from "./chunk-V3BSO2O6.js";
612
612
  import {
613
613
  GITHUB_PR_BODY_MAX_CHARS,
614
614
  GITHUB_PR_BODY_SAFE_CHARS,
@@ -696,7 +696,7 @@ import {
696
696
  suggestSlug,
697
697
  switchWorktreeBranch,
698
698
  warmGithubAgentAuth
699
- } from "./chunk-HNB2HTCV.js";
699
+ } from "./chunk-2IXKTAIT.js";
700
700
  import {
701
701
  FAMOUS_SOCCER_TEAMS,
702
702
  allocateTeamName,
@@ -728,7 +728,7 @@ import {
728
728
  worktreeNameFromPath,
729
729
  worktreeSlugForTicket,
730
730
  writeThread
731
- } from "./chunk-N5UGX3JL.js";
731
+ } from "./chunk-QB5MDOHL.js";
732
732
  import "./chunk-2P4XKWV2.js";
733
733
  import {
734
734
  BRIGHTSY_OAUTH_CLIENT_ID,
@@ -5126,7 +5126,7 @@ async function handleJobStop(params, _ctx) {
5126
5126
  return stopDetachedJob(jobCwd(rec), id, reason ? { reason } : void 0);
5127
5127
  }
5128
5128
  async function defaultTurnWait(ref, timeoutMs) {
5129
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-SDH6W6ZL.js");
5129
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-GSOW3RPA.js");
5130
5130
  const orch = getOrchestrator2();
5131
5131
  const thread = await orch.waitForTurn(ref, timeoutMs, {
5132
5132
  resolveIfStillRunning: true
@@ -5439,15 +5439,15 @@ async function getHomeBoardInputs(workspaces, opts) {
5439
5439
  }
5440
5440
  const promise = (async () => {
5441
5441
  const inputs = await loadHomeBoardInputs(workspaces);
5442
- const fetchedAt = opts?.now ?? Date.now();
5442
+ const fetchedAt2 = opts?.now ?? Date.now();
5443
5443
  const pins = syncBoardPins(listBoardPins(), inputs.issues, inputs.prs);
5444
5444
  replaceBoardPins(pins);
5445
- const loaded = { ...inputs, fetchedAt, fromCache: false, pins };
5445
+ const loaded = { ...inputs, fetchedAt: fetchedAt2, fromCache: false, pins };
5446
5446
  if (shouldCacheHomeBoardInputs(inputs)) {
5447
5447
  const entry = {
5448
5448
  version: CACHE_VERSION,
5449
5449
  workspaceKey: key,
5450
- fetchedAt,
5450
+ fetchedAt: fetchedAt2,
5451
5451
  inputs
5452
5452
  };
5453
5453
  memory = entry;
@@ -9165,14 +9165,46 @@ var RemoteHub = class {
9165
9165
  this.codes.delete(code);
9166
9166
  this.attachPhone(socket, host);
9167
9167
  }
9168
+ /** In-memory session, with the live Mac's label when that socket is up. */
9169
+ memorySession(sessionToken) {
9170
+ const cached2 = this.phonesByToken.get(sessionToken);
9171
+ if (!cached2) return null;
9172
+ const live = this.hosts.get(cached2.deviceId);
9173
+ const remembered = this.remembered.get(cached2.deviceId);
9174
+ const current = live?.sessionToken ?? remembered?.sessionToken ?? null;
9175
+ if (current && current !== sessionToken) {
9176
+ this.phonesByToken.delete(sessionToken);
9177
+ return null;
9178
+ }
9179
+ return {
9180
+ deviceId: cached2.deviceId,
9181
+ deviceLabel: live?.deviceLabel || remembered?.deviceLabel || cached2.deviceLabel
9182
+ };
9183
+ }
9184
+ knownSession(sessionToken) {
9185
+ const cached2 = this.memorySession(sessionToken);
9186
+ if (cached2) return cached2;
9187
+ const stored = this.opts.phoneSessions?.find(sessionToken) ?? null;
9188
+ if (!stored) return null;
9189
+ const live = this.hosts.get(stored.deviceId);
9190
+ const remembered = this.remembered.get(stored.deviceId);
9191
+ const current = live?.sessionToken ?? remembered?.sessionToken ?? null;
9192
+ if (current && current !== sessionToken) return null;
9193
+ this.phonesByToken.set(sessionToken, stored);
9194
+ if (remembered && !remembered.sessionToken) remembered.sessionToken = sessionToken;
9195
+ if (live && !live.sessionToken) live.sessionToken = sessionToken;
9196
+ return {
9197
+ deviceId: stored.deviceId,
9198
+ deviceLabel: live?.deviceLabel || remembered?.deviceLabel || stored.deviceLabel
9199
+ };
9200
+ }
9168
9201
  resumePhone(socket, sessionToken) {
9169
- const deviceId = this.phonesByToken.get(sessionToken);
9170
- const remembered = deviceId ? this.remembered.get(deviceId) : void 0;
9171
- if (!deviceId || !remembered || remembered.sessionToken !== sessionToken) {
9202
+ const known = this.knownSession(sessionToken);
9203
+ if (!known) {
9172
9204
  send2(socket, { type: "error", message: "phone session expired \u2014 pair again" });
9173
9205
  return;
9174
9206
  }
9175
- const host = this.hosts.get(deviceId);
9207
+ const host = this.hosts.get(known.deviceId);
9176
9208
  if (!host) {
9177
9209
  send2(socket, { type: "host_offline" });
9178
9210
  return;
@@ -9181,17 +9213,13 @@ var RemoteHub = class {
9181
9213
  }
9182
9214
  listHosts(socket, sessionTokens) {
9183
9215
  const hosts = sessionTokens.map((sessionToken) => {
9184
- const deviceId = this.phonesByToken.get(sessionToken);
9185
- const remembered = deviceId ? this.remembered.get(deviceId) : void 0;
9186
- const valid = Boolean(deviceId && remembered && remembered.sessionToken === sessionToken);
9187
- if (!valid || !deviceId || !remembered) {
9188
- return { sessionToken, deviceId: "", deviceLabel: "", online: false };
9189
- }
9190
- const host = this.hosts.get(deviceId);
9216
+ const known = this.knownSession(sessionToken);
9217
+ if (!known) return { sessionToken, deviceId: "", deviceLabel: "", online: false };
9218
+ const host = this.hosts.get(known.deviceId);
9191
9219
  return {
9192
9220
  sessionToken,
9193
- deviceId,
9194
- deviceLabel: host?.deviceLabel || remembered.deviceLabel,
9221
+ deviceId: known.deviceId,
9222
+ deviceLabel: host?.deviceLabel || known.deviceLabel,
9195
9223
  online: Boolean(host)
9196
9224
  };
9197
9225
  });
@@ -9222,18 +9250,20 @@ var RemoteHub = class {
9222
9250
  }
9223
9251
  host.phone = null;
9224
9252
  }
9253
+ const created = sessionToken == null;
9225
9254
  const token = sessionToken ?? randomBytes5(24).toString("hex");
9226
9255
  if (host.sessionToken && host.sessionToken !== token) {
9227
9256
  this.phonesByToken.delete(host.sessionToken);
9228
9257
  }
9229
9258
  host.sessionToken = token;
9230
9259
  host.phone = socket;
9231
- this.phonesByToken.set(token, host.deviceId);
9260
+ this.phonesByToken.set(token, { deviceId: host.deviceId, deviceLabel: host.deviceLabel });
9232
9261
  this.remembered.set(host.deviceId, {
9233
9262
  hostSecret: host.hostSecret,
9234
9263
  sessionToken: token,
9235
9264
  deviceLabel: host.deviceLabel
9236
9265
  });
9266
+ if (created) this.opts.phoneSessions?.save(host.deviceId, token);
9237
9267
  this.bySocket.set(socket, { role: "phone", deviceId: host.deviceId });
9238
9268
  this.opts.onAuthorized?.(socket);
9239
9269
  send2(socket, {
@@ -9273,7 +9303,11 @@ var RemoteHub = class {
9273
9303
  import { WebSocket as WsWebSocket3 } from "ws";
9274
9304
 
9275
9305
  // src/remote/phone-media.ts
9306
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
9307
+ import { join as join6 } from "path";
9276
9308
  var PHONE_IMAGE_MAX_BYTES = 15e5;
9309
+ var PHONE_TEXT_MAX_BYTES = 18e4;
9310
+ var PHONE_DIR_MAX = 200;
9277
9311
  var IMAGE_MIME = {
9278
9312
  png: "image/png",
9279
9313
  jpg: "image/jpeg",
@@ -9320,9 +9354,74 @@ function phoneImageDataUrl(worktreePath, raw) {
9320
9354
  return null;
9321
9355
  }
9322
9356
  }
9323
-
9324
- // src/remote/phone-chats.ts
9325
- import { randomUUID as randomUUID7 } from "crypto";
9357
+ function phoneDirectoryListing(worktreePath, relative) {
9358
+ try {
9359
+ const entries = readdirSync2(join6(worktreePath, relative), { withFileTypes: true });
9360
+ const dirs = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
9361
+ const names = entries.map((entry) => entry.name).filter((name) => name && !name.includes("/") && !name.includes("\0")).sort((a, b) => a.localeCompare(b));
9362
+ const shown = names.slice(0, PHONE_DIR_MAX);
9363
+ const lines = shown.map((name) => dirs.has(name) ? `${name}/` : name);
9364
+ const truncated = names.length > shown.length;
9365
+ if (truncated) lines.push(`\u2026 ${names.length - shown.length} more`);
9366
+ return { content: lines.join("\n"), truncated };
9367
+ } catch {
9368
+ return null;
9369
+ }
9370
+ }
9371
+ function phoneFilePreview(worktreePath, relative, kind) {
9372
+ if (kind === "dir") {
9373
+ const listing = phoneDirectoryListing(worktreePath, relative);
9374
+ if (!listing) return null;
9375
+ return {
9376
+ content: listing.content,
9377
+ encoding: "utf8",
9378
+ binary: false,
9379
+ directory: true,
9380
+ truncated: listing.truncated
9381
+ };
9382
+ }
9383
+ if (relative.toLowerCase().endsWith(".svg")) {
9384
+ try {
9385
+ const buf = readFileSync5(join6(worktreePath, relative));
9386
+ const truncated = buf.length > PHONE_TEXT_MAX_BYTES;
9387
+ return {
9388
+ content: buf.subarray(0, PHONE_TEXT_MAX_BYTES).toString("utf8"),
9389
+ encoding: "utf8",
9390
+ binary: false,
9391
+ directory: false,
9392
+ truncated,
9393
+ mime: "image/svg+xml"
9394
+ };
9395
+ } catch {
9396
+ return null;
9397
+ }
9398
+ }
9399
+ const mime = phoneImageMime(relative);
9400
+ if (mime) {
9401
+ const dataUrl = phoneImageDataUrl(worktreePath, relative);
9402
+ if (!dataUrl) return null;
9403
+ return {
9404
+ content: dataUrl,
9405
+ encoding: "base64",
9406
+ binary: true,
9407
+ directory: false,
9408
+ truncated: false,
9409
+ mime
9410
+ };
9411
+ }
9412
+ try {
9413
+ const file = readWorktreeFile(worktreePath, relative, { maxBytes: PHONE_TEXT_MAX_BYTES });
9414
+ return {
9415
+ content: file.content,
9416
+ encoding: file.encoding,
9417
+ binary: file.binary,
9418
+ directory: false,
9419
+ truncated: file.truncated
9420
+ };
9421
+ } catch {
9422
+ return null;
9423
+ }
9424
+ }
9326
9425
 
9327
9426
  // src/remote/phone-artifact.ts
9328
9427
  var FENCE_RE = /```([a-zA-Z0-9_+-]*)[^\n]*\n([\s\S]*?)(?:```|$)/g;
@@ -9459,6 +9558,263 @@ function parseOpenArtifact(value, chatId) {
9459
9558
  const hint = oneLine(str3(record.hint), 200);
9460
9559
  return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
9461
9560
  }
9561
+ var PHONE_ARTIFACT_MAX_CHARS = 18e4;
9562
+ function phoneArtifactDocuments(sources) {
9563
+ const docs = [];
9564
+ for (const source of sources) {
9565
+ const hints = /* @__PURE__ */ new Set();
9566
+ for (const match of source.text.matchAll(new RegExp(FENCE_RE.source, "g"))) {
9567
+ const language = match[1] ?? "";
9568
+ const content = match[2] ?? "";
9569
+ const kind = kindFor(language, content);
9570
+ if (!kind) continue;
9571
+ const hint = artifactHint(content);
9572
+ if (hint) hints.add(hint);
9573
+ docs.push({ title: titleFor(kind, content), content, kind });
9574
+ }
9575
+ for (const part of source.parts ?? []) {
9576
+ if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
9577
+ const input = asRecord2(part.input);
9578
+ const content = toolContent(input);
9579
+ const hint = artifactHint(content);
9580
+ if (hint && hints.has(hint)) continue;
9581
+ const typed = kindFor(str3(input?.type), content) ?? kindFor("", content);
9582
+ const title = oneLine(str3(input?.title), 80) || (typed ? titleFor(typed, content) : "Artifact");
9583
+ if (!str3(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
9584
+ if (hint) hints.add(hint);
9585
+ docs.push({ title, content, kind: typed ?? "text" });
9586
+ }
9587
+ }
9588
+ return docs;
9589
+ }
9590
+ function phoneArtifactReply(docs, request) {
9591
+ const match = pickArtifactMatch(docs, request);
9592
+ if (!match) return null;
9593
+ const truncated = match.content.length > PHONE_ARTIFACT_MAX_CHARS;
9594
+ return {
9595
+ title: match.title,
9596
+ kind: match.kind,
9597
+ content: truncated ? match.content.slice(0, PHONE_ARTIFACT_MAX_CHARS) : match.content,
9598
+ truncated
9599
+ };
9600
+ }
9601
+ function newest(items) {
9602
+ return items.length ? items[items.length - 1] ?? null : null;
9603
+ }
9604
+ function pickArtifactMatch(items, request) {
9605
+ const hint = request.hint?.trim();
9606
+ const title = request.title.trim();
9607
+ if (hint) {
9608
+ const exact = items.filter((item) => item.content && artifactHint(item.content) === hint);
9609
+ const exactTitled = title ? exact.filter((item) => item.title === title) : [];
9610
+ const exactMatch = newest(exactTitled.length ? exactTitled : exact);
9611
+ if (exactMatch) return exactMatch;
9612
+ if (title) {
9613
+ for (let i = items.length - 1; i >= 0; i--) {
9614
+ const item = items[i];
9615
+ if (!item?.content || item.title !== title) continue;
9616
+ if (normalizedBody(item.content).startsWith(hint)) return item;
9617
+ }
9618
+ }
9619
+ }
9620
+ if (!title) return null;
9621
+ for (let i = items.length - 1; i >= 0; i--) {
9622
+ if (items[i]?.title === title) return items[i] ?? null;
9623
+ }
9624
+ return null;
9625
+ }
9626
+
9627
+ // src/remote/phone-chats.ts
9628
+ import { randomUUID as randomUUID7 } from "crypto";
9629
+
9630
+ // src/remote/phone-header.ts
9631
+ var PHONE_HEADER_GIT_ACTIONS = [
9632
+ "commit-push",
9633
+ "create-draft",
9634
+ "resolve-conflicts",
9635
+ "ready-for-review",
9636
+ "merge"
9637
+ ];
9638
+ var HIDDEN = {
9639
+ visible: false,
9640
+ branch: "",
9641
+ additions: 0,
9642
+ deletions: 0,
9643
+ unpushed: 0,
9644
+ dirty: false,
9645
+ ready: false,
9646
+ title: ""
9647
+ };
9648
+ function phoneHeaderFrom(thread, diff) {
9649
+ if (isGlobalThread(thread)) return HIDDEN;
9650
+ const branch = thread.branchName.trim().replace(/^thread\//, "");
9651
+ if (!branch) return HIDDEN;
9652
+ const ready = diff != null;
9653
+ const uncommitted = diff?.scopeStats?.uncommitted;
9654
+ const additions = ready ? Math.max(0, Math.trunc(uncommitted?.additions ?? 0)) : 0;
9655
+ const deletions = ready ? Math.max(0, Math.trunc(uncommitted?.deletions ?? 0)) : 0;
9656
+ const unpushed = ready ? Math.max(0, Math.trunc(diff?.unpushed ?? 0)) : 0;
9657
+ const dirty = ready && (Boolean(diff?.dirty) || additions > 0 || deletions > 0);
9658
+ const pr = prSnapshot(thread);
9659
+ const action = ready ? headerAction(pr, { dirty: Boolean(diff?.dirty), unpushed }) : null;
9660
+ const lines = [branch];
9661
+ if (!ready) lines.push("Loading git status\u2026");
9662
+ else if (dirty) lines.push(`Uncommitted +${additions} \u2212${deletions}`);
9663
+ else lines.push("Working tree clean");
9664
+ if (unpushed > 0) lines.push(`${unpushed} unpushed commit${unpushed === 1 ? "" : "s"}`);
9665
+ return {
9666
+ visible: true,
9667
+ branch,
9668
+ additions,
9669
+ deletions,
9670
+ unpushed,
9671
+ dirty,
9672
+ ready,
9673
+ title: lines.join("\n"),
9674
+ ...pr.url ? { prUrl: pr.url } : {},
9675
+ ...pr.number ? { prNumber: pr.number } : {},
9676
+ ...pr.status ? { prStatus: pr.status } : {},
9677
+ ...action ? { action: action.id, actionLabel: action.label } : {}
9678
+ };
9679
+ }
9680
+ function phonePrPill(thread) {
9681
+ const pr = prSnapshot(thread);
9682
+ if (!pr.url) return null;
9683
+ return { url: pr.url, number: pr.number, status: pr.status };
9684
+ }
9685
+ async function readPhoneHeader(thread) {
9686
+ const pending = phoneHeaderFrom(thread, null);
9687
+ if (!pending.visible) return pending;
9688
+ try {
9689
+ const diff = await getOrchestrator().diff(thread.id, { includePatches: false });
9690
+ return phoneHeaderFrom(thread, diff);
9691
+ } catch {
9692
+ return pending;
9693
+ }
9694
+ }
9695
+ function prSnapshot(thread) {
9696
+ const url = thread.prUrl?.trim() || "";
9697
+ const number = url.match(/\/pull\/(\d+)/)?.[1] ?? "";
9698
+ const state = (thread.prState ?? "").toUpperCase();
9699
+ const merged = state === "MERGED";
9700
+ const closed = state === "CLOSED";
9701
+ const open = Boolean(url) && !merged && !closed;
9702
+ const draft = open && Boolean(thread.prIsDraft);
9703
+ const inMergeQueue = open && Boolean(thread.prIsInMergeQueue);
9704
+ const mergeable = (thread.prMergeable ?? "").toUpperCase();
9705
+ const mergeState = (thread.prMergeStateStatus ?? "").toUpperCase();
9706
+ const mergeConflicts = open && !inMergeQueue && (mergeable === "CONFLICTING" || mergeState === "DIRTY");
9707
+ const branchBehind = open && !inMergeQueue && !mergeConflicts && mergeState === "BEHIND";
9708
+ const checksFailed = Boolean(thread.prChecksFailed);
9709
+ const review = (open && !draft ? thread.prReviewDecision : "") ?? "";
9710
+ return {
9711
+ url,
9712
+ number,
9713
+ status: pillStatus({ url, merged, closed, draft, inMergeQueue, mergeConflicts, branchBehind, checksFailed, review }),
9714
+ merged,
9715
+ closed,
9716
+ open,
9717
+ draft,
9718
+ cowboy: Boolean(thread.cowboy),
9719
+ inMergeQueue,
9720
+ mergeConflicts,
9721
+ branchBehind,
9722
+ checksFailed,
9723
+ review: review.toUpperCase()
9724
+ };
9725
+ }
9726
+ function pillStatus(pr) {
9727
+ if (!pr.url) return "";
9728
+ if (pr.merged) return "Merged";
9729
+ if (pr.closed) return "Closed";
9730
+ if (pr.inMergeQueue) return "Queued";
9731
+ if (pr.mergeConflicts) return "Merge conflicts";
9732
+ if (pr.branchBehind) return "Behind base";
9733
+ if (pr.draft) return "Draft";
9734
+ if (pr.checksFailed) return "Checks failing";
9735
+ if (pr.review === "CHANGES_REQUESTED") return "Changes requested";
9736
+ if (pr.review !== "APPROVED") return "Needs approval";
9737
+ return "Approved";
9738
+ }
9739
+ function headerAction(pr, diff) {
9740
+ const hasLocalChanges = diff.dirty || diff.unpushed > 0;
9741
+ const originInSync = !hasLocalChanges;
9742
+ if (pr.merged) return null;
9743
+ if (pr.closed) return { id: "open-pr", label: "Closed" };
9744
+ if (pr.cowboy) {
9745
+ return hasLocalChanges ? { id: "commit-push", label: "Commit & push" } : { id: "commit-push", label: "Push" };
9746
+ }
9747
+ if (!pr.url) return { id: "create-draft", label: "Create PR" };
9748
+ if (pr.inMergeQueue) return { id: "open-pr", label: "Queued" };
9749
+ if (pr.mergeConflicts) return { id: "resolve-conflicts", label: "Resolve" };
9750
+ if (pr.branchBehind) return { id: "resolve-conflicts", label: "Update" };
9751
+ if (hasLocalChanges) return { id: "commit-push", label: "Commit & push" };
9752
+ if (pr.draft) {
9753
+ return originInSync ? { id: "ready-for-review", label: "Ready for review" } : { id: "commit-push", label: "Commit & push" };
9754
+ }
9755
+ if (pr.checksFailed) return { id: "open-checks", label: "Checks failing" };
9756
+ if (pr.review === "CHANGES_REQUESTED") return { id: "open-pr", label: "Changes requested" };
9757
+ if (pr.review !== "APPROVED") return { id: "open-pr", label: "Needs approval" };
9758
+ return { id: "merge", label: "Merge" };
9759
+ }
9760
+
9761
+ // src/remote/phone-panes.ts
9762
+ var MAX_ROWS = 200;
9763
+ function parsePhonePane(value, chatId) {
9764
+ const pane = value && typeof value === "object" ? value.pane : void 0;
9765
+ if (pane !== "files" && pane !== "changes" && pane !== "checks") return "invalid";
9766
+ const scopeRaw = value.scope;
9767
+ const scope = scopeRaw === "uncommitted" || scopeRaw === "commits" ? scopeRaw : void 0;
9768
+ return { op: "pane", chatId, pane, ...scope && pane === "changes" ? { scope } : {} };
9769
+ }
9770
+ async function readPhonePane(thread, pane, scope) {
9771
+ try {
9772
+ if (pane === "files") return await readFiles(thread.id);
9773
+ if (pane === "changes") return await readChanges(thread.id, scope ?? "commits");
9774
+ return await readChecks(thread.id);
9775
+ } catch (err) {
9776
+ const message = err instanceof Error ? err.message : String(err);
9777
+ return { rows: [], note: message };
9778
+ }
9779
+ }
9780
+ async function readFiles(threadId) {
9781
+ const paths = await getOrchestrator().listFiles(threadId);
9782
+ const shown = paths.slice(0, MAX_ROWS);
9783
+ return {
9784
+ rows: shown.map((path2) => ({ label: path2, path: path2 })),
9785
+ ...paths.length > shown.length ? { note: `${paths.length - shown.length} more files on the Mac` } : {}
9786
+ };
9787
+ }
9788
+ async function readChanges(threadId, scope) {
9789
+ const diff = await getOrchestrator().diff(threadId, { includePatches: false, scope });
9790
+ const shown = diff.files.slice(0, MAX_ROWS);
9791
+ return {
9792
+ rows: shown.map((file) => ({
9793
+ label: file.path,
9794
+ path: file.path,
9795
+ detail: changeDetail(file.additions, file.deletions)
9796
+ })),
9797
+ ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {}
9798
+ };
9799
+ }
9800
+ function changeDetail(additions, deletions) {
9801
+ const add = additions ?? 0;
9802
+ const del = deletions ?? 0;
9803
+ if (add === 0 && del === 0) return void 0;
9804
+ return `+${add} \u2212${del}`;
9805
+ }
9806
+ async function readChecks(threadId) {
9807
+ const checks = await getOrchestrator().getPrChecks(threadId);
9808
+ if (!checks) return { rows: [], note: "No pull request on this branch." };
9809
+ const shown = checks.slice(0, MAX_ROWS);
9810
+ return {
9811
+ rows: shown.map((check) => ({
9812
+ label: check.name,
9813
+ detail: check.bucket || check.state,
9814
+ ...check.link ? { href: check.link } : {}
9815
+ }))
9816
+ };
9817
+ }
9462
9818
 
9463
9819
  // src/remote/phone-defaults.ts
9464
9820
  function row(resolved) {
@@ -9476,6 +9832,29 @@ function phoneAccountDefaults() {
9476
9832
  };
9477
9833
  }
9478
9834
 
9835
+ // src/remote/phone-viewer-login.ts
9836
+ var TTL_MS = 5 * 6e4;
9837
+ var cached = "";
9838
+ var fetchedAt = 0;
9839
+ var inflight2 = null;
9840
+ function phoneViewerLogin() {
9841
+ return cached;
9842
+ }
9843
+ function refreshPhoneViewerLogin(force = false) {
9844
+ const now = Date.now();
9845
+ if (!force && fetchedAt && now - fetchedAt < TTL_MS) return Promise.resolve(cached);
9846
+ if (inflight2) return inflight2;
9847
+ const run2 = getGitHubStatus().then((status) => {
9848
+ cached = status.login?.trim() ?? "";
9849
+ fetchedAt = Date.now();
9850
+ return cached;
9851
+ }).catch(() => cached).finally(() => {
9852
+ if (inflight2 === run2) inflight2 = null;
9853
+ });
9854
+ inflight2 = run2;
9855
+ return run2;
9856
+ }
9857
+
9479
9858
  // src/remote/phone-stream.ts
9480
9859
  var PHONE_STREAM_FLUSH_MS = 80;
9481
9860
  function phoneStreamFrame(parts) {
@@ -9536,10 +9915,61 @@ function listPhoneMessages(thread) {
9536
9915
  return live.slice(-TRANSCRIPT_LIMIT);
9537
9916
  }
9538
9917
 
9539
- // src/remote/phone-chats.ts
9540
- var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
9918
+ // src/remote/phone-history.ts
9541
9919
  var PREVIEW_LIMIT = 90;
9542
9920
  var HISTORY_PAGE_SIZE = 40;
9921
+ function previewOf(thread) {
9922
+ const messages = listPhoneMessages(thread);
9923
+ const last = messages[messages.length - 1];
9924
+ if (!last) return "";
9925
+ return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
9926
+ }
9927
+ function repoName(repoPath) {
9928
+ const parts = repoPath.replace(/\/$/, "").split("/");
9929
+ return parts[parts.length - 1] || repoPath;
9930
+ }
9931
+ function isProjectPath(path2) {
9932
+ if (!path2 || path2 === "/" || path2 === ".") return false;
9933
+ return !isGlobalThread({ repoPath: path2 });
9934
+ }
9935
+ function historyStamp(thread) {
9936
+ return thread.archivedAt || thread.updatedAt;
9937
+ }
9938
+ function historyCursor(chat) {
9939
+ return `${chat.updatedAt} ${chat.id}`;
9940
+ }
9941
+ function listPhoneHistory(opts) {
9942
+ const query = opts?.query?.trim().toLowerCase() ?? "";
9943
+ const after = opts?.after?.trim() ?? "";
9944
+ const all = listThreads({ includeArchived: true }).filter((thread) => thread.status === "archived").filter((thread) => isGlobalThread(thread) || isProjectPath(thread.repoPath)).sort((a, b) => historyStamp(a) < historyStamp(b) ? 1 : historyStamp(a) > historyStamp(b) ? -1 : 0).map((thread) => ({
9945
+ id: thread.id,
9946
+ title: thread.title?.trim() || "Untitled",
9947
+ preview: previewOf(thread),
9948
+ updatedAt: historyStamp(thread),
9949
+ where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
9950
+ agent: thread.agent
9951
+ })).filter((chat) => {
9952
+ if (!query) return true;
9953
+ return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
9954
+ });
9955
+ let start = 0;
9956
+ if (after) {
9957
+ const index = all.findIndex((chat) => historyCursor(chat) === after);
9958
+ start = index >= 0 ? index + 1 : all.length;
9959
+ }
9960
+ const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
9961
+ const last = chats.at(-1);
9962
+ const more = start + chats.length < all.length;
9963
+ return {
9964
+ chats,
9965
+ total: all.length,
9966
+ ...more && last ? { next: historyCursor(last) } : {}
9967
+ };
9968
+ }
9969
+
9970
+ // src/remote/phone-chats.ts
9971
+ var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
9972
+ var PREVIEW_LIMIT2 = 90;
9543
9973
  var AGENT_KINDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "brightsy", "cursor"]);
9544
9974
  var MAX_PHONE_FILES = 8;
9545
9975
  var MAX_PHONE_FILE_CHARS = Math.ceil(8 * 1024 * 1024 * 4 / 3);
@@ -9569,7 +9999,7 @@ function parsePhoneControl(value) {
9569
9999
  }
9570
10000
  if (op === "sources") {
9571
10001
  const repoPath = textField(value, "repoPath");
9572
- if (!repoPath || !isProjectPath(repoPath)) return "invalid";
10002
+ if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
9573
10003
  const query = textField(value, "query");
9574
10004
  return { op: "sources", repoPath, ...query ? { query } : {} };
9575
10005
  }
@@ -9587,7 +10017,15 @@ function parsePhoneControl(value) {
9587
10017
  if (op === "create") return parsePhoneCreate(value);
9588
10018
  const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
9589
10019
  if (!chatId) return "invalid";
9590
- if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
10020
+ if (op === "open" || op === "archive" || op === "restore" || op === "stop" || op === "review" || op === "header") {
10021
+ return { op, chatId };
10022
+ }
10023
+ if (op === "git") {
10024
+ const action = textField(value, "action");
10025
+ if (!PHONE_HEADER_GIT_ACTIONS.includes(action)) return "invalid";
10026
+ return { op: "git", chatId, action };
10027
+ }
10028
+ if (op === "pane") return parsePhonePane(value, chatId);
9591
10029
  if (op === "open-artifact") return parseOpenArtifact(value, chatId);
9592
10030
  if (op === "open-file" || op === "media") {
9593
10031
  const path2 = textField(value, "path");
@@ -9727,7 +10165,7 @@ function parsePhoneCreate(value) {
9727
10165
  }
9728
10166
  if (where === "project") {
9729
10167
  const repoPath = textField(value, "repoPath");
9730
- if (!repoPath || !isProjectPath(repoPath)) return "invalid";
10168
+ if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
9731
10169
  const sourceRaw = textField(value, "sourceType");
9732
10170
  let sourceType;
9733
10171
  if (sourceRaw === "branch" || sourceRaw === "pr" || sourceRaw === "ticket") sourceType = sourceRaw;
@@ -9750,94 +10188,70 @@ function parsePhoneCreate(value) {
9750
10188
  return "invalid";
9751
10189
  }
9752
10190
  function summarize(thread) {
10191
+ const pr = phonePrPill(thread);
9753
10192
  return {
9754
10193
  id: thread.id,
9755
10194
  title: thread.title?.trim() || "Untitled",
9756
10195
  status: thread.status,
9757
- preview: previewOf(thread),
10196
+ preview: previewOf2(thread),
9758
10197
  updatedAt: thread.updatedAt,
9759
- agent: thread.agent
10198
+ agent: thread.agent,
10199
+ ...pr?.url ? { prUrl: pr.url } : {},
10200
+ ...pr?.number ? { prNumber: pr.number } : {},
10201
+ ...pr?.status ? { prStatus: pr.status } : {}
9760
10202
  };
9761
10203
  }
9762
- function previewOf(thread) {
10204
+ function previewOf2(thread) {
9763
10205
  const messages = listPhoneMessages(thread);
9764
10206
  const last = messages[messages.length - 1];
9765
10207
  if (!last) return "";
9766
- return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
10208
+ return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT2);
9767
10209
  }
9768
- function repoName(repoPath) {
10210
+ function repoName2(repoPath) {
9769
10211
  const parts = repoPath.replace(/\/$/, "").split("/");
9770
10212
  return parts[parts.length - 1] || repoPath;
9771
10213
  }
9772
- function isProjectPath(path2) {
10214
+ function isProjectPath2(path2) {
9773
10215
  if (!path2 || path2 === "/" || path2 === ".") return false;
9774
10216
  return !isGlobalThread({ repoPath: path2 });
9775
10217
  }
9776
10218
  function listPhoneChats() {
9777
10219
  return listGlobalThreads().slice().sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).map(summarize);
9778
10220
  }
9779
- function listPhoneSidebar() {
10221
+ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
10222
+ const settings = loadAppSettings();
9780
10223
  const threads = listThreads();
9781
10224
  const byRepo = /* @__PURE__ */ new Map();
9782
10225
  for (const thread of threads) {
9783
- if (!isProjectPath(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
10226
+ if (!isProjectPath2(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
9784
10227
  const list = byRepo.get(thread.repoPath) ?? [];
9785
10228
  list.push(thread);
9786
10229
  byRepo.set(thread.repoPath, list);
9787
10230
  }
9788
10231
  for (const workspace of listWorkspaces()) {
9789
- if (!isProjectPath(workspace.path) || byRepo.has(workspace.path)) continue;
10232
+ if (!isProjectPath2(workspace.path) || byRepo.has(workspace.path)) continue;
9790
10233
  byRepo.set(workspace.path, []);
9791
10234
  }
9792
- const projects = [...byRepo.entries()].sort(([a], [b]) => repoName(a).localeCompare(repoName(b))).map(([path2, repoThreads]) => ({
9793
- name: repoName(path2),
10235
+ const projects = [...byRepo.entries()].sort(([a], [b]) => repoName2(a).localeCompare(repoName2(b))).map(([path2, repoThreads]) => ({
10236
+ name: repoName2(path2),
9794
10237
  path: path2,
9795
10238
  worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => {
9796
10239
  const tags = workspaceTagsFromGroup(group);
10240
+ const badge = classifyWorktreeListBadges(
10241
+ group,
10242
+ viewerLogin,
10243
+ resolveProjectReviewLabel(settings, path2)
10244
+ )[0];
9797
10245
  return {
9798
10246
  label: worktreeDisplayLabelForGroup(group),
9799
10247
  chats: group.map(summarize),
9800
- ...tags.length > 0 ? { tags } : {}
10248
+ ...tags.length > 0 ? { tags } : {},
10249
+ ...badge ? { badge: { id: badge.id, label: badge.label, title: badge.title } } : {}
9801
10250
  };
9802
10251
  })
9803
10252
  }));
9804
10253
  return { orchestration: listPhoneChats(), projects, defaults: phoneAccountDefaults() };
9805
10254
  }
9806
- function historyStamp(thread) {
9807
- return thread.archivedAt || thread.updatedAt;
9808
- }
9809
- function historyCursor(chat) {
9810
- return `${chat.updatedAt} ${chat.id}`;
9811
- }
9812
- function historyQueryMatch(chat, query) {
9813
- if (!query) return true;
9814
- return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
9815
- }
9816
- function listPhoneHistory(opts) {
9817
- const query = opts?.query?.trim().toLowerCase() ?? "";
9818
- const after = opts?.after?.trim() ?? "";
9819
- const all = listThreads({ includeArchived: true }).filter((thread) => thread.status === "archived").filter((thread) => isGlobalThread(thread) || isProjectPath(thread.repoPath)).sort((a, b) => historyStamp(a) < historyStamp(b) ? 1 : historyStamp(a) > historyStamp(b) ? -1 : 0).map((thread) => ({
9820
- id: thread.id,
9821
- title: thread.title?.trim() || "Untitled",
9822
- preview: previewOf(thread),
9823
- updatedAt: historyStamp(thread),
9824
- where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
9825
- agent: thread.agent
9826
- })).filter((chat) => historyQueryMatch(chat, query));
9827
- let start = 0;
9828
- if (after) {
9829
- const index = all.findIndex((chat) => historyCursor(chat) === after);
9830
- start = index >= 0 ? index + 1 : all.length;
9831
- }
9832
- const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
9833
- const last = chats.at(-1);
9834
- const more = start + chats.length < all.length;
9835
- return {
9836
- chats,
9837
- total: all.length,
9838
- ...more && last ? { next: historyCursor(last) } : {}
9839
- };
9840
- }
9841
10255
  function matchPhoneThread(chatId) {
9842
10256
  const id = chatId.trim();
9843
10257
  if (!id) return null;
@@ -9850,7 +10264,7 @@ function matchPhoneThread(chatId) {
9850
10264
  }
9851
10265
  function visiblePhoneThread(thread) {
9852
10266
  if (isGlobalThread(thread)) return true;
9853
- return isProjectPath(thread.repoPath);
10267
+ return isProjectPath2(thread.repoPath);
9854
10268
  }
9855
10269
  function findPhoneChat(chatId) {
9856
10270
  const thread = matchPhoneThread(chatId);
@@ -9916,7 +10330,7 @@ function projectPlace(thread) {
9916
10330
  }
9917
10331
  function assertKnownProject(repoPath) {
9918
10332
  const path2 = repoPath.trim();
9919
- if (!isProjectPath(path2)) throw new Error("Pick a project on this Mac.");
10333
+ if (!isProjectPath2(path2)) throw new Error("Pick a project on this Mac.");
9920
10334
  const known = listWorkspaces().some((workspace) => sameRepoPath(workspace.path, path2)) || listThreads().some(
9921
10335
  (thread) => thread.status !== "archived" && sameRepoPath(thread.repoPath, path2)
9922
10336
  );
@@ -10019,6 +10433,55 @@ function archivePhoneChat(chatId) {
10019
10433
  return true;
10020
10434
  }
10021
10435
 
10436
+ // src/remote/phone-workspace-ops.ts
10437
+ function dispatchPhoneWorkspace(cmd, emit, publishOpened) {
10438
+ if (cmd.op === "pane") {
10439
+ const thread = readPhoneThread(cmd.chatId);
10440
+ if (!thread) {
10441
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10442
+ return;
10443
+ }
10444
+ void readPhonePane(thread, cmd.pane, cmd.scope).then((body) => {
10445
+ emit({
10446
+ op: "pane",
10447
+ chatId: cmd.chatId,
10448
+ pane: cmd.pane,
10449
+ rows: body.rows,
10450
+ ...body.note ? { note: body.note } : {}
10451
+ });
10452
+ });
10453
+ return;
10454
+ }
10455
+ if (cmd.op === "header") {
10456
+ const thread = readPhoneThread(cmd.chatId);
10457
+ if (!thread) {
10458
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10459
+ return;
10460
+ }
10461
+ void readPhoneHeader(thread).then((header) => {
10462
+ emit({ op: "header", chatId: cmd.chatId, ...header });
10463
+ });
10464
+ return;
10465
+ }
10466
+ if (cmd.op === "git") {
10467
+ void getOrchestrator().askGit(cmd.chatId, cmd.action).then((tab) => publishOpened(tab.id)).catch((err) => {
10468
+ emit({
10469
+ op: "error",
10470
+ chatId: cmd.chatId,
10471
+ message: err instanceof Error ? err.message : String(err)
10472
+ });
10473
+ });
10474
+ return;
10475
+ }
10476
+ void getOrchestrator().requestReview(cmd.chatId).then((tab) => publishOpened(tab.id)).catch((err) => {
10477
+ emit({
10478
+ op: "error",
10479
+ chatId: cmd.chatId,
10480
+ message: err instanceof Error ? err.message : String(err)
10481
+ });
10482
+ });
10483
+ }
10484
+
10022
10485
  // src/remote/phone-sources.ts
10023
10486
  function sourceWarning(label, err) {
10024
10487
  const message = err instanceof Error ? err.message : String(err);
@@ -10584,10 +11047,25 @@ function handlePhoneControl(cmd, opts) {
10584
11047
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file is not in this worktree." });
10585
11048
  return;
10586
11049
  }
10587
- opts.onOpenWorktreeFile?.({
10588
- threadId: thread.id,
11050
+ const preview = phoneFilePreview(thread.worktreePath, relative, kind === "dir" ? "dir" : "file");
11051
+ if (!preview) {
11052
+ emitControl(opts, {
11053
+ op: "error",
11054
+ chatId: cmd.chatId,
11055
+ message: "That file could not be opened on the phone."
11056
+ });
11057
+ return;
11058
+ }
11059
+ emitControl(opts, {
11060
+ op: "file",
11061
+ chatId: cmd.chatId,
10589
11062
  path: relative,
10590
- ...kind === "dir" ? { directory: true } : {},
11063
+ content: preview.content,
11064
+ encoding: preview.encoding,
11065
+ ...preview.binary ? { binary: true } : {},
11066
+ ...preview.directory ? { directory: true } : {},
11067
+ ...preview.truncated ? { truncated: true } : {},
11068
+ ...preview.mime ? { mime: preview.mime } : {},
10591
11069
  ...cmd.startLine != null ? { startLine: cmd.startLine } : {},
10592
11070
  ...cmd.endLine != null ? { endLine: cmd.endLine } : {}
10593
11071
  });
@@ -10602,13 +11080,34 @@ function handlePhoneControl(cmd, opts) {
10602
11080
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10603
11081
  return;
10604
11082
  }
10605
- opts.onOpenArtifact?.({
10606
- threadId: thread.id,
10607
- title: cmd.title,
10608
- ...cmd.hint ? { hint: cmd.hint } : {}
11083
+ const liveParts = readTurnLiveParts(thread.id);
11084
+ const docs = phoneArtifactDocuments([
11085
+ ...(thread.messages ?? []).filter((message) => message.role === "agent").map((message) => ({ text: message.text.trim(), parts: message.parts })),
11086
+ { text: visibleAssistantText(void 0, liveParts), parts: liveParts }
11087
+ ]);
11088
+ const body = phoneArtifactReply(docs, { title: cmd.title, ...cmd.hint ? { hint: cmd.hint } : {} });
11089
+ if (!body) {
11090
+ emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That artifact is not in this chat." });
11091
+ return;
11092
+ }
11093
+ emitControl(opts, {
11094
+ op: "artifact",
11095
+ chatId: cmd.chatId,
11096
+ title: body.title,
11097
+ kind: body.kind,
11098
+ content: body.content,
11099
+ ...body.truncated ? { truncated: true } : {}
10609
11100
  });
10610
11101
  return;
10611
11102
  }
11103
+ if (cmd.op === "pane" || cmd.op === "header" || cmd.op === "git" || cmd.op === "review") {
11104
+ dispatchPhoneWorkspace(
11105
+ cmd,
11106
+ (reply) => emitControl(opts, reply),
11107
+ (chatId2) => publishOpenedChat(opts, chatId2)
11108
+ );
11109
+ return;
11110
+ }
10612
11111
  if (cmd.op === "stop") {
10613
11112
  if (!readPhoneThread(cmd.chatId)) {
10614
11113
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
@@ -10696,7 +11195,9 @@ function chatKey(row2) {
10696
11195
  function phoneBoardKey(sidebar) {
10697
11196
  const orchestration = sidebar.orchestration.map(chatKey).join("\n");
10698
11197
  const projects = sidebar.projects.map((project) => {
10699
- const trees = project.worktrees.map((tree) => `${tree.label} ${(tree.tags ?? []).join(",")} ${tree.chats.map(chatKey).join("\n")}`).join("\n");
11198
+ const trees = project.worktrees.map(
11199
+ (tree) => `${tree.label} ${tree.badge?.id ?? ""} ${(tree.tags ?? []).join(",")} ${tree.chats.map(chatKey).join("\n")}`
11200
+ ).join("\n");
10700
11201
  return `${project.path} ${project.name}
10701
11202
  ${trees}`;
10702
11203
  }).join("\n---\n");
@@ -10774,10 +11275,23 @@ function startPhoneBoardSync(opts) {
10774
11275
  const unsubscribe = (opts.subscribe ?? subscribePhoneBoard)(schedule);
10775
11276
  const offEvents = (opts.events ?? ((listener) => getOrchestrator().on(listener)))(handle);
10776
11277
  push();
11278
+ let loginTimer = null;
11279
+ const refreshViewerLogin = opts.refreshViewerLogin;
11280
+ if (refreshViewerLogin) {
11281
+ const afterLogin = () => {
11282
+ void refreshViewerLogin().then(() => {
11283
+ if (!stopped) push();
11284
+ });
11285
+ };
11286
+ afterLogin();
11287
+ loginTimer = setInterval(afterLogin, opts.viewerLoginIntervalMs ?? 5 * 6e4);
11288
+ }
10777
11289
  return () => {
10778
11290
  stopped = true;
10779
11291
  if (timer) clearTimeout(timer);
10780
11292
  timer = null;
11293
+ if (loginTimer) clearInterval(loginTimer);
11294
+ loginTimer = null;
10781
11295
  unsubscribe();
10782
11296
  offEvents();
10783
11297
  };
@@ -10844,7 +11358,8 @@ function runRemoteHost(opts) {
10844
11358
  },
10845
11359
  onRunningChat: (chatId) => {
10846
11360
  deliverPhoneTurn({ onOutbound: emit }, chatId);
10847
- }
11361
+ },
11362
+ refreshViewerLogin: () => refreshPhoneViewerLogin(true)
10848
11363
  });
10849
11364
  };
10850
11365
  const send3 = (msg) => {
@@ -11023,14 +11538,14 @@ import { randomBytes as randomBytes7 } from "crypto";
11023
11538
 
11024
11539
  // src/relay/accounts.ts
11025
11540
  import { createHash as createHash2, randomBytes as randomBytes6, randomUUID as randomUUID8, timingSafeEqual as timingSafeEqual2 } from "crypto";
11026
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, realpathSync } from "fs";
11541
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, realpathSync } from "fs";
11027
11542
  import { createRequire } from "module";
11028
- import { dirname, join as join6 } from "path";
11543
+ import { dirname, join as join7 } from "path";
11029
11544
  var GITHUB_ACCOUNT_ISSUER = "https://github.com";
11030
11545
  var RELAY_DEVICE_CAP = 3;
11031
11546
  function thisModuleFile() {
11032
11547
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
11033
- return cjsFile || process.argv[1] || join6(process.cwd(), "package.json");
11548
+ return cjsFile || process.argv[1] || join7(process.cwd(), "package.json");
11034
11549
  }
11035
11550
  function openDatabase(filePath) {
11036
11551
  const req = createRequire(thisModuleFile());
@@ -11068,7 +11583,7 @@ function assertRelayAccountsDurable(filePath) {
11068
11583
  if (process.env.NODE_ENV !== "production") return;
11069
11584
  if (process.platform !== "linux") return;
11070
11585
  const dir = realpathSync(dirname(filePath));
11071
- const mounts = readFileSync5("/proc/mounts", "utf8");
11586
+ const mounts = readFileSync6("/proc/mounts", "utf8");
11072
11587
  const point = longestMountPoint(dir, mounts);
11073
11588
  if (point === "/") {
11074
11589
  throw new Error(
@@ -11106,9 +11621,15 @@ var RelayAccountStore = class {
11106
11621
  account_id TEXT NOT NULL,
11107
11622
  host_secret_hash TEXT NOT NULL,
11108
11623
  device_label TEXT NOT NULL,
11109
- created_at INTEGER NOT NULL
11624
+ created_at INTEGER NOT NULL,
11625
+ session_token_hash TEXT
11110
11626
  );
11111
11627
  `);
11628
+ const cols = this.db.prepare("PRAGMA table_info(devices)").all();
11629
+ if (!cols.some((col) => col.name === "session_token_hash")) {
11630
+ this.db.exec("ALTER TABLE devices ADD COLUMN session_token_hash TEXT");
11631
+ }
11632
+ this.db.exec("CREATE INDEX IF NOT EXISTS devices_session ON devices(session_token_hash)");
11112
11633
  }
11113
11634
  close() {
11114
11635
  this.db.close();
@@ -11237,6 +11758,20 @@ var RelayAccountStore = class {
11237
11758
  throw err;
11238
11759
  }
11239
11760
  }
11761
+ /** Store a phone pairing when it is created. A later process looks it up on a memory miss. */
11762
+ savePhoneSession(deviceId, sessionToken) {
11763
+ const id = deviceId.trim();
11764
+ const token = sessionToken.trim();
11765
+ if (!id || !token) return;
11766
+ this.db.prepare("UPDATE devices SET session_token_hash = ? WHERE device_id = ?").run(sha256(token), id);
11767
+ }
11768
+ findPhoneSession(sessionToken) {
11769
+ const token = sessionToken.trim();
11770
+ if (!token) return null;
11771
+ const row2 = this.db.prepare("SELECT device_id, device_label FROM devices WHERE session_token_hash = ?").get(sha256(token));
11772
+ if (!row2) return null;
11773
+ return { deviceId: row2.device_id, deviceLabel: row2.device_label };
11774
+ }
11240
11775
  requireAccount(accountId) {
11241
11776
  const row2 = this.db.prepare("SELECT id FROM accounts WHERE id = ?").get(accountId);
11242
11777
  if (!row2) throw new Error("That Sideboard account is no longer on the relay.");
@@ -12226,6 +12761,10 @@ async function startRelayServer(opts) {
12226
12761
  const decision = accountStore.authorizeHost(input);
12227
12762
  return decision.ok ? { ok: true } : { ok: false, message: decision.message };
12228
12763
  } : void 0,
12764
+ phoneSessions: accountStore ? {
12765
+ find: (sessionToken) => accountStore.findPhoneSession(sessionToken),
12766
+ save: (deviceId, sessionToken) => accountStore.savePhoneSession(deviceId, sessionToken)
12767
+ } : void 0,
12229
12768
  onAuthorized: (socket) => authMarks.get(socket)?.()
12230
12769
  });
12231
12770
  const handleCallback = async (reqUrl, res) => {
@@ -12521,8 +13060,8 @@ async function startRelayServer(opts) {
12521
13060
  }
12522
13061
 
12523
13062
  // src/slack/outbound-watch.ts
12524
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
12525
- import { join as join7 } from "path";
13063
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
13064
+ import { join as join8 } from "path";
12526
13065
  var MAX_WATCHES = 40;
12527
13066
  var MAX_REPLIES_PER_WATCH = 30;
12528
13067
  var WATCH_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -12530,7 +13069,7 @@ var POLL_INTERVAL_MS = 12e3;
12530
13069
  var lastPollMs = 0;
12531
13070
  var nameCache = /* @__PURE__ */ new Map();
12532
13071
  function storePath3() {
12533
- return join7(appDataDir(), "slack-outbound-watch.json");
13072
+ return join8(appDataDir(), "slack-outbound-watch.json");
12534
13073
  }
12535
13074
  function watchId(teamId, channelId, ts) {
12536
13075
  return `${teamId}:${channelId}:${ts}`;
@@ -12545,7 +13084,7 @@ function readStore3() {
12545
13084
  const path2 = storePath3();
12546
13085
  if (!existsSync5(path2)) return [];
12547
13086
  try {
12548
- const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync6(path2, "utf8"));
13087
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync7(path2, "utf8"));
12549
13088
  return Array.isArray(parsed?.watches) ? parsed.watches : [];
12550
13089
  } catch {
12551
13090
  return [];
@@ -12597,7 +13136,7 @@ async function continueSourceThread(threadId, prompt) {
12597
13136
  await continueOnReply(threadId, prompt);
12598
13137
  return;
12599
13138
  }
12600
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-SDH6W6ZL.js");
13139
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-GSOW3RPA.js");
12601
13140
  await getOrchestrator2().send(threadId, prompt);
12602
13141
  } catch {
12603
13142
  }
@@ -12706,8 +13245,8 @@ function displayName(user) {
12706
13245
  return fromProfile || user.real_name?.trim() || user.name?.trim() || "";
12707
13246
  }
12708
13247
  async function resolveUserName(token, userId, fallback, fetchImpl) {
12709
- const cached = nameCache.get(userId);
12710
- if (cached) return cached;
13248
+ const cached2 = nameCache.get(userId);
13249
+ if (cached2) return cached2;
12711
13250
  try {
12712
13251
  const data = await slackApi(token, "users.info", { user: userId }, fetchImpl);
12713
13252
  const name = displayName(data.user ?? {}) || fallback;