@sideboard-ai/core 0.1.289 → 0.1.291

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/index.js CHANGED
@@ -186,7 +186,7 @@ import {
186
186
  worktreeBoardStatus,
187
187
  worktreeCleanupSettings,
188
188
  writeWorktreeFile
189
- } from "./chunk-C546EOEF.js";
189
+ } from "./chunk-QOUTSISS.js";
190
190
  import {
191
191
  LEGACY_PLAN_FILE_REL,
192
192
  PLAN_FILE_NAME,
@@ -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-IG4VU7MT.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
@@ -9303,7 +9303,11 @@ var RemoteHub = class {
9303
9303
  import { WebSocket as WsWebSocket3 } from "ws";
9304
9304
 
9305
9305
  // src/remote/phone-media.ts
9306
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
9307
+ import { join as join6 } from "path";
9306
9308
  var PHONE_IMAGE_MAX_BYTES = 15e5;
9309
+ var PHONE_TEXT_MAX_BYTES = 18e4;
9310
+ var PHONE_DIR_MAX = 200;
9307
9311
  var IMAGE_MIME = {
9308
9312
  png: "image/png",
9309
9313
  jpg: "image/jpeg",
@@ -9350,9 +9354,74 @@ function phoneImageDataUrl(worktreePath, raw) {
9350
9354
  return null;
9351
9355
  }
9352
9356
  }
9353
-
9354
- // src/remote/phone-chats.ts
9355
- 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
+ }
9356
9425
 
9357
9426
  // src/remote/phone-artifact.ts
9358
9427
  var FENCE_RE = /```([a-zA-Z0-9_+-]*)[^\n]*\n([\s\S]*?)(?:```|$)/g;
