@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.cjs CHANGED
@@ -26376,12 +26376,13 @@ function formatArtifactDirective() {
26376
26376
  "Sideboard side column (desktop UI) \u2014 Sideboard MCP tools present_artifact, present_schema, present_files, ask_user (see their descriptions). claude.ai\u2019s \u201CArtifact\u201D tool does not exist here; never say artifacts, the CMS UI, or the Files column are unavailable.",
26377
26377
  "- Standalone documents: a fenced block tagged `html` (preferred), `svg`, or `markdown` containing the FULL document opens the side column by itself. Use that or present_artifact \u2014 never both for the same body.",
26378
26378
  "- present_artifact type=log appends: same artifact_id, content = new lines only (plus status/phase). Do not resend the full log or wrap it in HTML.",
26379
+ "- Chat links open the side column and files: `[label](sideboard://artifact/<artifact_id or title>)` opens that pane; `[label](sideboard://file/<path>#L12-L40)` opens the file (line range optional). A repo-relative markdown link opens the file too.",
26379
26380
  "- Data: a markdown table is enough to read. Call present_schema only when the user needs to filter/edit/publish/persist rows \u2014 including when they ask for an editable table after you already showed markdown. Never re-present rows you already wrote just to display them.",
26380
26381
  "- ask_user only when work is blocked on a few concrete options (approach fork, which API, auth vs cookies): first a short chat message explaining the decision and each option, then the call (description on every option), then stop and wait. Not for greetings, check-ins, an invented menu of next tasks, or after a review \u2014 reply in chat. If one option is the obvious default, proceed. ask_user already notifies the parent orchestrator. Call notify_orchestrator only when blocked without a picker."
26381
26382
  ].join("\n");
26382
26383
  }
26383
26384
  function formatUiReminder() {
26384
- return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos, \u201Cwhat next?\u201D, or after a review) \u2014 reply in chat. ask_user wakes the parent orchestrator; notify_orchestrator if blocked without a picker. Do not say artifacts/CMS UI are unavailable. Dev / preview: run_dev_script (same as the Dev button; pass name \u2014 several scripts can run at once) returns {url, port, preview} (SIDEBOARD_PORT \u2014 never guess :3000). preview=url \u2192 open url; preview=window \u2192 Electron window is the app, url is renderer/HMR only. list_run_scripts active[].url if already running; get_run_log for that script\u2019s Run-tab terminal; stop_dev_script to stop one.";
26385
+ return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). Chat links: `[name](sideboard://artifact/<id-or-title>)` opens that pane; `[name](sideboard://file/path#L12-L40)` opens a file. ask_user only for a real multiple-choice (not hellos, \u201Cwhat next?\u201D, or after a review) \u2014 reply in chat. ask_user wakes the parent orchestrator; notify_orchestrator if blocked without a picker. Do not say artifacts/CMS UI are unavailable. Dev / preview: run_dev_script (same as the Dev button; pass name \u2014 several scripts can run at once) returns {url, port, preview} (SIDEBOARD_PORT \u2014 never guess :3000). preview=url \u2192 open url; preview=window \u2192 Electron window is the app, url is renderer/HMR only. list_run_scripts active[].url if already running; get_run_log for that script\u2019s Run-tab terminal; stop_dev_script to stop one.";
26385
26386
  }
26386
26387
  var import_node_fs63, import_node_path60, PR_GOAL_RE, GITHUB_TICKET_REF, KEYED_TICKET_REF;