@@ -9489,6 +9558,309 @@ function parseOpenArtifact(value, chatId) {
9489
9558
  const hint = oneLine(str3(record.hint), 200);
9490
9559
  return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
9491
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-code-refs.ts
9762
+ var MAX_REFS = 8;
9763
+ var MAX_TEXT = 2e4;
9764
+ var MAX_TOTAL = 4e4;
9765
+ function lineNumber(value, key) {
9766
+ const raw = value[key];
9767
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 1e6) return void 0;
9768
+ return raw;
9769
+ }
9770
+ function parsePhoneCodeRefs(value) {
9771
+ if (value == null) return [];
9772
+ if (!Array.isArray(value) || value.length > MAX_REFS) return "invalid";
9773
+ const refs = [];
9774
+ let total = 0;
9775
+ for (const item of value) {
9776
+ if (!item || typeof item !== "object") return "invalid";
9777
+ const pathRaw = item.path;
9778
+ const path2 = typeof pathRaw === "string" ? pathRaw.trim().replace(/\\/g, "/").slice(0, 500) : "";
9779
+ const startLine = lineNumber(item, "startLine");
9780
+ const endLine = lineNumber(item, "endLine");
9781
+ const textRaw = item.text;
9782
+ const text5 = typeof textRaw === "string" ? textRaw.replace(/\n$/, "").slice(0, MAX_TEXT) : "";
9783
+ if (!path2 || !startLine || !endLine || endLine < startLine || !text5.trim()) return "invalid";
9784
+ total += text5.length;
9785
+ if (total > MAX_TOTAL) return "invalid";
9786
+ refs.push({ path: path2, startLine, endLine, text: text5 });
9787
+ }
9788
+ return refs;
9789
+ }
9790
+
9791
+ // src/remote/phone-panes.ts
9792
+ var MAX_ROWS = 200;
9793
+ var MAX_QUERY = 200;
9794
+ function parsePhonePane(value, chatId) {
9795
+ const pane = value && typeof value === "object" ? value.pane : void 0;
9796
+ if (pane !== "files" && pane !== "changes" && pane !== "checks") return "invalid";
9797
+ const scopeRaw = value.scope;
9798
+ const scope = scopeRaw === "uncommitted" || scopeRaw === "commits" ? scopeRaw : void 0;
9799
+ const queryRaw = value.query;
9800
+ const query = typeof queryRaw === "string" ? queryRaw.trim().slice(0, MAX_QUERY) : "";
9801
+ return {
9802
+ op: "pane",
9803
+ chatId,
9804
+ pane,
9805
+ ...scope && pane === "changes" ? { scope } : {},
9806
+ ...query && pane === "files" ? { query } : {}
9807
+ };
9808
+ }
9809
+ function filterPhoneFiles(paths, query) {
9810
+ const q = query.trim().toLowerCase();
9811
+ if (!q) return paths;
9812
+ return paths.filter((path2) => path2.toLowerCase().includes(q));
9813
+ }
9814
+ async function readPhonePane(thread, pane, scope, query) {
9815
+ try {
9816
+ if (pane === "files") return await readFiles(thread.id, query);
9817
+ if (pane === "changes") return await readChanges(thread.id, scope ?? "commits");
9818
+ return await readChecks(thread.id);
9819
+ } catch (err) {
9820
+ const message = err instanceof Error ? err.message : String(err);
9821
+ return { rows: [], note: message };
9822
+ }
9823
+ }
9824
+ async function readFiles(threadId, query) {
9825
+ const paths = filterPhoneFiles(await getOrchestrator().listFiles(threadId), query ?? "");
9826
+ const shown = paths.slice(0, MAX_ROWS);
9827
+ const extra = paths.length - shown.length;
9828
+ const note = extra > 0 ? query?.trim() ? `${extra} more matching files` : `${extra} more files on the Mac` : void 0;
9829
+ return {
9830
+ rows: shown.map((path2) => ({ label: path2, path: path2 })),
9831
+ ...note ? { note } : {}
9832
+ };
9833
+ }
9834
+ async function readChanges(threadId, scope) {
9835
+ const diff = await getOrchestrator().diff(threadId, { includePatches: false, scope });
9836
+ const shown = diff.files.slice(0, MAX_ROWS);
9837
+ return {
9838
+ rows: shown.map((file) => ({
9839
+ label: file.path,
9840
+ path: file.path,
9841
+ detail: changeDetail(file.additions, file.deletions)
9842
+ })),
9843
+ ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {}
9844
+ };
9845
+ }
9846
+ function changeDetail(additions, deletions) {
9847
+ const add = additions ?? 0;
9848
+ const del = deletions ?? 0;
9849
+ if (add === 0 && del === 0) return void 0;
9850
+ return `+${add} \u2212${del}`;
9851
+ }
9852
+ async function readChecks(threadId) {
9853
+ const checks = await getOrchestrator().getPrChecks(threadId);
9854
+ if (!checks) return { rows: [], note: "No pull request on this branch." };
9855
+ const shown = checks.slice(0, MAX_ROWS);
9856
+ return {
9857
+ rows: shown.map((check) => ({
9858
+ label: check.name,
9859
+ detail: check.bucket || check.state,
9860
+ ...check.link ? { href: check.link } : {}
9861
+ }))
9862
+ };
9863
+ }
9492
9864
 
9493
9865
  // src/remote/phone-defaults.ts
9494
9866
  function row(resolved) {
@@ -9589,10 +9961,61 @@ function listPhoneMessages(thread) {
9589
9961
  return live.slice(-TRANSCRIPT_LIMIT);
9590
9962
  }
9591
9963
 
9592
- // src/remote/phone-chats.ts
9593
- var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
9964
+ // src/remote/phone-history.ts
9594
9965
  var PREVIEW_LIMIT = 90;
9595
9966
  var HISTORY_PAGE_SIZE = 40;
9967
+ function previewOf(thread) {
9968
+ const messages = listPhoneMessages(thread);
9969
+ const last = messages[messages.length - 1];
9970
+ if (!last) return "";
9971
+ return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
9972
+ }
9973
+ function repoName(repoPath) {
9974
+ const parts = repoPath.replace(/\/$/, "").split("/");
9975
+ return parts[parts.length - 1] || repoPath;
9976
+ }
9977
+ function isProjectPath(path2) {
9978
+ if (!path2 || path2 === "/" || path2 === ".") return false;
9979
+ return !isGlobalThread({ repoPath: path2 });
9980
+ }
9981
+ function historyStamp(thread) {
9982
+ return thread.archivedAt || thread.updatedAt;
9983
+ }
9984
+ function historyCursor(chat) {
9985
+ return `${chat.updatedAt} ${chat.id}`;
9986
+ }
9987
+ function listPhoneHistory(opts) {
9988
+ const query = opts?.query?.trim().toLowerCase() ?? "";
9989
+ const after = opts?.after?.trim() ?? "";
9990
+ 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) => ({
9991
+ id: thread.id,
9992
+ title: thread.title?.trim() || "Untitled",
9993
+ preview: previewOf(thread),
9994
+ updatedAt: historyStamp(thread),
9995
+ where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
9996
+ agent: thread.agent
9997
+ })).filter((chat) => {
9998
+ if (!query) return true;
9999
+ return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
10000
+ });
10001
+ let start = 0;
10002
+ if (after) {
10003
+ const index = all.findIndex((chat) => historyCursor(chat) === after);
10004
+ start = index >= 0 ? index + 1 : all.length;
10005
+ }
10006
+ const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
10007
+ const last = chats.at(-1);
10008
+ const more = start + chats.length < all.length;
10009
+ return {
10010
+ chats,
10011
+ total: all.length,
10012
+ ...more && last ? { next: historyCursor(last) } : {}
10013
+ };
10014
+ }
10015
+
10016
+ // src/remote/phone-chats.ts
10017
+ var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
10018
+ var PREVIEW_LIMIT2 = 90;
9596
10019
  var AGENT_KINDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "brightsy", "cursor"]);
9597
10020
  var MAX_PHONE_FILES = 8;
9598
10021
  var MAX_PHONE_FILE_CHARS = Math.ceil(8 * 1024 * 1024 * 4 / 3);
@@ -9622,7 +10045,7 @@ function parsePhoneControl(value) {
9622
10045
  }
9623
10046
  if (op === "sources") {
9624
10047
  const repoPath = textField(value, "repoPath");
9625
- if (!repoPath || !isProjectPath(repoPath)) return "invalid";
10048
+ if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
9626
10049
  const query = textField(value, "query");
9627
10050
  return { op: "sources", repoPath, ...query ? { query } : {} };
9628
10051
  }