26387
26388
  var init_instructions = __esm({
@@ -40026,10 +40027,16 @@ init_stage_files();
40026
40027
  init_orchestrator();
40027
40028
  init_app_settings();
40028
40029
  init_implied_first_prompt();
40030
+ init_message_parts();
40031
+ init_turn_live();
40029
40032
 
40030
40033
  // src/remote/phone-media.ts
40034
+ var import_node_fs71 = require("fs");
40035
+ var import_node_path69 = require("path");
40031
40036
  init_diff();
40032
40037
  var PHONE_IMAGE_MAX_BYTES = 15e5;
40038
+ var PHONE_TEXT_MAX_BYTES = 18e4;
40039
+ var PHONE_DIR_MAX = 200;
40033
40040
  var IMAGE_MIME = {
40034
40041
  png: "image/png",
40035
40042
  jpg: "image/jpeg",
@@ -40076,21 +40083,74 @@ function phoneImageDataUrl(worktreePath, raw) {
40076
40083
  return null;
40077
40084
  }
40078
40085
  }
40079
-
40080
- // src/remote/phone-chats.ts
40081
- var import_node_crypto26 = require("crypto");
40082
- init_list_models();
40083
- init_home_board();
40084
- init_stage_files();
40085
- init_worktree_labels();
40086
- init_thinking_effort();
40087
- init_app_settings();
40088
- init_global_workspace();
40089
- init_thread_store();
40090
- init_workspaces();
40091
- init_chat_tabs();
40092
- init_create();
40093
- init_orchestrator();
40086
+ function phoneDirectoryListing(worktreePath, relative) {
40087
+ try {
40088
+ const entries = (0, import_node_fs71.readdirSync)((0, import_node_path69.join)(worktreePath, relative), { withFileTypes: true });
40089
+ const dirs = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
40090
+ const names = entries.map((entry) => entry.name).filter((name) => name && !name.includes("/") && !name.includes("\0")).sort((a, b) => a.localeCompare(b));
40091
+ const shown = names.slice(0, PHONE_DIR_MAX);
40092
+ const lines = shown.map((name) => dirs.has(name) ? `${name}/` : name);
40093
+ const truncated = names.length > shown.length;
40094
+ if (truncated) lines.push(`\u2026 ${names.length - shown.length} more`);
40095
+ return { content: lines.join("\n"), truncated };
40096
+ } catch {
40097
+ return null;
40098
+ }
40099
+ }
40100
+ function phoneFilePreview(worktreePath, relative, kind) {
40101
+ if (kind === "dir") {
40102
+ const listing = phoneDirectoryListing(worktreePath, relative);
40103
+ if (!listing) return null;
40104
+ return {
40105
+ content: listing.content,
40106
+ encoding: "utf8",
40107
+ binary: false,
40108
+ directory: true,
40109
+ truncated: listing.truncated
40110
+ };
40111
+ }
40112
+ if (relative.toLowerCase().endsWith(".svg")) {
40113
+ try {
40114
+ const buf = (0, import_node_fs71.readFileSync)((0, import_node_path69.join)(worktreePath, relative));
40115
+ const truncated = buf.length > PHONE_TEXT_MAX_BYTES;
40116
+ return {
40117
+ content: buf.subarray(0, PHONE_TEXT_MAX_BYTES).toString("utf8"),
40118
+ encoding: "utf8",
40119
+ binary: false,
40120
+ directory: false,
40121
+ truncated,
40122
+ mime: "image/svg+xml"
40123
+ };
40124
+ } catch {
40125
+ return null;
40126
+ }
40127
+ }
40128
+ const mime = phoneImageMime(relative);
40129
+ if (mime) {
40130
+ const dataUrl = phoneImageDataUrl(worktreePath, relative);
40131
+ if (!dataUrl) return null;
40132
+ return {
40133
+ content: dataUrl,
40134
+ encoding: "base64",
40135
+ binary: true,
40136
+ directory: false,
40137
+ truncated: false,
40138
+ mime
40139
+ };
40140
+ }
40141
+ try {
40142
+ const file = readWorktreeFile(worktreePath, relative, { maxBytes: PHONE_TEXT_MAX_BYTES });
40143
+ return {
40144
+ content: file.content,
40145
+ encoding: file.encoding,
40146
+ binary: file.binary,
40147
+ directory: false,
40148
+ truncated: file.truncated
40149
+ };
40150
+ } catch {
40151
+ return null;
40152
+ }
40153
+ }
40094
40154
 
40095
40155
  // src/remote/phone-artifact.ts
40096
40156
  var FENCE_RE = /```([a-zA-Z0-9_+-]*)[^\n]*\n([\s\S]*?)(?:```|$)/g;
@@ -40227,6 +40287,327 @@ function parseOpenArtifact(value, chatId) {
40227
40287
  const hint = oneLine(str6(record.hint), 200);
40228
40288
  return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
40229
40289
  }
40290
+ var PHONE_ARTIFACT_MAX_CHARS = 18e4;
40291
+ function phoneArtifactDocuments(sources) {
40292
+ const docs = [];
40293
+ for (const source of sources) {
40294
+ const hints = /* @__PURE__ */ new Set();
40295
+ for (const match of source.text.matchAll(new RegExp(FENCE_RE.source, "g"))) {
40296
+ const language = match[1] ?? "";
40297
+ const content = match[2] ?? "";
40298
+ const kind = kindFor(language, content);
40299
+ if (!kind) continue;
40300
+ const hint = artifactHint(content);
40301
+ if (hint) hints.add(hint);
40302
+ docs.push({ title: titleFor(kind, content), content, kind });
40303
+ }
40304
+ for (const part of source.parts ?? []) {
40305
+ if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
40306
+ const input = asRecord14(part.input);
40307
+ const content = toolContent(input);
40308
+ const hint = artifactHint(content);
40309
+ if (hint && hints.has(hint)) continue;
40310
+ const typed = kindFor(str6(input?.type), content) ?? kindFor("", content);
40311
+ const title = oneLine(str6(input?.title), 80) || (typed ? titleFor(typed, content) : "Artifact");
40312
+ if (!str6(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
40313
+ if (hint) hints.add(hint);
40314
+ docs.push({ title, content, kind: typed ?? "text" });
40315
+ }
40316
+ }
40317
+ return docs;
40318
+ }
40319
+ function phoneArtifactReply(docs, request) {
40320
+ const match = pickArtifactMatch(docs, request);
40321
+ if (!match) return null;
40322
+ const truncated = match.content.length > PHONE_ARTIFACT_MAX_CHARS;
40323
+ return {
40324
+ title: match.title,
40325
+ kind: match.kind,
40326
+ content: truncated ? match.content.slice(0, PHONE_ARTIFACT_MAX_CHARS) : match.content,
40327
+ truncated
40328
+ };
40329
+ }
40330
+ function newest(items) {
40331
+ return items.length ? items[items.length - 1] ?? null : null;
40332
+ }
40333
+ function pickArtifactMatch(items, request) {
40334
+ const hint = request.hint?.trim();
40335
+ const title = request.title.trim();
40336
+ if (hint) {
40337
+ const exact = items.filter((item) => item.content && artifactHint(item.content) === hint);
40338
+ const exactTitled = title ? exact.filter((item) => item.title === title) : [];
40339
+ const exactMatch = newest(exactTitled.length ? exactTitled : exact);
40340
+ if (exactMatch) return exactMatch;
40341
+ if (title) {
40342
+ for (let i = items.length - 1; i >= 0; i--) {
40343
+ const item = items[i];
40344
+ if (!item?.content || item.title !== title) continue;
40345
+ if (normalizedBody(item.content).startsWith(hint)) return item;
40346
+ }
40347
+ }
40348
+ }
40349
+ if (!title) return null;
40350
+ for (let i = items.length - 1; i >= 0; i--) {
40351
+ if (items[i]?.title === title) return items[i] ?? null;
40352
+ }
40353
+ return null;
40354
+ }
40355
+
40356
+ // src/remote/phone-workspace-ops.ts
40357
+ init_orchestrator();
40358
+
40359
+ // src/remote/phone-chats.ts
40360
+ var import_node_crypto26 = require("crypto");
40361
+ init_list_models();
40362
+ init_home_board();
40363
+ init_stage_files();
40364
+ init_worktree_labels();
40365
+ init_thinking_effort();
40366
+ init_app_settings();
40367
+ init_global_workspace();
40368
+ init_thread_store();
40369
+ init_workspaces();
40370
+ init_chat_tabs();
40371
+ init_create();
40372
+ init_orchestrator();
40373
+
40374
+ // src/remote/phone-header.ts
40375
+ init_orchestrator();
40376
+ init_global_workspace();
40377
+ var PHONE_HEADER_GIT_ACTIONS = [
40378
+ "commit-push",
40379
+ "create-draft",
40380
+ "resolve-conflicts",
40381
+ "ready-for-review",
40382
+ "merge"
40383
+ ];
40384
+ var HIDDEN = {
40385
+ visible: false,
40386
+ branch: "",
40387
+ additions: 0,
40388
+ deletions: 0,
40389
+ unpushed: 0,
40390
+ dirty: false,
40391
+ ready: false,
40392
+ title: ""
40393
+ };
40394
+ function phoneHeaderFrom(thread, diff) {
40395
+ if (isGlobalThread(thread)) return HIDDEN;
40396
+ const branch = thread.branchName.trim().replace(/^thread\//, "");
40397
+ if (!branch) return HIDDEN;
40398
+ const ready = diff != null;
40399
+ const uncommitted = diff?.scopeStats?.uncommitted;
40400
+ const additions = ready ? Math.max(0, Math.trunc(uncommitted?.additions ?? 0)) : 0;
40401
+ const deletions = ready ? Math.max(0, Math.trunc(uncommitted?.deletions ?? 0)) : 0;
40402
+ const unpushed = ready ? Math.max(0, Math.trunc(diff?.unpushed ?? 0)) : 0;
40403
+ const dirty = ready && (Boolean(diff?.dirty) || additions > 0 || deletions > 0);
40404
+ const pr = prSnapshot(thread);
40405
+ const action = ready ? headerAction(pr, { dirty: Boolean(diff?.dirty), unpushed }) : null;
40406
+ const lines = [branch];
40407
+ if (!ready) lines.push("Loading git status\u2026");
40408
+ else if (dirty) lines.push(`Uncommitted +${additions} \u2212${deletions}`);
40409
+ else lines.push("Working tree clean");
40410
+ if (unpushed > 0) lines.push(`${unpushed} unpushed commit${unpushed === 1 ? "" : "s"}`);
40411
+ return {
40412
+ visible: true,
40413
+ branch,
40414
+ additions,
40415
+ deletions,
40416
+ unpushed,
40417
+ dirty,
40418
+ ready,
40419
+ title: lines.join("\n"),
40420
+ ...pr.url ? { prUrl: pr.url } : {},
40421
+ ...pr.number ? { prNumber: pr.number } : {},
40422
+ ...pr.status ? { prStatus: pr.status } : {},
40423
+ ...action ? { action: action.id, actionLabel: action.label } : {}
40424
+ };
40425
+ }
40426
+ function phonePrPill(thread) {
40427
+ const pr = prSnapshot(thread);
40428
+ if (!pr.url) return null;
40429
+ return { url: pr.url, number: pr.number, status: pr.status };
40430
+ }
40431
+ async function readPhoneHeader(thread) {
40432
+ const pending = phoneHeaderFrom(thread, null);
40433
+ if (!pending.visible) return pending;
40434
+ try {
40435
+ const diff = await getOrchestrator().diff(thread.id, { includePatches: false });
40436
+ return phoneHeaderFrom(thread, diff);
40437
+ } catch {
40438
+ return pending;
40439
+ }
40440
+ }
40441
+ function prSnapshot(thread) {
40442
+ const url = thread.prUrl?.trim() || "";
40443
+ const number = url.match(/\/pull\/(\d+)/)?.[1] ?? "";
40444
+ const state = (thread.prState ?? "").toUpperCase();
40445
+ const merged = state === "MERGED";
40446
+ const closed = state === "CLOSED";
40447
+ const open = Boolean(url) && !merged && !closed;
40448
+ const draft = open && Boolean(thread.prIsDraft);
40449
+ const inMergeQueue = open && Boolean(thread.prIsInMergeQueue);
40450
+ const mergeable = (thread.prMergeable ?? "").toUpperCase();
40451
+ const mergeState = (thread.prMergeStateStatus ?? "").toUpperCase();
40452
+ const mergeConflicts = open && !inMergeQueue && (mergeable === "CONFLICTING" || mergeState === "DIRTY");
40453
+ const branchBehind = open && !inMergeQueue && !mergeConflicts && mergeState === "BEHIND";
40454
+ const checksFailed = Boolean(thread.prChecksFailed);
40455
+ const review = (open && !draft ? thread.prReviewDecision : "") ?? "";
40456
+ return {
40457
+ url,
40458
+ number,
40459
+ status: pillStatus({ url, merged, closed, draft, inMergeQueue, mergeConflicts, branchBehind, checksFailed, review }),
40460
+ merged,
40461
+ closed,
40462
+ open,
40463
+ draft,
40464
+ cowboy: Boolean(thread.cowboy),
40465
+ inMergeQueue,
40466
+ mergeConflicts,
40467
+ branchBehind,
40468
+ checksFailed,
40469
+ review: review.toUpperCase()
40470
+ };
40471
+ }
40472
+ function pillStatus(pr) {
40473
+ if (!pr.url) return "";
40474
+ if (pr.merged) return "Merged";
40475
+ if (pr.closed) return "Closed";
40476
+ if (pr.inMergeQueue) return "Queued";
40477
+ if (pr.mergeConflicts) return "Merge conflicts";
40478
+ if (pr.branchBehind) return "Behind base";
40479
+ if (pr.draft) return "Draft";
40480
+ if (pr.checksFailed) return "Checks failing";
40481
+ if (pr.review === "CHANGES_REQUESTED") return "Changes requested";
40482
+ if (pr.review !== "APPROVED") return "Needs approval";
40483
+ return "Approved";
40484
+ }
40485
+ function headerAction(pr, diff) {
40486
+ const hasLocalChanges = diff.dirty || diff.unpushed > 0;
40487
+ const originInSync = !hasLocalChanges;
40488
+ if (pr.merged) return null;
40489
+ if (pr.closed) return { id: "open-pr", label: "Closed" };
40490
+ if (pr.cowboy) {
40491
+ return hasLocalChanges ? { id: "commit-push", label: "Commit & push" } : { id: "commit-push", label: "Push" };
40492
+ }
40493
+ if (!pr.url) return { id: "create-draft", label: "Create PR" };
40494
+ if (pr.inMergeQueue) return { id: "open-pr", label: "Queued" };
40495
+ if (pr.mergeConflicts) return { id: "resolve-conflicts", label: "Resolve" };
40496
+ if (pr.branchBehind) return { id: "resolve-conflicts", label: "Update" };
40497
+ if (hasLocalChanges) return { id: "commit-push", label: "Commit & push" };
40498
+ if (pr.draft) {
40499
+ return originInSync ? { id: "ready-for-review", label: "Ready for review" } : { id: "commit-push", label: "Commit & push" };
40500
+ }
40501
+ if (pr.checksFailed) return { id: "open-checks", label: "Checks failing" };
40502
+ if (pr.review === "CHANGES_REQUESTED") return { id: "open-pr", label: "Changes requested" };
40503
+ if (pr.review !== "APPROVED") return { id: "open-pr", label: "Needs approval" };
40504
+ return { id: "merge", label: "Merge" };
40505
+ }
40506
+
40507
+ // src/remote/phone-code-refs.ts
40508
+ var MAX_REFS = 8;
40509
+ var MAX_TEXT = 2e4;
40510
+ var MAX_TOTAL = 4e4;
40511
+ function lineNumber(value, key) {
40512
+ const raw = value[key];
40513
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 1e6) return void 0;
40514
+ return raw;
40515
+ }
40516
+ function parsePhoneCodeRefs(value) {
40517
+ if (value == null) return [];
40518
+ if (!Array.isArray(value) || value.length > MAX_REFS) return "invalid";
40519
+ const refs = [];
40520
+ let total = 0;
40521
+ for (const item of value) {
40522
+ if (!item || typeof item !== "object") return "invalid";
40523
+ const pathRaw = item.path;
40524
+ const path2 = typeof pathRaw === "string" ? pathRaw.trim().replace(/\\/g, "/").slice(0, 500) : "";
40525
+ const startLine = lineNumber(item, "startLine");
40526
+ const endLine = lineNumber(item, "endLine");
40527
+ const textRaw = item.text;
40528
+ const text7 = typeof textRaw === "string" ? textRaw.replace(/\n$/, "").slice(0, MAX_TEXT) : "";
40529
+ if (!path2 || !startLine || !endLine || endLine < startLine || !text7.trim()) return "invalid";
40530
+ total += text7.length;
40531
+ if (total > MAX_TOTAL) return "invalid";
40532
+ refs.push({ path: path2, startLine, endLine, text: text7 });
40533
+ }
40534
+ return refs;
40535
+ }
40536
+
40537
+ // src/remote/phone-panes.ts
40538
+ init_orchestrator();
40539
+ var MAX_ROWS = 200;
40540
+ var MAX_QUERY = 200;
40541
+ function parsePhonePane(value, chatId) {
40542
+ const pane = value && typeof value === "object" ? value.pane : void 0;
40543
+ if (pane !== "files" && pane !== "changes" && pane !== "checks") return "invalid";
40544
+ const scopeRaw = value.scope;
40545
+ const scope = scopeRaw === "uncommitted" || scopeRaw === "commits" ? scopeRaw : void 0;
40546
+ const queryRaw = value.query;
40547
+ const query = typeof queryRaw === "string" ? queryRaw.trim().slice(0, MAX_QUERY) : "";
40548
+ return {
40549
+ op: "pane",
40550
+ chatId,
40551
+ pane,
40552
+ ...scope && pane === "changes" ? { scope } : {},
40553
+ ...query && pane === "files" ? { query } : {}
40554
+ };
40555
+ }
40556
+ function filterPhoneFiles(paths, query) {
40557
+ const q = query.trim().toLowerCase();
40558
+ if (!q) return paths;
40559
+ return paths.filter((path2) => path2.toLowerCase().includes(q));
40560
+ }
40561
+ async function readPhonePane(thread, pane, scope, query) {
40562
+ try {
40563
+ if (pane === "files") return await readFiles(thread.id, query);
40564
+ if (pane === "changes") return await readChanges(thread.id, scope ?? "commits");
40565
+ return await readChecks(thread.id);
40566
+ } catch (err) {
40567
+ const message = err instanceof Error ? err.message : String(err);
40568
+ return { rows: [], note: message };
40569
+ }
40570
+ }
40571
+ async function readFiles(threadId, query) {
40572
+ const paths = filterPhoneFiles(await getOrchestrator().listFiles(threadId), query ?? "");
40573
+ const shown = paths.slice(0, MAX_ROWS);
40574
+ const extra = paths.length - shown.length;
40575
+ const note = extra > 0 ? query?.trim() ? `${extra} more matching files` : `${extra} more files on the Mac` : void 0;
40576
+ return {
40577
+ rows: shown.map((path2) => ({ label: path2, path: path2 })),
40578
+ ...note ? { note } : {}
40579
+ };
40580
+ }
40581
+ async function readChanges(threadId, scope) {
40582
+ const diff = await getOrchestrator().diff(threadId, { includePatches: false, scope });
40583
+ const shown = diff.files.slice(0, MAX_ROWS);
40584
+ return {
40585
+ rows: shown.map((file) => ({
40586
+ label: file.path,
40587
+ path: file.path,
40588
+ detail: changeDetail(file.additions, file.deletions)
40589
+ })),
40590
+ ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {}
40591
+ };
40592
+ }
40593
+ function changeDetail(additions, deletions) {
40594
+ const add = additions ?? 0;
40595
+ const del = deletions ?? 0;
40596
+ if (add === 0 && del === 0) return void 0;
40597
+ return `+${add} \u2212${del}`;
40598
+ }
40599
+ async function readChecks(threadId) {
40600
+ const checks = await getOrchestrator().getPrChecks(threadId);
40601
+ if (!checks) return { rows: [], note: "No pull request on this branch." };
40602
+ const shown = checks.slice(0, MAX_ROWS);
40603
+ return {
40604
+ rows: shown.map((check) => ({
40605
+ label: check.name,
40606
+ detail: check.bucket || check.state,
40607
+ ...check.link ? { href: check.link } : {}
40608
+ }))
40609
+ };
40610
+ }
40230
40611
 
40231
40612
  // src/remote/phone-defaults.ts
40232
40613
  init_app_settings();
@@ -40332,10 +40713,63 @@ function listPhoneMessages(thread) {
40332
40713
  return live.slice(-TRANSCRIPT_LIMIT);
40333
40714
  }
40334
40715
 
40335
- // src/remote/phone-chats.ts
40336
- var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
40716
+ // src/remote/phone-history.ts
40717
+ init_global_workspace();
40718
+ init_thread_store();
40337
40719
  var PREVIEW_LIMIT = 90;
40338
40720
  var HISTORY_PAGE_SIZE = 40;
40721
+ function previewOf(thread) {
40722
+ const messages = listPhoneMessages(thread);
40723
+ const last = messages[messages.length - 1];
40724
+ if (!last) return "";
40725
+ return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
40726
+ }
40727
+ function repoName(repoPath) {
40728
+ const parts = repoPath.replace(/\/$/, "").split("/");
40729
+ return parts[parts.length - 1] || repoPath;
40730
+ }
40731
+ function isProjectPath(path2) {
40732
+ if (!path2 || path2 === "/" || path2 === ".") return false;
40733
+ return !isGlobalThread({ repoPath: path2 });
40734
+ }
40735
+ function historyStamp(thread) {
40736
+ return thread.archivedAt || thread.updatedAt;
40737
+ }
40738
+ function historyCursor(chat) {
40739
+ return `${chat.updatedAt} ${chat.id}`;
40740
+ }
40741
+ function listPhoneHistory(opts) {
40742
+ const query = opts?.query?.trim().toLowerCase() ?? "";
40743
+ const after = opts?.after?.trim() ?? "";
40744
+ 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) => ({
40745
+ id: thread.id,
40746
+ title: thread.title?.trim() || "Untitled",
40747
+ preview: previewOf(thread),
40748
+ updatedAt: historyStamp(thread),
40749
+ where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
40750
+ agent: thread.agent
40751
+ })).filter((chat) => {
40752
+ if (!query) return true;
40753
+ return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
40754
+ });
40755
+ let start = 0;
40756
+ if (after) {
40757
+ const index = all.findIndex((chat) => historyCursor(chat) === after);
40758
+ start = index >= 0 ? index + 1 : all.length;
40759
+ }
40760
+ const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
40761
+ const last = chats.at(-1);
40762
+ const more = start + chats.length < all.length;
40763
+ return {
40764
+ chats,
40765
+ total: all.length,
40766
+ ...more && last ? { next: historyCursor(last) } : {}
40767
+ };
40768
+ }
40769
+
40770
+ // src/remote/phone-chats.ts
40771
+ var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
40772
+ var PREVIEW_LIMIT2 = 90;
40339
40773
  var AGENT_KINDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "brightsy", "cursor"]);