@@ -9640,7 +10063,15 @@ function parsePhoneControl(value) {
9640
10063
  if (op === "create") return parsePhoneCreate(value);
9641
10064
  const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
9642
10065
  if (!chatId) return "invalid";
9643
- if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
10066
+ if (op === "open" || op === "archive" || op === "restore" || op === "stop" || op === "review" || op === "header") {
10067
+ return { op, chatId };
10068
+ }
10069
+ if (op === "git") {
10070
+ const action = textField(value, "action");
10071
+ if (!PHONE_HEADER_GIT_ACTIONS.includes(action)) return "invalid";
10072
+ return { op: "git", chatId, action };
10073
+ }
10074
+ if (op === "pane") return parsePhonePane(value, chatId);
9644
10075
  if (op === "open-artifact") return parseOpenArtifact(value, chatId);
9645
10076
  if (op === "open-file" || op === "media") {
9646
10077
  const path2 = textField(value, "path");
@@ -9661,7 +10092,7 @@ function parsePhoneControl(value) {
9661
10092
  if (op === "options") return { op: "options", chatId, ...draft };
9662
10093
  if (op === "prompt") {
9663
10094
  const text5 = typeof value.text === "string" ? value.text.trim() : "";
9664
- const hasPayload = Boolean(text5 || draft.files?.length || draft.links?.length);
10095
+ const hasPayload = Boolean(text5 || draft.files?.length || draft.links?.length || draft.refs?.length);
9665
10096
  if (!hasPayload) return "invalid";
9666
10097
  return { op: "prompt", chatId, text: text5, ...draft };
9667
10098
  }
@@ -9725,6 +10156,9 @@ function parsePhoneDraft(value) {
9725
10156
  const links = parsePhoneLinks(value.links);
9726
10157
  if (links === "invalid") return "invalid";
9727
10158
  if (links.length) draft.links = links;
10159
+ const refs = parsePhoneCodeRefs(value.refs);
10160
+ if (refs === "invalid") return "invalid";
10161
+ if (refs.length) draft.refs = refs;
9728
10162
  const prompt = textField(value, "prompt");
9729
10163
  if (prompt) draft.prompt = prompt.slice(0, 2e4);
9730
10164
  return draft;
@@ -9780,7 +10214,7 @@ function parsePhoneCreate(value) {
9780
10214
  }
9781
10215
  if (where === "project") {
9782
10216
  const repoPath = textField(value, "repoPath");
9783
- if (!repoPath || !isProjectPath(repoPath)) return "invalid";
10217
+ if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
9784
10218
  const sourceRaw = textField(value, "sourceType");
9785
10219
  let sourceType;
9786
10220
  if (sourceRaw === "branch" || sourceRaw === "pr" || sourceRaw === "ticket") sourceType = sourceRaw;
@@ -9803,26 +10237,30 @@ function parsePhoneCreate(value) {
9803
10237
  return "invalid";
9804
10238
  }
9805
10239
  function summarize(thread) {
10240
+ const pr = phonePrPill(thread);
9806
10241
  return {
9807
10242
  id: thread.id,
9808
10243
  title: thread.title?.trim() || "Untitled",
9809
10244
  status: thread.status,
9810
- preview: previewOf(thread),
10245
+ preview: previewOf2(thread),
9811
10246
  updatedAt: thread.updatedAt,
9812
- agent: thread.agent
10247
+ agent: thread.agent,
10248
+ ...pr?.url ? { prUrl: pr.url } : {},
10249
+ ...pr?.number ? { prNumber: pr.number } : {},
10250
+ ...pr?.status ? { prStatus: pr.status } : {}
9813
10251
  };
9814
10252
  }
9815
- function previewOf(thread) {
10253
+ function previewOf2(thread) {
9816
10254
  const messages = listPhoneMessages(thread);
9817
10255
  const last = messages[messages.length - 1];
9818
10256
  if (!last) return "";
9819
- return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
10257
+ return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT2);
9820
10258
  }
9821
- function repoName(repoPath) {
10259
+ function repoName2(repoPath) {
9822
10260
  const parts = repoPath.replace(/\/$/, "").split("/");
9823
10261
  return parts[parts.length - 1] || repoPath;
9824
10262
  }
9825
- function isProjectPath(path2) {
10263
+ function isProjectPath2(path2) {
9826
10264
  if (!path2 || path2 === "/" || path2 === ".") return false;
9827
10265
  return !isGlobalThread({ repoPath: path2 });
9828
10266
  }
@@ -9834,17 +10272,17 @@ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
9834
10272
  const threads = listThreads();
9835
10273
  const byRepo = /* @__PURE__ */ new Map();
9836
10274
  for (const thread of threads) {
9837
- if (!isProjectPath(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
10275
+ if (!isProjectPath2(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
9838
10276
  const list = byRepo.get(thread.repoPath) ?? [];
9839
10277
  list.push(thread);
9840
10278
  byRepo.set(thread.repoPath, list);
9841
10279
  }
9842
10280
  for (const workspace of listWorkspaces()) {
9843
- if (!isProjectPath(workspace.path) || byRepo.has(workspace.path)) continue;
10281
+ if (!isProjectPath2(workspace.path) || byRepo.has(workspace.path)) continue;
9844
10282
  byRepo.set(workspace.path, []);
9845
10283
  }
9846
- const projects = [...byRepo.entries()].sort(([a], [b]) => repoName(a).localeCompare(repoName(b))).map(([path2, repoThreads]) => ({
9847
- name: repoName(path2),
10284
+ const projects = [...byRepo.entries()].sort(([a], [b]) => repoName2(a).localeCompare(repoName2(b))).map(([path2, repoThreads]) => ({
10285
+ name: repoName2(path2),
9848
10286
  path: path2,
9849
10287
  worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => {
9850
10288
  const tags = workspaceTagsFromGroup(group);
@@ -9863,41 +10301,6 @@ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
9863
10301
  }));
9864
10302
  return { orchestration: listPhoneChats(), projects, defaults: phoneAccountDefaults() };
9865
10303
  }
9866
- function historyStamp(thread) {
9867
- return thread.archivedAt || thread.updatedAt;
9868
- }
9869
- function historyCursor(chat) {
9870
- return `${chat.updatedAt} ${chat.id}`;
9871
- }
9872
- function historyQueryMatch(chat, query) {
9873
- if (!query) return true;
9874
- return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
9875
- }
9876
- function listPhoneHistory(opts) {
9877
- const query = opts?.query?.trim().toLowerCase() ?? "";
9878
- const after = opts?.after?.trim() ?? "";
9879
- 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) => ({
9880
- id: thread.id,
9881
- title: thread.title?.trim() || "Untitled",
9882
- preview: previewOf(thread),
9883
- updatedAt: historyStamp(thread),
9884
- where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
9885
- agent: thread.agent
9886
- })).filter((chat) => historyQueryMatch(chat, query));
9887
- let start = 0;
9888
- if (after) {
9889
- const index = all.findIndex((chat) => historyCursor(chat) === after);
9890
- start = index >= 0 ? index + 1 : all.length;
9891
- }
9892
- const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
9893
- const last = chats.at(-1);
9894
- const more = start + chats.length < all.length;
9895
- return {
9896
- chats,
9897
- total: all.length,
9898
- ...more && last ? { next: historyCursor(last) } : {}
9899
- };
9900
- }
9901
10304
  function matchPhoneThread(chatId) {
9902
10305
  const id = chatId.trim();
9903
10306
  if (!id) return null;
@@ -9910,7 +10313,7 @@ function matchPhoneThread(chatId) {
9910
10313
  }
9911
10314
  function visiblePhoneThread(thread) {
9912
10315
  if (isGlobalThread(thread)) return true;
9913
- return isProjectPath(thread.repoPath);
10316
+ return isProjectPath2(thread.repoPath);
9914
10317
  }
9915
10318
  function findPhoneChat(chatId) {
9916
10319
  const thread = matchPhoneThread(chatId);
@@ -9976,7 +10379,7 @@ function projectPlace(thread) {
9976
10379
  }
9977
10380
  function assertKnownProject(repoPath) {
9978
10381
  const path2 = repoPath.trim();
9979
- if (!isProjectPath(path2)) throw new Error("Pick a project on this Mac.");
10382
+ if (!isProjectPath2(path2)) throw new Error("Pick a project on this Mac.");
9980
10383
  const known = listWorkspaces().some((workspace) => sameRepoPath(workspace.path, path2)) || listThreads().some(
9981
10384
  (thread) => thread.status !== "archived" && sameRepoPath(thread.repoPath, path2)
9982
10385
  );
@@ -10079,6 +10482,56 @@ function archivePhoneChat(chatId) {
10079
10482
  return true;
10080
10483
  }
10081
10484
 
10485
+ // src/remote/phone-workspace-ops.ts
10486
+ function dispatchPhoneWorkspace(cmd, emit, publishOpened) {
10487
+ if (cmd.op === "pane") {
10488
+ const thread = readPhoneThread(cmd.chatId);
10489
+ if (!thread) {
10490
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10491
+ return;
10492
+ }
10493
+ void readPhonePane(thread, cmd.pane, cmd.scope, cmd.query).then((body) => {
10494
+ emit({
10495
+ op: "pane",
10496
+ chatId: cmd.chatId,
10497
+ pane: cmd.pane,
10498
+ rows: body.rows,
10499
+ ...body.note ? { note: body.note } : {},
10500
+ ...cmd.pane === "files" ? { query: cmd.query ?? "" } : {}
10501
+ });
10502
+ });
10503
+ return;
10504
+ }
10505
+ if (cmd.op === "header") {
10506
+ const thread = readPhoneThread(cmd.chatId);
10507
+ if (!thread) {
10508
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10509
+ return;
10510
+ }
10511
+ void readPhoneHeader(thread).then((header) => {
10512
+ emit({ op: "header", chatId: cmd.chatId, ...header });
10513
+ });
10514
+ return;
10515
+ }
10516
+ if (cmd.op === "git") {
10517
+ void getOrchestrator().askGit(cmd.chatId, cmd.action).then((tab) => publishOpened(tab.id)).catch((err) => {
10518
+ emit({
10519
+ op: "error",
10520
+ chatId: cmd.chatId,
10521
+ message: err instanceof Error ? err.message : String(err)
10522
+ });
10523
+ });
10524
+ return;
10525
+ }
10526
+ void getOrchestrator().requestReview(cmd.chatId).then((tab) => publishOpened(tab.id)).catch((err) => {
10527
+ emit({
10528
+ op: "error",
10529
+ chatId: cmd.chatId,
10530
+ message: err instanceof Error ? err.message : String(err)
10531
+ });
10532
+ });
10533
+ }
10534
+
10082
10535
  // src/remote/phone-sources.ts
10083
10536
  function sourceWarning(label, err) {
10084
10537
  const message = err instanceof Error ? err.message : String(err);
@@ -10443,7 +10896,8 @@ function stagePhoneTurnFiles(worktreePath, draft) {
10443
10896
  worktreePath,
10444
10897
  (draft.files ?? []).map((file) => ({ name: file.name, dataBase64: file.dataBase64 }))
10445
10898
  );
10446
- return [...links, ...files];
10899
+ const refs = (draft.refs ?? []).map((ref) => buildCodeRefAttachment(ref));
10900
+ return [...links, ...files, ...refs];
10447
10901
  }
10448
10902
  function handlePhoneControl(cmd, opts) {
10449
10903
  const log = opts.onLog ?? (() => void 0);
@@ -10644,10 +11098,25 @@ function handlePhoneControl(cmd, opts) {
10644
11098
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file is not in this worktree." });
10645
11099
  return;
10646
11100
  }
10647
- opts.onOpenWorktreeFile?.({
10648
- threadId: thread.id,
11101
+ const preview = phoneFilePreview(thread.worktreePath, relative, kind === "dir" ? "dir" : "file");
11102
+ if (!preview) {
11103
+ emitControl(opts, {
11104
+ op: "error",
11105
+ chatId: cmd.chatId,
11106
+ message: "That file could not be opened on the phone."
11107
+ });
11108
+ return;
11109
+ }
11110
+ emitControl(opts, {
11111
+ op: "file",
11112
+ chatId: cmd.chatId,
10649
11113
  path: relative,
10650
- ...kind === "dir" ? { directory: true } : {},
11114
+ content: preview.content,
11115
+ encoding: preview.encoding,
11116
+ ...preview.binary ? { binary: true } : {},
11117
+ ...preview.directory ? { directory: true } : {},
11118
+ ...preview.truncated ? { truncated: true } : {},
11119
+ ...preview.mime ? { mime: preview.mime } : {},
10651
11120
  ...cmd.startLine != null ? { startLine: cmd.startLine } : {},
10652
11121
  ...cmd.endLine != null ? { endLine: cmd.endLine } : {}
10653
11122
  });
@@ -10662,13 +11131,34 @@ function handlePhoneControl(cmd, opts) {
10662
11131
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
10663
11132
  return;
10664
11133
  }
10665
- opts.onOpenArtifact?.({
10666
- threadId: thread.id,
10667
- title: cmd.title,
10668
- ...cmd.hint ? { hint: cmd.hint } : {}
11134
+ const liveParts = readTurnLiveParts(thread.id);
11135
+ const docs = phoneArtifactDocuments([
11136
+ ...(thread.messages ?? []).filter((message) => message.role === "agent").map((message) => ({ text: message.text.trim(), parts: message.parts })),
11137
+ { text: visibleAssistantText(void 0, liveParts), parts: liveParts }
11138
+ ]);
11139
+ const body = phoneArtifactReply(docs, { title: cmd.title, ...cmd.hint ? { hint: cmd.hint } : {} });
11140
+ if (!body) {
11141
+ emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That artifact is not in this chat." });
11142
+ return;
11143
+ }
11144
+ emitControl(opts, {
11145
+ op: "artifact",
11146
+ chatId: cmd.chatId,
11147
+ title: body.title,
11148
+ kind: body.kind,
11149
+ content: body.content,
11150
+ ...body.truncated ? { truncated: true } : {}
10669
11151
  });
10670
11152
  return;
10671
11153
  }
11154
+ if (cmd.op === "pane" || cmd.op === "header" || cmd.op === "git" || cmd.op === "review") {
11155
+ dispatchPhoneWorkspace(
11156
+ cmd,
11157
+ (reply) => emitControl(opts, reply),
11158
+ (chatId2) => publishOpenedChat(opts, chatId2)
11159
+ );
11160
+ return;
11161
+ }
10672
11162
  if (cmd.op === "stop") {
10673
11163
  if (!readPhoneThread(cmd.chatId)) {
10674
11164
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
@@ -11099,14 +11589,14 @@ import { randomBytes as randomBytes7 } from "crypto";
11099
11589
 
11100
11590
  // src/relay/accounts.ts
11101
11591
  import { createHash as createHash2, randomBytes as randomBytes6, randomUUID as randomUUID8, timingSafeEqual as timingSafeEqual2 } from "crypto";
11102
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, realpathSync } from "fs";
11592
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, realpathSync } from "fs";
11103
11593
  import { createRequire } from "module";
11104
- import { dirname, join as join6 } from "path";
11594
+ import { dirname, join as join7 } from "path";
11105
11595
  var GITHUB_ACCOUNT_ISSUER = "https://github.com";
11106
11596
  var RELAY_DEVICE_CAP = 3;
11107
11597
  function thisModuleFile() {
11108
11598
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
11109
- return cjsFile || process.argv[1] || join6(process.cwd(), "package.json");
11599
+ return cjsFile || process.argv[1] || join7(process.cwd(), "package.json");
11110
11600
  }
11111
11601
  function openDatabase(filePath) {
11112
11602
  const req = createRequire(thisModuleFile());
@@ -11144,7 +11634,7 @@ function assertRelayAccountsDurable(filePath) {
11144
11634
  if (process.env.NODE_ENV !== "production") return;
11145
11635
  if (process.platform !== "linux") return;
11146
11636
  const dir = realpathSync(dirname(filePath));
11147
- const mounts = readFileSync5("/proc/mounts", "utf8");
11637
+ const mounts = readFileSync6("/proc/mounts", "utf8");
11148
11638
  const point = longestMountPoint(dir, mounts);
11149
11639
  if (point === "/") {
11150
11640
  throw new Error(
@@ -12621,8 +13111,8 @@ async function startRelayServer(opts) {
12621
13111
  }
12622
13112
 
12623
13113
  // src/slack/outbound-watch.ts
12624
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
12625
- import { join as join7 } from "path";
13114
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
13115
+ import { join as join8 } from "path";
12626
13116
  var MAX_WATCHES = 40;
12627
13117
  var MAX_REPLIES_PER_WATCH = 30;
12628
13118
  var WATCH_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -12630,7 +13120,7 @@ var POLL_INTERVAL_MS = 12e3;
12630
13120
  var lastPollMs = 0;
12631
13121
  var nameCache = /* @__PURE__ */ new Map();
12632
13122
  function storePath3() {
12633
- return join7(appDataDir(), "slack-outbound-watch.json");
13123
+ return join8(appDataDir(), "slack-outbound-watch.json");
12634
13124
  }
12635
13125
  function watchId(teamId, channelId, ts) {
12636
13126
  return `${teamId}:${channelId}:${ts}`;
@@ -12645,7 +13135,7 @@ function readStore3() {
12645
13135
  const path2 = storePath3();
12646
13136
  if (!existsSync5(path2)) return [];
12647
13137
  try {
12648
- const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync6(path2, "utf8"));
13138
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync7(path2, "utf8"));
12649
13139
  return Array.isArray(parsed?.watches) ? parsed.watches : [];
12650
13140
  } catch {
12651
13141
  return [];
@@ -12697,7 +13187,7 @@ async function continueSourceThread(threadId, prompt) {
12697
13187
  await continueOnReply(threadId, prompt);
12698
13188
  return;
12699
13189
  }
12700
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-IG4VU7MT.js");
13190
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-GSOW3RPA.js");
12701
13191
  await getOrchestrator2().send(threadId, prompt);
12702
13192
  } catch {
12703
13193
  }