40340
40774
  var MAX_PHONE_FILES = 8;
40341
40775
  var MAX_PHONE_FILE_CHARS = Math.ceil(8 * 1024 * 1024 * 4 / 3);
@@ -40365,7 +40799,7 @@ function parsePhoneControl(value) {
40365
40799
  }
40366
40800
  if (op === "sources") {
40367
40801
  const repoPath = textField(value, "repoPath");
40368
- if (!repoPath || !isProjectPath(repoPath)) return "invalid";
40802
+ if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
40369
40803
  const query = textField(value, "query");
40370
40804
  return { op: "sources", repoPath, ...query ? { query } : {} };
40371
40805
  }
@@ -40383,7 +40817,15 @@ function parsePhoneControl(value) {
40383
40817
  if (op === "create") return parsePhoneCreate(value);
40384
40818
  const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
40385
40819
  if (!chatId) return "invalid";
40386
- if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
40820
+ if (op === "open" || op === "archive" || op === "restore" || op === "stop" || op === "review" || op === "header") {
40821
+ return { op, chatId };
40822
+ }
40823
+ if (op === "git") {
40824
+ const action = textField(value, "action");
40825
+ if (!PHONE_HEADER_GIT_ACTIONS.includes(action)) return "invalid";
40826
+ return { op: "git", chatId, action };
40827
+ }
40828
+ if (op === "pane") return parsePhonePane(value, chatId);
40387
40829
  if (op === "open-artifact") return parseOpenArtifact(value, chatId);
40388
40830
  if (op === "open-file" || op === "media") {
40389
40831
  const path2 = textField(value, "path");
@@ -40404,7 +40846,7 @@ function parsePhoneControl(value) {
40404
40846
  if (op === "options") return { op: "options", chatId, ...draft };
40405
40847
  if (op === "prompt") {
40406
40848
  const text7 = typeof value.text === "string" ? value.text.trim() : "";
40407
- const hasPayload = Boolean(text7 || draft.files?.length || draft.links?.length);
40849
+ const hasPayload = Boolean(text7 || draft.files?.length || draft.links?.length || draft.refs?.length);
40408
40850
  if (!hasPayload) return "invalid";
40409
40851
  return { op: "prompt", chatId, text: text7, ...draft };
40410
40852
  }
@@ -40468,6 +40910,9 @@ function parsePhoneDraft(value) {
40468
40910
  const links = parsePhoneLinks(value.links);
40469
40911
  if (links === "invalid") return "invalid";
40470
40912
  if (links.length) draft.links = links;
40913
+ const refs = parsePhoneCodeRefs(value.refs);
40914
+ if (refs === "invalid") return "invalid";
40915
+ if (refs.length) draft.refs = refs;
40471
40916
  const prompt = textField(value, "prompt");
40472
40917
  if (prompt) draft.prompt = prompt.slice(0, 2e4);
40473
40918
  return draft;
@@ -40523,7 +40968,7 @@ function parsePhoneCreate(value) {
40523
40968
  }
40524
40969
  if (where === "project") {
40525
40970
  const repoPath = textField(value, "repoPath");
40526
- if (!repoPath || !isProjectPath(repoPath)) return "invalid";
40971
+ if (!repoPath || !isProjectPath2(repoPath)) return "invalid";
40527
40972
  const sourceRaw = textField(value, "sourceType");
40528
40973
  let sourceType;
40529
40974
  if (sourceRaw === "branch" || sourceRaw === "pr" || sourceRaw === "ticket") sourceType = sourceRaw;
@@ -40546,26 +40991,30 @@ function parsePhoneCreate(value) {
40546
40991
  return "invalid";
40547
40992
  }
40548
40993
  function summarize(thread) {
40994
+ const pr = phonePrPill(thread);
40549
40995
  return {
40550
40996
  id: thread.id,
40551
40997
  title: thread.title?.trim() || "Untitled",
40552
40998
  status: thread.status,
40553
- preview: previewOf(thread),
40999
+ preview: previewOf2(thread),
40554
41000
  updatedAt: thread.updatedAt,
40555
- agent: thread.agent
41001
+ agent: thread.agent,
41002
+ ...pr?.url ? { prUrl: pr.url } : {},
41003
+ ...pr?.number ? { prNumber: pr.number } : {},
41004
+ ...pr?.status ? { prStatus: pr.status } : {}
40556
41005
  };
40557
41006
  }
40558
- function previewOf(thread) {
41007
+ function previewOf2(thread) {
40559
41008
  const messages = listPhoneMessages(thread);
40560
41009
  const last = messages[messages.length - 1];
40561
41010
  if (!last) return "";
40562
- return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT);
41011
+ return clip(last.text.replace(/\s+/g, " ").trim(), PREVIEW_LIMIT2);
40563
41012
  }
40564
- function repoName(repoPath) {
41013
+ function repoName2(repoPath) {
40565
41014
  const parts = repoPath.replace(/\/$/, "").split("/");
40566
41015
  return parts[parts.length - 1] || repoPath;
40567
41016
  }
40568
- function isProjectPath(path2) {
41017
+ function isProjectPath2(path2) {
40569
41018
  if (!path2 || path2 === "/" || path2 === ".") return false;
40570
41019
  return !isGlobalThread({ repoPath: path2 });
40571
41020
  }
@@ -40577,17 +41026,17 @@ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
40577
41026
  const threads = listThreads();
40578
41027
  const byRepo = /* @__PURE__ */ new Map();
40579
41028
  for (const thread of threads) {
40580
- if (!isProjectPath(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
41029
+ if (!isProjectPath2(thread.repoPath) || isRemovedWorkspace(thread.repoPath)) continue;
40581
41030
  const list = byRepo.get(thread.repoPath) ?? [];
40582
41031
  list.push(thread);
40583
41032
  byRepo.set(thread.repoPath, list);
40584
41033
  }
40585
41034
  for (const workspace of listWorkspaces()) {
40586
- if (!isProjectPath(workspace.path) || byRepo.has(workspace.path)) continue;
41035
+ if (!isProjectPath2(workspace.path) || byRepo.has(workspace.path)) continue;
40587
41036
  byRepo.set(workspace.path, []);
40588
41037
  }
40589
- const projects = [...byRepo.entries()].sort(([a], [b]) => repoName(a).localeCompare(repoName(b))).map(([path2, repoThreads]) => ({
40590
- name: repoName(path2),
41038
+ const projects = [...byRepo.entries()].sort(([a], [b]) => repoName2(a).localeCompare(repoName2(b))).map(([path2, repoThreads]) => ({
41039
+ name: repoName2(path2),
40591
41040
  path: path2,
40592
41041
  worktrees: groupHomeBoardWorktrees(repoThreads).map((group) => {
40593
41042
  const tags = workspaceTagsFromGroup(group);
@@ -40606,41 +41055,6 @@ function listPhoneSidebar(viewerLogin = phoneViewerLogin()) {
40606
41055
  }));
40607
41056
  return { orchestration: listPhoneChats(), projects, defaults: phoneAccountDefaults() };
40608
41057
  }
40609
- function historyStamp(thread) {
40610
- return thread.archivedAt || thread.updatedAt;
40611
- }
40612
- function historyCursor(chat) {
40613
- return `${chat.updatedAt} ${chat.id}`;
40614
- }
40615
- function historyQueryMatch(chat, query) {
40616
- if (!query) return true;
40617
- return `${chat.title} ${chat.where} ${chat.agent} ${chat.preview}`.toLowerCase().includes(query);
40618
- }
40619
- function listPhoneHistory(opts) {
40620
- const query = opts?.query?.trim().toLowerCase() ?? "";
40621
- const after = opts?.after?.trim() ?? "";
40622
- 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) => ({
40623
- id: thread.id,
40624
- title: thread.title?.trim() || "Untitled",
40625
- preview: previewOf(thread),
40626
- updatedAt: historyStamp(thread),
40627
- where: isGlobalThread(thread) ? "Orchestration" : repoName(thread.repoPath),
40628
- agent: thread.agent
40629
- })).filter((chat) => historyQueryMatch(chat, query));
40630
- let start = 0;
40631
- if (after) {
40632
- const index = all.findIndex((chat) => historyCursor(chat) === after);
40633
- start = index >= 0 ? index + 1 : all.length;
40634
- }
40635
- const chats = all.slice(start, start + HISTORY_PAGE_SIZE);
40636
- const last = chats.at(-1);
40637
- const more = start + chats.length < all.length;
40638
- return {
40639
- chats,
40640
- total: all.length,
40641
- ...more && last ? { next: historyCursor(last) } : {}
40642
- };
40643
- }
40644
41058
  function matchPhoneThread(chatId) {
40645
41059
  const id = chatId.trim();
40646
41060
  if (!id) return null;
@@ -40653,7 +41067,7 @@ function matchPhoneThread(chatId) {
40653
41067
  }
40654
41068
  function visiblePhoneThread(thread) {
40655
41069
  if (isGlobalThread(thread)) return true;
40656
- return isProjectPath(thread.repoPath);
41070
+ return isProjectPath2(thread.repoPath);
40657
41071
  }
40658
41072
  function findPhoneChat(chatId) {
40659
41073
  const thread = matchPhoneThread(chatId);
@@ -40719,7 +41133,7 @@ function projectPlace(thread) {
40719
41133
  }
40720
41134
  function assertKnownProject(repoPath) {
40721
41135
  const path2 = repoPath.trim();
40722
- if (!isProjectPath(path2)) throw new Error("Pick a project on this Mac.");
41136
+ if (!isProjectPath2(path2)) throw new Error("Pick a project on this Mac.");
40723
41137
  const known = listWorkspaces().some((workspace) => sameRepoPath3(workspace.path, path2)) || listThreads().some(
40724
41138
  (thread) => thread.status !== "archived" && sameRepoPath3(thread.repoPath, path2)
40725
41139
  );
@@ -40822,6 +41236,56 @@ function archivePhoneChat(chatId) {
40822
41236
  return true;
40823
41237
  }
40824
41238
 
41239
+ // src/remote/phone-workspace-ops.ts
41240
+ function dispatchPhoneWorkspace(cmd, emit, publishOpened) {
41241
+ if (cmd.op === "pane") {
41242
+ const thread = readPhoneThread(cmd.chatId);
41243
+ if (!thread) {
41244
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
41245
+ return;
41246
+ }
41247
+ void readPhonePane(thread, cmd.pane, cmd.scope, cmd.query).then((body) => {
41248
+ emit({
41249
+ op: "pane",
41250
+ chatId: cmd.chatId,
41251
+ pane: cmd.pane,
41252
+ rows: body.rows,
41253
+ ...body.note ? { note: body.note } : {},
41254
+ ...cmd.pane === "files" ? { query: cmd.query ?? "" } : {}
41255
+ });
41256
+ });
41257
+ return;
41258
+ }
41259
+ if (cmd.op === "header") {
41260
+ const thread = readPhoneThread(cmd.chatId);
41261
+ if (!thread) {
41262
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
41263
+ return;
41264
+ }
41265
+ void readPhoneHeader(thread).then((header) => {
41266
+ emit({ op: "header", chatId: cmd.chatId, ...header });
41267
+ });
41268
+ return;
41269
+ }
41270
+ if (cmd.op === "git") {
41271
+ void getOrchestrator().askGit(cmd.chatId, cmd.action).then((tab) => publishOpened(tab.id)).catch((err) => {
41272
+ emit({
41273
+ op: "error",
41274
+ chatId: cmd.chatId,
41275
+ message: err instanceof Error ? err.message : String(err)
41276
+ });
41277
+ });
41278
+ return;
41279
+ }
41280
+ void getOrchestrator().requestReview(cmd.chatId).then((tab) => publishOpened(tab.id)).catch((err) => {
41281
+ emit({
41282
+ op: "error",
41283
+ chatId: cmd.chatId,
41284
+ message: err instanceof Error ? err.message : String(err)
41285
+ });
41286
+ });
41287
+ }
41288
+
40825
41289
  // src/remote/phone-sources.ts
40826
41290
  init_worktree();
40827
41291
  function sourceWarning(label, err) {
@@ -41197,7 +41661,8 @@ function stagePhoneTurnFiles(worktreePath, draft) {
41197
41661
  worktreePath,
41198
41662
  (draft.files ?? []).map((file) => ({ name: file.name, dataBase64: file.dataBase64 }))
41199
41663
  );
41200
- return [...links, ...files];
41664
+ const refs = (draft.refs ?? []).map((ref) => buildCodeRefAttachment(ref));
41665
+ return [...links, ...files, ...refs];
41201
41666
  }
41202
41667
  function handlePhoneControl(cmd, opts) {
41203
41668
  const log = opts.onLog ?? (() => void 0);
@@ -41398,10 +41863,25 @@ function handlePhoneControl(cmd, opts) {
41398
41863
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file is not in this worktree." });
41399
41864
  return;
41400
41865
  }
41401
- opts.onOpenWorktreeFile?.({
41402
- threadId: thread.id,
41866
+ const preview = phoneFilePreview(thread.worktreePath, relative, kind === "dir" ? "dir" : "file");
41867
+ if (!preview) {
41868
+ emitControl(opts, {
41869
+ op: "error",
41870
+ chatId: cmd.chatId,
41871
+ message: "That file could not be opened on the phone."
41872
+ });
41873
+ return;
41874
+ }
41875
+ emitControl(opts, {
41876
+ op: "file",
41877
+ chatId: cmd.chatId,
41403
41878
  path: relative,
41404
- ...kind === "dir" ? { directory: true } : {},
41879
+ content: preview.content,
41880
+ encoding: preview.encoding,
41881
+ ...preview.binary ? { binary: true } : {},
41882
+ ...preview.directory ? { directory: true } : {},
41883
+ ...preview.truncated ? { truncated: true } : {},
41884
+ ...preview.mime ? { mime: preview.mime } : {},
41405
41885
  ...cmd.startLine != null ? { startLine: cmd.startLine } : {},
41406
41886
  ...cmd.endLine != null ? { endLine: cmd.endLine } : {}
41407
41887
  });
@@ -41416,13 +41896,34 @@ function handlePhoneControl(cmd, opts) {
41416
41896
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
41417
41897
  return;
41418
41898
  }
41419
- opts.onOpenArtifact?.({
41420
- threadId: thread.id,
41421
- title: cmd.title,
41422
- ...cmd.hint ? { hint: cmd.hint } : {}
41899
+ const liveParts = readTurnLiveParts(thread.id);
41900
+ const docs = phoneArtifactDocuments([
41901
+ ...(thread.messages ?? []).filter((message) => message.role === "agent").map((message) => ({ text: message.text.trim(), parts: message.parts })),
41902
+ { text: visibleAssistantText(void 0, liveParts), parts: liveParts }
41903
+ ]);
41904
+ const body = phoneArtifactReply(docs, { title: cmd.title, ...cmd.hint ? { hint: cmd.hint } : {} });
41905
+ if (!body) {
41906
+ emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That artifact is not in this chat." });
41907
+ return;
41908
+ }
41909
+ emitControl(opts, {
41910
+ op: "artifact",
41911
+ chatId: cmd.chatId,
41912
+ title: body.title,
41913
+ kind: body.kind,
41914
+ content: body.content,
41915
+ ...body.truncated ? { truncated: true } : {}
41423
41916
  });
41424
41917
  return;
41425
41918
  }
41919
+ if (cmd.op === "pane" || cmd.op === "header" || cmd.op === "git" || cmd.op === "review") {
41920
+ dispatchPhoneWorkspace(
41921
+ cmd,
41922
+ (reply) => emitControl(opts, reply),
41923
+ (chatId2) => publishOpenedChat(opts, chatId2)
41924
+ );
41925
+ return;
41926
+ }
41426
41927
  if (cmd.op === "stop") {
41427
41928
  if (!readPhoneThread(cmd.chatId)) {
41428
41929
  emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
@@ -41501,7 +42002,7 @@ function handleRemoteInbound(text7, opts) {
41501
42002
  }
41502
42003
 
41503
42004
  // src/remote/phone-board-sync.ts
41504
- var import_node_path69 = require("path");
42005
+ var import_node_path70 = require("path");
41505
42006
  var import_chokidar = require("chokidar");
41506
42007
  init_orchestrator();
41507
42008
  init_paths();
@@ -41546,7 +42047,7 @@ function subscribePhoneBoard(notify) {
41546
42047
  threads.on("error", () => void 0);
41547
42048
  const data = (0, import_chokidar.watch)(appDataDir(), { ignoreInitial: true, depth: 0 });
41548
42049
  const onData = (changed) => {
41549
- const name = (0, import_node_path69.basename)(changed);
42050
+ const name = (0, import_node_path70.basename)(changed);
41550
42051
  if (name !== "workspaces.json" && name !== "removed-workspaces.json" && name !== "settings.json") return;
41551
42052
  notify();
41552
42053
  };
@@ -41857,14 +42358,14 @@ init_fetch();
41857
42358
 
41858
42359
  // src/relay/accounts.ts
41859
42360
  var import_node_crypto27 = require("crypto");
41860
- var import_node_fs71 = require("fs");
42361
+ var import_node_fs72 = require("fs");
41861
42362
  var import_node_module7 = require("module");
41862
- var import_node_path70 = require("path");
42363
+ var import_node_path71 = require("path");
41863
42364
  var GITHUB_ACCOUNT_ISSUER = "https://github.com";
41864
42365
  var RELAY_DEVICE_CAP = 3;
41865
42366
  function thisModuleFile3() {
41866
42367
  const cjsFile = typeof __filename !== "undefined" ? __filename : "";
41867
- return cjsFile || process.argv[1] || (0, import_node_path70.join)(process.cwd(), "package.json");
42368
+ return cjsFile || process.argv[1] || (0, import_node_path71.join)(process.cwd(), "package.json");
41868
42369
  }
41869
42370
  function openDatabase(filePath) {
41870
42371
  const req = (0, import_node_module7.createRequire)(thisModuleFile3());
@@ -41901,8 +42402,8 @@ function assertRelayAccountsDurable(filePath) {
41901
42402
  if (process.env.SIDEBOARD_RELAY_ACCOUNTS_EPHEMERAL === "1") return;
41902
42403
  if (process.env.NODE_ENV !== "production") return;
41903
42404
  if (process.platform !== "linux") return;
41904
- const dir = (0, import_node_fs71.realpathSync)((0, import_node_path70.dirname)(filePath));
41905
- const mounts = (0, import_node_fs71.readFileSync)("/proc/mounts", "utf8");
42405
+ const dir = (0, import_node_fs72.realpathSync)((0, import_node_path71.dirname)(filePath));
42406
+ const mounts = (0, import_node_fs72.readFileSync)("/proc/mounts", "utf8");
41906
42407
  const point = longestMountPoint(dir, mounts);
41907
42408
  if (point === "/") {
41908
42409
  throw new Error(
@@ -41913,7 +42414,7 @@ function assertRelayAccountsDurable(filePath) {
41913
42414
  var RelayAccountStore = class {
41914
42415
  db;
41915
42416
  constructor(filePath) {
41916
- (0, import_node_fs71.mkdirSync)((0, import_node_path70.dirname)(filePath), { recursive: true });
42417
+ (0, import_node_fs72.mkdirSync)((0, import_node_path71.dirname)(filePath), { recursive: true });
41917
42418
  this.db = openDatabase(filePath);
41918
42419
  this.db.exec(`
41919
42420
  CREATE TABLE IF NOT EXISTS accounts (
@@ -42947,9 +43448,9 @@ var import_node_http5 = require("http");
42947
43448
  var import_ws6 = require("ws");
42948
43449
 
42949
43450
  // src/relay/static.ts
42950
- var import_node_fs72 = require("fs");
43451
+ var import_node_fs73 = require("fs");
42951
43452
  var import_promises = require("fs/promises");
42952
- var import_node_path71 = __toESM(require("path"), 1);
43453
+ var import_node_path72 = __toESM(require("path"), 1);
42953
43454
  var TYPES = {
42954
43455
  ".css": "text/css; charset=utf-8",
42955
43456
  ".html": "text/html; charset=utf-8",
@@ -42980,9 +43481,9 @@ function resolveStaticPath(root, requestUrl) {
42980
43481
  return null;
42981
43482
  }
42982
43483
  if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
42983
- const rootResolved = import_node_path71.default.resolve(root);
42984
- const candidate = import_node_path71.default.resolve(rootResolved, `.${pathname}`);
42985
- if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path71.default.sep)) {
43484
+ const rootResolved = import_node_path72.default.resolve(root);
43485
+ const candidate = import_node_path72.default.resolve(rootResolved, `.${pathname}`);
43486
+ if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path72.default.sep)) {
42986
43487
  return null;
42987
43488
  }
42988
43489
  return candidate;
@@ -42996,7 +43497,7 @@ async function fileSize(file) {
42996
43497
  }
42997
43498
  }
42998
43499
  function sendFile(req, res, file, size) {
42999
- const ext = import_node_path71.default.extname(file).toLowerCase();
43500
+ const ext = import_node_path72.default.extname(file).toLowerCase();
43000
43501
  res.writeHead(200, {
43001
43502
  "Content-Type": TYPES[ext] ?? "application/octet-stream",
43002
43503
  "Content-Length": size,
@@ -43006,7 +43507,7 @@ function sendFile(req, res, file, size) {
43006
43507
  res.end();
43007
43508
  return true;
43008
43509
  }
43009
- (0, import_node_fs72.createReadStream)(file).pipe(res);
43510
+ (0, import_node_fs73.createReadStream)(file).pipe(res);
43010
43511
  return true;
43011
43512
  }
43012
43513
  async function tryServeStatic(req, res, root) {
@@ -43015,7 +43516,7 @@ async function tryServeStatic(req, res, root) {
43015
43516
  if (!candidate) return false;
43016
43517
  const direct = await fileSize(candidate);
43017
43518
  if (direct != null) return sendFile(req, res, candidate, direct);
43018
- const asIndex = import_node_path71.default.join(candidate, "index.html");
43519
+ const asIndex = import_node_path72.default.join(candidate, "index.html");
43019
43520
  const indexSize = await fileSize(asIndex);
43020
43521
  if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
43021
43522
  return false;
@@ -43380,8 +43881,8 @@ async function startRelayServer(opts) {
43380
43881
  }
43381
43882
 
43382
43883
  // src/slack/outbound-watch.ts
43383
- var import_node_fs73 = require("fs");
43384
- var import_node_path72 = require("path");
43884
+ var import_node_fs74 = require("fs");
43885
+ var import_node_path73 = require("path");
43385
43886
  init_paths();
43386
43887
  init_private_file();
43387
43888
  init_secure_file();
@@ -43394,7 +43895,7 @@ var POLL_INTERVAL_MS = 12e3;
43394
43895
  var lastPollMs = 0;
43395
43896
  var nameCache = /* @__PURE__ */ new Map();
43396
43897
  function storePath4() {
43397
- return (0, import_node_path72.join)(appDataDir(), "slack-outbound-watch.json");
43898
+ return (0, import_node_path73.join)(appDataDir(), "slack-outbound-watch.json");
43398
43899
  }
43399
43900
  function watchId(teamId, channelId, ts) {
43400
43901
  return `${teamId}:${channelId}:${ts}`;
@@ -43407,9 +43908,9 @@ function tsNewer(a, b) {
43407
43908
  }
43408
43909
  function readStore4() {
43409
43910
  const path2 = storePath4();
43410
- if (!(0, import_node_fs73.existsSync)(path2)) return [];
43911
+ if (!(0, import_node_fs74.existsSync)(path2)) return [];
43411
43912
  try {
43412
- const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse((0, import_node_fs73.readFileSync)(path2, "utf8"));
43913
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse((0, import_node_fs74.readFileSync)(path2, "utf8"));
43413
43914
  return Array.isArray(parsed?.watches) ? parsed.watches : [];
43414
43915
  } catch {
43415
43916
  return [];