@sideboard-ai/core 0.1.275 → 0.1.277
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 +531 -81
- package/dist/index.d.cts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +514 -70
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -829,9 +829,9 @@ function resolveViewerProfile(account, project) {
|
|
|
829
829
|
};
|
|
830
830
|
}
|
|
831
831
|
function compactNotes(notes, max = 160) {
|
|
832
|
-
const
|
|
833
|
-
if (
|
|
834
|
-
return `${
|
|
832
|
+
const oneLine2 = notes.replace(/\s+/g, " ").trim();
|
|
833
|
+
if (oneLine2.length <= max) return oneLine2;
|
|
834
|
+
return `${oneLine2.slice(0, max - 1).trimEnd()}\u2026`;
|
|
835
835
|
}
|
|
836
836
|
function formatAccountProfilePlaybookLine(profile) {
|
|
837
837
|
if (!profile.accountNotes) return "";
|
|
@@ -4747,13 +4747,13 @@ function parseDiffStat(result) {
|
|
|
4747
4747
|
}
|
|
4748
4748
|
function clipThinkingForStore(text6, max = THINKING_PART_MAX_CHARS) {
|
|
4749
4749
|
if (text6.length <= max) return text6;
|
|
4750
|
-
const
|
|
4751
|
-
return
|
|
4750
|
+
const marker2 = "\u2026(earlier thinking trimmed)\n";
|
|
4751
|
+
return marker2 + text6.slice(text6.length - Math.max(1, max - marker2.length));
|
|
4752
4752
|
}
|
|
4753
4753
|
function clipEventTail(text6, max) {
|
|
4754
4754
|
if (text6.length <= max) return text6;
|
|
4755
|
-
const
|
|
4756
|
-
return
|
|
4755
|
+
const marker2 = "\u2026(truncated)\n";
|
|
4756
|
+
return marker2 + text6.slice(text6.length - Math.max(1, max - marker2.length));
|
|
4757
4757
|
}
|
|
4758
4758
|
function clipAgentEventForPaint(event) {
|
|
4759
4759
|
if (event.type === "tool_result" && event.content) {
|
|
@@ -18311,18 +18311,18 @@ function budgetSeedBlocks(messages, blocks, maxChars) {
|
|
|
18311
18311
|
used = next;
|
|
18312
18312
|
}
|
|
18313
18313
|
const dropped = keep.filter((k) => !k).length;
|
|
18314
|
-
const
|
|
18314
|
+
const marker2 = `_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`;
|
|
18315
18315
|
const kept = [];
|
|
18316
18316
|
let markerPlaced = false;
|
|
18317
18317
|
for (let i = 0; i < blocks.length; i++) {
|
|
18318
18318
|
if (!keep[i]) continue;
|
|
18319
18319
|
if (dropped > 0 && !markerPlaced && messages[i].role !== "summary") {
|
|
18320
|
-
kept.push(
|
|
18320
|
+
kept.push(marker2);
|
|
18321
18321
|
markerPlaced = true;
|
|
18322
18322
|
}
|
|
18323
18323
|
kept.push(blocks[i]);
|
|
18324
18324
|
}
|
|
18325
|
-
if (dropped > 0 && !markerPlaced) kept.push(
|
|
18325
|
+
if (dropped > 0 && !markerPlaced) kept.push(marker2);
|
|
18326
18326
|
return kept.join("\n\n");
|
|
18327
18327
|
}
|
|
18328
18328
|
function buildSessionSeed(messages, opts) {
|
|
@@ -33375,8 +33375,8 @@ function crushMarkdown(text6, opts) {
|
|
|
33375
33375
|
}
|
|
33376
33376
|
const must = blocks.filter((b) => looksLikeMustKeep(b));
|
|
33377
33377
|
const rest = blocks.filter((b) => !looksLikeMustKeep(b));
|
|
33378
|
-
const
|
|
33379
|
-
let used = must.reduce((n, b) => n + b.length, 0) +
|
|
33378
|
+
const marker2 = `[\u2026crushed body; ${original.length} chars originally. Pass include=full to read the rest.]`;
|
|
33379
|
+
let used = must.reduce((n, b) => n + b.length, 0) + marker2.length;
|
|
33380
33380
|
const keptRest = [];
|
|
33381
33381
|
const headN = Math.max(1, Math.ceil(rest.length * 0.3));
|
|
33382
33382
|
const tailN = Math.max(1, Math.ceil(rest.length * 0.15));
|
|
@@ -33416,7 +33416,7 @@ function crushMarkdown(text6, opts) {
|
|
|
33416
33416
|
continue;
|
|
33417
33417
|
}
|
|
33418
33418
|
if (!inserted) {
|
|
33419
|
-
out.push(
|
|
33419
|
+
out.push(marker2);
|
|
33420
33420
|
inserted = true;
|
|
33421
33421
|
}
|
|
33422
33422
|
}
|
|
@@ -38738,12 +38738,61 @@ init_global_workspace();
|
|
|
38738
38738
|
init_thread_store();
|
|
38739
38739
|
init_implied_first_prompt();
|
|
38740
38740
|
|
|
38741
|
+
// src/remote/phone-media.ts
|
|
38742
|
+
init_diff();
|
|
38743
|
+
var PHONE_IMAGE_MAX_BYTES = 15e5;
|
|
38744
|
+
var IMAGE_MIME = {
|
|
38745
|
+
png: "image/png",
|
|
38746
|
+
jpg: "image/jpeg",
|
|
38747
|
+
jpeg: "image/jpeg",
|
|
38748
|
+
gif: "image/gif",
|
|
38749
|
+
webp: "image/webp",
|
|
38750
|
+
bmp: "image/bmp"
|
|
38751
|
+
};
|
|
38752
|
+
function phoneWorktreeRelative(worktreePath, raw) {
|
|
38753
|
+
let text6 = raw.trim();
|
|
38754
|
+
if (!text6) return null;
|
|
38755
|
+
if (/^file:\/\//i.test(text6)) {
|
|
38756
|
+
try {
|
|
38757
|
+
text6 = decodeURIComponent(new URL(text6).pathname);
|
|
38758
|
+
} catch {
|
|
38759
|
+
return null;
|
|
38760
|
+
}
|
|
38761
|
+
}
|
|
38762
|
+
text6 = (text6.split("#")[0] ?? text6).replace(/\\/g, "/");
|
|
38763
|
+
if (!text6 || text6.includes("\0") || text6.split("/").includes("..")) return null;
|
|
38764
|
+
const root = worktreePath.replace(/\/+$/, "");
|
|
38765
|
+
if (!root || root === "/") return null;
|
|
38766
|
+
if (text6.startsWith("/")) {
|
|
38767
|
+
if (text6 !== root && !text6.startsWith(`${root}/`)) return null;
|
|
38768
|
+
text6 = text6 === root ? "" : text6.slice(root.length + 1);
|
|
38769
|
+
}
|
|
38770
|
+
text6 = text6.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
38771
|
+
if (!text6 || text6 === "." || text6.startsWith("/")) return null;
|
|
38772
|
+
return text6;
|
|
38773
|
+
}
|
|
38774
|
+
function phoneImageMime(relativePath) {
|
|
38775
|
+
const ext = relativePath.split(".").pop()?.toLowerCase() ?? "";
|
|
38776
|
+
return IMAGE_MIME[ext] ?? null;
|
|
38777
|
+
}
|
|
38778
|
+
function phoneImageDataUrl(worktreePath, raw) {
|
|
38779
|
+
const relative = phoneWorktreeRelative(worktreePath, raw);
|
|
38780
|
+
if (!relative) return null;
|
|
38781
|
+
const mime = phoneImageMime(relative);
|
|
38782
|
+
if (!mime) return null;
|
|
38783
|
+
try {
|
|
38784
|
+
const file = readWorktreeFileForUpload(worktreePath, relative, { maxBytes: PHONE_IMAGE_MAX_BYTES });
|
|
38785
|
+
return `data:${mime};base64,${file.contentBase64}`;
|
|
38786
|
+
} catch {
|
|
38787
|
+
return null;
|
|
38788
|
+
}
|
|
38789
|
+
}
|
|
38790
|
+
|
|
38741
38791
|
// src/remote/phone-chats.ts
|
|
38742
38792
|
var import_node_crypto25 = require("crypto");
|
|
38743
38793
|
init_list_models();
|
|
38744
38794
|
init_home_board();
|
|
38745
38795
|
init_stage_files();
|
|
38746
|
-
init_worktree();
|
|
38747
38796
|
init_worktree_labels();
|
|
38748
38797
|
init_thinking_effort();
|
|
38749
38798
|
init_app_settings();
|
|
@@ -38753,6 +38802,144 @@ init_workspaces();
|
|
|
38753
38802
|
init_chat_tabs();
|
|
38754
38803
|
init_create();
|
|
38755
38804
|
init_orchestrator();
|
|
38805
|
+
|
|
38806
|
+
// src/remote/phone-artifact.ts
|
|
38807
|
+
var FENCE_RE = /```([a-zA-Z0-9_+-]*)[^\n]*\n([\s\S]*?)(?:```|$)/g;
|
|
38808
|
+
var DOC_LANGS = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "svg", "markdown", "md", "mdx", "jsx", "tsx", "react"]);
|
|
38809
|
+
var MIN_FENCE_CHARS = 40;
|
|
38810
|
+
var MIN_MD_CHARS = 120;
|
|
38811
|
+
function oneLine(value, max) {
|
|
38812
|
+
return value.replace(/[`\r\n]/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
|
|
38813
|
+
}
|
|
38814
|
+
function asRecord13(value) {
|
|
38815
|
+
if (typeof value === "string") {
|
|
38816
|
+
try {
|
|
38817
|
+
return asRecord13(JSON.parse(value));
|
|
38818
|
+
} catch {
|
|
38819
|
+
return void 0;
|
|
38820
|
+
}
|
|
38821
|
+
}
|
|
38822
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
38823
|
+
return void 0;
|
|
38824
|
+
}
|
|
38825
|
+
function str6(value) {
|
|
38826
|
+
return typeof value === "string" ? value : "";
|
|
38827
|
+
}
|
|
38828
|
+
function normalizedBody(content) {
|
|
38829
|
+
return content.trim().replace(/\s+/g, " ");
|
|
38830
|
+
}
|
|
38831
|
+
function artifactHint(content) {
|
|
38832
|
+
const body = normalizedBody(content);
|
|
38833
|
+
if (!body) return "";
|
|
38834
|
+
return fnv1a32(body).toString(36);
|
|
38835
|
+
}
|
|
38836
|
+
function fnv1a32(value) {
|
|
38837
|
+
let hash = 2166136261;
|
|
38838
|
+
for (let i = 0; i < value.length; i++) {
|
|
38839
|
+
hash ^= value.charCodeAt(i);
|
|
38840
|
+
hash = Math.imul(hash, 16777619);
|
|
38841
|
+
}
|
|
38842
|
+
return hash >>> 0;
|
|
38843
|
+
}
|
|
38844
|
+
function looksLikeHtml(content) {
|
|
38845
|
+
const head = content.trim().slice(0, 200).toLowerCase();
|
|
38846
|
+
return head.startsWith("<!doctype") || head.startsWith("<html") || head.startsWith("<svg");
|
|
38847
|
+
}
|
|
38848
|
+
function kindFor(language, content) {
|
|
38849
|
+
const lang = language.toLowerCase();
|
|
38850
|
+
if (!lang) {
|
|
38851
|
+
if (!looksLikeHtml(content) || content.trim().length < MIN_FENCE_CHARS) return null;
|
|
38852
|
+
return content.trim().toLowerCase().startsWith("<svg") ? "svg" : "html";
|
|
38853
|
+
}
|
|
38854
|
+
if (!DOC_LANGS.has(lang)) return null;
|
|
38855
|
+
const min = lang === "markdown" || lang === "md" || lang === "mdx" ? MIN_MD_CHARS : MIN_FENCE_CHARS;
|
|
38856
|
+
if (content.trim().length < min) return null;
|
|
38857
|
+
if (lang === "svg") return "svg";
|
|
38858
|
+
if (lang === "markdown" || lang === "md" || lang === "mdx") return "markdown";
|
|
38859
|
+
if (lang === "jsx" || lang === "tsx" || lang === "react") return "react";
|
|
38860
|
+
return "html";
|
|
38861
|
+
}
|
|
38862
|
+
function titleFor(kind, content) {
|
|
38863
|
+
if (kind === "html" || kind === "svg") {
|
|
38864
|
+
const title = /<title[^>]*>([^<]+)<\/title>/i.exec(content)?.[1]?.trim();
|
|
38865
|
+
if (title) return oneLine(title, 80);
|
|
38866
|
+
}
|
|
38867
|
+
if (kind === "markdown") {
|
|
38868
|
+
const heading = /^#\s+(.+)$/m.exec(content)?.[1]?.trim();
|
|
38869
|
+
if (heading) return oneLine(heading, 80);
|
|
38870
|
+
}
|
|
38871
|
+
if (kind === "react") {
|
|
38872
|
+
const name = /export\s+default\s+(?:function|class)\s+([A-Za-z_$][\w$]*)/.exec(content)?.[1];
|
|
38873
|
+
if (name) return name;
|
|
38874
|
+
}
|
|
38875
|
+
if (kind === "svg") return "SVG artifact";
|
|
38876
|
+
if (kind === "markdown") return "Document";
|
|
38877
|
+
if (kind === "react") return "React artifact";
|
|
38878
|
+
return "HTML artifact";
|
|
38879
|
+
}
|
|
38880
|
+
function marker(title, hint) {
|
|
38881
|
+
return hint ? `\`\`\`sb-artifact
|
|
38882
|
+
${title}
|
|
38883
|
+
${hint}
|
|
38884
|
+
\`\`\`` : `\`\`\`sb-artifact
|
|
38885
|
+
${title}
|
|
38886
|
+
\`\`\``;
|
|
38887
|
+
}
|
|
38888
|
+
function toolContent(input) {
|
|
38889
|
+
if (!input) return "";
|
|
38890
|
+
const direct = input.content;
|
|
38891
|
+
if (typeof direct === "string") return direct;
|
|
38892
|
+
const nested = asRecord13(direct);
|
|
38893
|
+
if (!nested) return "";
|
|
38894
|
+
for (const key of ["html", "markup", "text", "source", "content", "code"]) {
|
|
38895
|
+
if (typeof nested[key] === "string") return nested[key];
|
|
38896
|
+
}
|
|
38897
|
+
return "";
|
|
38898
|
+
}
|
|
38899
|
+
function isArtifactTool(name) {
|
|
38900
|
+
const short = name.split("__").pop() ?? name;
|
|
38901
|
+
return /^(present_artifact|create_artifact|update_artifact)$/i.test(short);
|
|
38902
|
+
}
|
|
38903
|
+
function phoneArtifactText(text6, parts, limit = Number.POSITIVE_INFINITY) {
|
|
38904
|
+
const hints = /* @__PURE__ */ new Set();
|
|
38905
|
+
const body = text6.replace(new RegExp(FENCE_RE.source, "g"), (full, language, content) => {
|
|
38906
|
+
const kind = kindFor(language, content);
|
|
38907
|
+
if (!kind) return full;
|
|
38908
|
+
const hint = artifactHint(content);
|
|
38909
|
+
hints.add(hint);
|
|
38910
|
+
return marker(titleFor(kind, content), hint);
|
|
38911
|
+
});
|
|
38912
|
+
const extras = [];
|
|
38913
|
+
for (const part of parts ?? []) {
|
|
38914
|
+
if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
|
|
38915
|
+
const input = asRecord13(part.input);
|
|
38916
|
+
const content = toolContent(input);
|
|
38917
|
+
const hint = artifactHint(content);
|
|
38918
|
+
if (hint && hints.has(hint)) continue;
|
|
38919
|
+
const kind = kindFor("", content);
|
|
38920
|
+
const title = oneLine(str6(input?.title), 80) || (kind ? titleFor(kind, content) : "Artifact");
|
|
38921
|
+
if (!str6(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
|
|
38922
|
+
if (hint) hints.add(hint);
|
|
38923
|
+
extras.push(marker(title, hint));
|
|
38924
|
+
}
|
|
38925
|
+
const tail = extras.join("\n\n");
|
|
38926
|
+
const room = tail ? Math.max(0, limit - tail.length - 2) : limit;
|
|
38927
|
+
const prose = body.length > room ? `${body.slice(0, Math.max(0, room - 1))}\u2026` : body;
|
|
38928
|
+
if (!tail) return prose;
|
|
38929
|
+
return prose ? `${prose}
|
|
38930
|
+
|
|
38931
|
+
${tail}` : tail;
|
|
38932
|
+
}
|
|
38933
|
+
function parseOpenArtifact(value, chatId) {
|
|
38934
|
+
const record = asRecord13(value);
|
|
38935
|
+
if (!record) return "invalid";
|
|
38936
|
+
const title = oneLine(str6(record.title), 200);
|
|
38937
|
+
if (!title) return "invalid";
|
|
38938
|
+
const hint = oneLine(str6(record.hint), 200);
|
|
38939
|
+
return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
|
|
38940
|
+
}
|
|
38941
|
+
|
|
38942
|
+
// src/remote/phone-chats.ts
|
|
38756
38943
|
var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
|
|
38757
38944
|
var PHONE_USER_PREFIX = "Phone\n\n";
|
|
38758
38945
|
var TRANSCRIPT_LIMIT = 40;
|
|
@@ -38807,6 +38994,21 @@ function parsePhoneControl(value) {
|
|
|
38807
38994
|
const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
|
|
38808
38995
|
if (!chatId) return "invalid";
|
|
38809
38996
|
if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
|
|
38997
|
+
if (op === "open-artifact") return parseOpenArtifact(value, chatId);
|
|
38998
|
+
if (op === "open-file" || op === "media") {
|
|
38999
|
+
const path2 = textField(value, "path");
|
|
39000
|
+
if (!path2 || path2.length > 2e3) return "invalid";
|
|
39001
|
+
if (op === "media") return { op, chatId, path: path2 };
|
|
39002
|
+
const startLine = lineField(value, "startLine");
|
|
39003
|
+
const endLine = lineField(value, "endLine");
|
|
39004
|
+
return {
|
|
39005
|
+
op,
|
|
39006
|
+
chatId,
|
|
39007
|
+
path: path2,
|
|
39008
|
+
...startLine != null ? { startLine } : {},
|
|
39009
|
+
...endLine != null ? { endLine } : {}
|
|
39010
|
+
};
|
|
39011
|
+
}
|
|
38810
39012
|
const draft = parsePhoneDraft(value);
|
|
38811
39013
|
if (draft === "invalid") return "invalid";
|
|
38812
39014
|
if (op === "options") return { op: "options", chatId, ...draft };
|
|
@@ -38822,6 +39024,11 @@ function textField(value, key) {
|
|
|
38822
39024
|
const raw = value[key];
|
|
38823
39025
|
return typeof raw === "string" ? raw.trim() : "";
|
|
38824
39026
|
}
|
|
39027
|
+
function lineField(value, key) {
|
|
39028
|
+
const raw = value[key];
|
|
39029
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 1e6) return void 0;
|
|
39030
|
+
return raw;
|
|
39031
|
+
}
|
|
38825
39032
|
function agentField(value) {
|
|
38826
39033
|
const agent = textField(value, "agent");
|
|
38827
39034
|
if (!agent) return void 0;
|
|
@@ -38965,7 +39172,8 @@ function summarize(thread) {
|
|
|
38965
39172
|
title: thread.title?.trim() || "Untitled",
|
|
38966
39173
|
status: thread.status,
|
|
38967
39174
|
preview: previewOf(thread),
|
|
38968
|
-
updatedAt: thread.updatedAt
|
|
39175
|
+
updatedAt: thread.updatedAt,
|
|
39176
|
+
agent: thread.agent
|
|
38969
39177
|
};
|
|
38970
39178
|
}
|
|
38971
39179
|
function previewOf(thread) {
|
|
@@ -38979,10 +39187,8 @@ function listPhoneMessages(thread) {
|
|
|
38979
39187
|
for (const message of thread.messages) {
|
|
38980
39188
|
if (message.role !== "user" && message.role !== "agent") continue;
|
|
38981
39189
|
const names = (message.attachments ?? []).map((item) => item.name.trim()).filter(Boolean);
|
|
38982
|
-
const
|
|
38983
|
-
|
|
38984
|
-
MESSAGE_LIMIT
|
|
38985
|
-
);
|
|
39190
|
+
const visible = [phoneVisibleText(message.role, message.text), names.length ? names.join(", ") : ""].filter(Boolean).join("\n");
|
|
39191
|
+
const text6 = message.role === "agent" ? phoneArtifactText(visible, message.parts, MESSAGE_LIMIT) : clip(visible, MESSAGE_LIMIT);
|
|
38986
39192
|
if (!text6) continue;
|
|
38987
39193
|
out.push({ role: message.role, text: text6 });
|
|
38988
39194
|
}
|
|
@@ -39061,13 +39267,28 @@ function listPhoneHistory(opts) {
|
|
|
39061
39267
|
...more && last ? { next: historyCursor(last) } : {}
|
|
39062
39268
|
};
|
|
39063
39269
|
}
|
|
39064
|
-
function
|
|
39270
|
+
function matchPhoneThread(chatId) {
|
|
39065
39271
|
const id = chatId.trim();
|
|
39066
39272
|
if (!id) return null;
|
|
39067
|
-
const
|
|
39273
|
+
const exact = readThread(id);
|
|
39274
|
+
if (exact) return exact;
|
|
39275
|
+
const match = findThreadByRef(id);
|
|
39276
|
+
if (!match) return null;
|
|
39277
|
+
if (match.id !== id && !match.id.startsWith(id)) return null;
|
|
39278
|
+
return match;
|
|
39279
|
+
}
|
|
39280
|
+
function visiblePhoneThread(thread) {
|
|
39281
|
+
if (isGlobalThread(thread)) return true;
|
|
39282
|
+
return isProjectPath(thread.repoPath);
|
|
39283
|
+
}
|
|
39284
|
+
function findPhoneChat(chatId) {
|
|
39285
|
+
const thread = matchPhoneThread(chatId);
|
|
39286
|
+
if (!thread || !visiblePhoneThread(thread)) return null;
|
|
39287
|
+
return thread;
|
|
39288
|
+
}
|
|
39289
|
+
function readPhoneThread(chatId) {
|
|
39290
|
+
const thread = findPhoneChat(chatId);
|
|
39068
39291
|
if (!thread || thread.status === "archived") return null;
|
|
39069
|
-
if (isGlobalThread(thread)) return thread;
|
|
39070
|
-
if (!isProjectPath(thread.repoPath)) return null;
|
|
39071
39292
|
return thread;
|
|
39072
39293
|
}
|
|
39073
39294
|
function phoneComposerOptions(thread) {
|
|
@@ -39213,6 +39434,22 @@ async function createPhoneProjectWorktree(repoPathOrInput) {
|
|
|
39213
39434
|
});
|
|
39214
39435
|
return openedFrom(thread, projectPlace(thread));
|
|
39215
39436
|
}
|
|
39437
|
+
async function listPhoneModels(agent) {
|
|
39438
|
+
const catalogs = await listModelsForAgent(agent);
|
|
39439
|
+
return (catalogs[0]?.models ?? []).slice(0, 40).map((model) => ({
|
|
39440
|
+
id: model.id,
|
|
39441
|
+
label: model.displayName || model.id
|
|
39442
|
+
}));
|
|
39443
|
+
}
|
|
39444
|
+
function archivePhoneChat(chatId) {
|
|
39445
|
+
const thread = readPhoneThread(chatId);
|
|
39446
|
+
if (!thread) return false;
|
|
39447
|
+
updateThread(thread.id, { status: "archived" });
|
|
39448
|
+
return true;
|
|
39449
|
+
}
|
|
39450
|
+
|
|
39451
|
+
// src/remote/phone-sources.ts
|
|
39452
|
+
init_worktree();
|
|
39216
39453
|
function sourceWarning(label, err) {
|
|
39217
39454
|
const message = err instanceof Error ? err.message : String(err);
|
|
39218
39455
|
return message ? `${label}: ${message}` : `${label} failed.`;
|
|
@@ -39255,60 +39492,42 @@ async function listPhoneSources(repoPath, query) {
|
|
|
39255
39492
|
...warnings.length ? { warnings } : {}
|
|
39256
39493
|
};
|
|
39257
39494
|
}
|
|
39258
|
-
async function listPhoneModels(agent) {
|
|
39259
|
-
const catalogs = await listModelsForAgent(agent);
|
|
39260
|
-
return (catalogs[0]?.models ?? []).slice(0, 40).map((model) => ({
|
|
39261
|
-
id: model.id,
|
|
39262
|
-
label: model.displayName || model.id
|
|
39263
|
-
}));
|
|
39264
|
-
}
|
|
39265
|
-
function archivePhoneChat(chatId) {
|
|
39266
|
-
const thread = readPhoneThread(chatId);
|
|
39267
|
-
if (!thread) return false;
|
|
39268
|
-
updateThread(thread.id, { status: "archived" });
|
|
39269
|
-
return true;
|
|
39270
|
-
}
|
|
39271
39495
|
|
|
39272
|
-
// src/remote/
|
|
39273
|
-
|
|
39274
|
-
|
|
39275
|
-
return `Phone
|
|
39496
|
+
// src/remote/phone-live.ts
|
|
39497
|
+
init_orchestrator();
|
|
39498
|
+
init_thread_store();
|
|
39276
39499
|
|
|
39277
|
-
|
|
39500
|
+
// src/remote/phone-stream.ts
|
|
39501
|
+
init_message_parts();
|
|
39502
|
+
var PHONE_STREAM_FLUSH_MS = 80;
|
|
39503
|
+
function phoneStreamFrame(parts) {
|
|
39504
|
+
return {
|
|
39505
|
+
text: phoneArtifactText(visibleAssistantText(void 0, parts), parts),
|
|
39506
|
+
activity: liveActivitySummary(parts)
|
|
39507
|
+
};
|
|
39278
39508
|
}
|
|
39279
|
-
|
|
39280
|
-
|
|
39509
|
+
var SKIPPED = /* @__PURE__ */ new Set(["session_id", "usage", "stderr", "exit"]);
|
|
39510
|
+
function phoneStreamParts(parts, event) {
|
|
39511
|
+
if (SKIPPED.has(event.type)) return null;
|
|
39512
|
+
return applyAgentEvent(parts, event);
|
|
39281
39513
|
}
|
|
39282
|
-
|
|
39283
|
-
|
|
39514
|
+
function phoneStreamFlushNow(event) {
|
|
39515
|
+
return event.type === "tool_use" || event.type === "tool_result" && !event.partial;
|
|
39516
|
+
}
|
|
39517
|
+
|
|
39518
|
+
// src/remote/phone-live.ts
|
|
39519
|
+
var REMOTE_STOPPED_REPLY = "Sideboard stopped the in-progress turn.";
|
|
39284
39520
|
var chatChains = /* @__PURE__ */ new Map();
|
|
39285
39521
|
var chatGeneration = /* @__PURE__ */ new Map();
|
|
39286
|
-
|
|
39287
|
-
const run2 = handleChain2.then(fn, fn);
|
|
39288
|
-
handleChain2 = run2.then(
|
|
39289
|
-
() => void 0,
|
|
39290
|
-
() => void 0
|
|
39291
|
-
);
|
|
39292
|
-
}
|
|
39293
|
-
function interruptRemoteCoordinator(deviceId, log = () => void 0) {
|
|
39294
|
-
try {
|
|
39295
|
-
const live = findRemoteCoordinator(deviceId);
|
|
39296
|
-
if (!live) return false;
|
|
39297
|
-
const fresh = readThread(live.id) ?? live;
|
|
39298
|
-
if (fresh.status !== "running" && fresh.status !== "queued") return false;
|
|
39299
|
-
getOrchestrator().stop(fresh.id, { clearQueue: true });
|
|
39300
|
-
log(`interrupt phone \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`);
|
|
39301
|
-
return true;
|
|
39302
|
-
} catch (err) {
|
|
39303
|
-
log(`interrupt: ${err instanceof Error ? err.message : String(err)}`);
|
|
39304
|
-
return false;
|
|
39305
|
-
}
|
|
39306
|
-
}
|
|
39522
|
+
var phoneTurnGen = /* @__PURE__ */ new Map();
|
|
39307
39523
|
function bumpChat(chatId) {
|
|
39308
39524
|
const next = (chatGeneration.get(chatId) ?? 0) + 1;
|
|
39309
39525
|
chatGeneration.set(chatId, next);
|
|
39310
39526
|
return next;
|
|
39311
39527
|
}
|
|
39528
|
+
function finishPhoneTurn(chatId, generation) {
|
|
39529
|
+
if (phoneTurnGen.get(chatId) === generation) phoneTurnGen.delete(chatId);
|
|
39530
|
+
}
|
|
39312
39531
|
function enqueueChat(chatId, fn) {
|
|
39313
39532
|
const prev = chatChains.get(chatId) ?? Promise.resolve();
|
|
39314
39533
|
const run2 = prev.then(fn, fn);
|
|
@@ -39323,6 +39542,49 @@ function enqueueChat(chatId, fn) {
|
|
|
39323
39542
|
function emitControl(opts, reply) {
|
|
39324
39543
|
opts.onOutbound({ type: "assistant", text: encodePhoneControl(reply) });
|
|
39325
39544
|
}
|
|
39545
|
+
function watchPhoneStream(opts, chatId, threadId, generation) {
|
|
39546
|
+
let parts = [];
|
|
39547
|
+
let timer = null;
|
|
39548
|
+
let lastKey = "";
|
|
39549
|
+
let closed = false;
|
|
39550
|
+
const alive = () => !closed && chatGeneration.get(chatId) === generation;
|
|
39551
|
+
const flush2 = () => {
|
|
39552
|
+
timer = null;
|
|
39553
|
+
if (!alive()) return;
|
|
39554
|
+
const frame = phoneStreamFrame(parts);
|
|
39555
|
+
const key = `${frame.activity}
|
|
39556
|
+
${frame.text}`;
|
|
39557
|
+
if (key === lastKey) return;
|
|
39558
|
+
if (!frame.text && !frame.activity) return;
|
|
39559
|
+
lastKey = key;
|
|
39560
|
+
emitControl(opts, { op: "stream", chatId, text: frame.text, activity: frame.activity });
|
|
39561
|
+
};
|
|
39562
|
+
const schedule = (event) => {
|
|
39563
|
+
if (phoneStreamFlushNow(event) || lastKey === "") {
|
|
39564
|
+
if (timer) {
|
|
39565
|
+
clearTimeout(timer);
|
|
39566
|
+
timer = null;
|
|
39567
|
+
}
|
|
39568
|
+
flush2();
|
|
39569
|
+
return;
|
|
39570
|
+
}
|
|
39571
|
+
if (timer) return;
|
|
39572
|
+
timer = setTimeout(flush2, PHONE_STREAM_FLUSH_MS);
|
|
39573
|
+
};
|
|
39574
|
+
const off = getOrchestrator().on((event) => {
|
|
39575
|
+
if (!alive()) return;
|
|
39576
|
+
if (event.type !== "turn_output" || event.threadId !== threadId) return;
|
|
39577
|
+
const next = phoneStreamParts(parts, event.event);
|
|
39578
|
+
if (!next) return;
|
|
39579
|
+
parts = next;
|
|
39580
|
+
schedule(event.event);
|
|
39581
|
+
});
|
|
39582
|
+
return () => {
|
|
39583
|
+
closed = true;
|
|
39584
|
+
if (timer) clearTimeout(timer);
|
|
39585
|
+
off();
|
|
39586
|
+
};
|
|
39587
|
+
}
|
|
39326
39588
|
function interruptChat(chatId, log) {
|
|
39327
39589
|
try {
|
|
39328
39590
|
const thread = readPhoneThread(chatId);
|
|
@@ -39370,7 +39632,12 @@ function emitChatTurn(opts, chatId, threadId) {
|
|
|
39370
39632
|
completedEmpty: () => "",
|
|
39371
39633
|
failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
|
|
39372
39634
|
});
|
|
39373
|
-
|
|
39635
|
+
const agent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
39636
|
+
emitControl(opts, {
|
|
39637
|
+
op: "assistant",
|
|
39638
|
+
chatId,
|
|
39639
|
+
text: phoneArtifactText(reply.trim(), agent?.parts)
|
|
39640
|
+
});
|
|
39374
39641
|
}
|
|
39375
39642
|
function phoneError(err) {
|
|
39376
39643
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -39378,20 +39645,73 @@ function phoneError(err) {
|
|
|
39378
39645
|
function deliverPhoneTurn(opts, chatId) {
|
|
39379
39646
|
const thread = readPhoneThread(chatId);
|
|
39380
39647
|
if (!thread || thread.status !== "running" && thread.status !== "queued") return;
|
|
39381
|
-
const
|
|
39382
|
-
|
|
39383
|
-
|
|
39648
|
+
const id = thread.id;
|
|
39649
|
+
const claimed = phoneTurnGen.get(id);
|
|
39650
|
+
if (claimed != null && claimed === chatGeneration.get(id)) return;
|
|
39651
|
+
const generation = bumpChat(id);
|
|
39652
|
+
phoneTurnGen.set(id, generation);
|
|
39653
|
+
enqueueChat(id, async () => {
|
|
39654
|
+
if (chatGeneration.get(id) !== generation) return;
|
|
39655
|
+
const stopStream = watchPhoneStream(opts, id, id, generation);
|
|
39384
39656
|
try {
|
|
39385
|
-
await getOrchestrator().waitForTurn(
|
|
39657
|
+
await getOrchestrator().waitForTurn(id, 14 * 60 * 1e3);
|
|
39386
39658
|
} catch (err) {
|
|
39387
|
-
|
|
39388
|
-
|
|
39659
|
+
stopStream();
|
|
39660
|
+
finishPhoneTurn(id, generation);
|
|
39661
|
+
if (chatGeneration.get(id) !== generation) return;
|
|
39662
|
+
emitControl(opts, { op: "error", chatId: id, message: `Sideboard failed: ${phoneError(err)}` });
|
|
39389
39663
|
return;
|
|
39390
39664
|
}
|
|
39391
|
-
|
|
39392
|
-
|
|
39665
|
+
stopStream();
|
|
39666
|
+
finishPhoneTurn(id, generation);
|
|
39667
|
+
if (chatGeneration.get(id) !== generation) return;
|
|
39668
|
+
emitChatTurn(opts, id, id);
|
|
39393
39669
|
});
|
|
39394
39670
|
}
|
|
39671
|
+
|
|
39672
|
+
// src/remote/listen.ts
|
|
39673
|
+
function formatRemotePrompt(text6) {
|
|
39674
|
+
return `Phone
|
|
39675
|
+
|
|
39676
|
+
${text6.trim()}`;
|
|
39677
|
+
}
|
|
39678
|
+
function isRemoteStopCommand(text6) {
|
|
39679
|
+
return text6.trim().toLowerCase() === "stop";
|
|
39680
|
+
}
|
|
39681
|
+
var handleChain2 = Promise.resolve();
|
|
39682
|
+
var inboundGeneration = 0;
|
|
39683
|
+
function enqueue(fn) {
|
|
39684
|
+
const run2 = handleChain2.then(fn, fn);
|
|
39685
|
+
handleChain2 = run2.then(
|
|
39686
|
+
() => void 0,
|
|
39687
|
+
() => void 0
|
|
39688
|
+
);
|
|
39689
|
+
}
|
|
39690
|
+
function interruptRemoteCoordinator(deviceId, log = () => void 0) {
|
|
39691
|
+
try {
|
|
39692
|
+
const live = findRemoteCoordinator(deviceId);
|
|
39693
|
+
if (!live) return false;
|
|
39694
|
+
const fresh = readThread(live.id) ?? live;
|
|
39695
|
+
if (fresh.status !== "running" && fresh.status !== "queued") return false;
|
|
39696
|
+
getOrchestrator().stop(fresh.id, { clearQueue: true });
|
|
39697
|
+
log(`interrupt phone \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`);
|
|
39698
|
+
return true;
|
|
39699
|
+
} catch (err) {
|
|
39700
|
+
log(`interrupt: ${err instanceof Error ? err.message : String(err)}`);
|
|
39701
|
+
return false;
|
|
39702
|
+
}
|
|
39703
|
+
}
|
|
39704
|
+
function publishOpenedChat(opts, chatId) {
|
|
39705
|
+
const opened = openPhoneChat(chatId);
|
|
39706
|
+
if (!opened) {
|
|
39707
|
+
emitControl(opts, { op: "error", message: "That agent is not on this Mac." });
|
|
39708
|
+
return;
|
|
39709
|
+
}
|
|
39710
|
+
emitControl(opts, { op: "opened", ...opened });
|
|
39711
|
+
if (opened.chat.status === "running" || opened.chat.status === "queued") {
|
|
39712
|
+
deliverPhoneTurn(opts, opened.chat.id);
|
|
39713
|
+
}
|
|
39714
|
+
}
|
|
39395
39715
|
function applyPhoneOptions(chatId, draft) {
|
|
39396
39716
|
const patch = {};
|
|
39397
39717
|
if (draft.agent) patch.agent = draft.agent;
|
|
@@ -39542,12 +39862,21 @@ function handlePhoneControl(cmd, opts) {
|
|
|
39542
39862
|
return;
|
|
39543
39863
|
}
|
|
39544
39864
|
if (cmd.op === "open") {
|
|
39545
|
-
const
|
|
39546
|
-
if (!
|
|
39865
|
+
const thread = findPhoneChat(cmd.chatId);
|
|
39866
|
+
if (!thread) {
|
|
39547
39867
|
emitControl(opts, { op: "error", message: "That agent is not on this Mac." });
|
|
39548
39868
|
return;
|
|
39549
39869
|
}
|
|
39550
|
-
|
|
39870
|
+
if (thread.status === "archived") {
|
|
39871
|
+
void getOrchestrator().restore(thread.id).then(() => {
|
|
39872
|
+
emitControl(opts, { op: "sidebar", ...listPhoneSidebar() });
|
|
39873
|
+
publishOpenedChat(opts, thread.id);
|
|
39874
|
+
}).catch((err) => {
|
|
39875
|
+
emitControl(opts, { op: "error", message: phoneError(err) });
|
|
39876
|
+
});
|
|
39877
|
+
return;
|
|
39878
|
+
}
|
|
39879
|
+
publishOpenedChat(opts, thread.id);
|
|
39551
39880
|
return;
|
|
39552
39881
|
}
|
|
39553
39882
|
if (cmd.op === "archive") {
|
|
@@ -39576,6 +39905,54 @@ function handlePhoneControl(cmd, opts) {
|
|
|
39576
39905
|
});
|
|
39577
39906
|
return;
|
|
39578
39907
|
}
|
|
39908
|
+
if (cmd.op === "media") {
|
|
39909
|
+
const thread = readPhoneThread(cmd.chatId);
|
|
39910
|
+
const dataUrl = thread ? phoneImageDataUrl(thread.worktreePath, cmd.path) : null;
|
|
39911
|
+
emitControl(opts, {
|
|
39912
|
+
op: "media",
|
|
39913
|
+
chatId: cmd.chatId,
|
|
39914
|
+
path: cmd.path,
|
|
39915
|
+
...dataUrl ? { dataUrl } : {}
|
|
39916
|
+
});
|
|
39917
|
+
return;
|
|
39918
|
+
}
|
|
39919
|
+
if (cmd.op === "open-file") {
|
|
39920
|
+
const thread = readPhoneThread(cmd.chatId);
|
|
39921
|
+
const relative = thread ? phoneWorktreeRelative(thread.worktreePath, cmd.path) : null;
|
|
39922
|
+
if (!thread || !relative) {
|
|
39923
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file is not in this worktree." });
|
|
39924
|
+
return;
|
|
39925
|
+
}
|
|
39926
|
+
void getOrchestrator().statPath(thread.id, relative).then((kind) => {
|
|
39927
|
+
if (kind === "missing") {
|
|
39928
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file is not in this worktree." });
|
|
39929
|
+
return;
|
|
39930
|
+
}
|
|
39931
|
+
opts.onOpenWorktreeFile?.({
|
|
39932
|
+
threadId: thread.id,
|
|
39933
|
+
path: relative,
|
|
39934
|
+
...kind === "dir" ? { directory: true } : {},
|
|
39935
|
+
...cmd.startLine != null ? { startLine: cmd.startLine } : {},
|
|
39936
|
+
...cmd.endLine != null ? { endLine: cmd.endLine } : {}
|
|
39937
|
+
});
|
|
39938
|
+
}).catch(() => {
|
|
39939
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That file is not in this worktree." });
|
|
39940
|
+
});
|
|
39941
|
+
return;
|
|
39942
|
+
}
|
|
39943
|
+
if (cmd.op === "open-artifact") {
|
|
39944
|
+
const thread = readPhoneThread(cmd.chatId);
|
|
39945
|
+
if (!thread) {
|
|
39946
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
39947
|
+
return;
|
|
39948
|
+
}
|
|
39949
|
+
opts.onOpenArtifact?.({
|
|
39950
|
+
threadId: thread.id,
|
|
39951
|
+
title: cmd.title,
|
|
39952
|
+
...cmd.hint ? { hint: cmd.hint } : {}
|
|
39953
|
+
});
|
|
39954
|
+
return;
|
|
39955
|
+
}
|
|
39579
39956
|
if (cmd.op === "stop") {
|
|
39580
39957
|
if (!readPhoneThread(cmd.chatId)) {
|
|
39581
39958
|
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
@@ -39588,6 +39965,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
39588
39965
|
}
|
|
39589
39966
|
const chatId = cmd.chatId;
|
|
39590
39967
|
const generation = bumpChat(chatId);
|
|
39968
|
+
phoneTurnGen.set(chatId, generation);
|
|
39591
39969
|
interruptChat(chatId, log);
|
|
39592
39970
|
enqueueChat(chatId, async () => {
|
|
39593
39971
|
if (chatGeneration.get(chatId) !== generation) return;
|
|
@@ -39603,6 +39981,7 @@ function handlePhoneControl(cmd, opts) {
|
|
|
39603
39981
|
}
|
|
39604
39982
|
}
|
|
39605
39983
|
if (chatGeneration.get(chatId) !== generation) return;
|
|
39984
|
+
const stopStream = watchPhoneStream(opts, chatId, thread.id, generation);
|
|
39606
39985
|
try {
|
|
39607
39986
|
try {
|
|
39608
39987
|
applyPhoneOptions(thread.id, cmd);
|
|
@@ -39620,6 +39999,9 @@ function handlePhoneControl(cmd, opts) {
|
|
|
39620
39999
|
const message = err instanceof Error ? err.message : String(err);
|
|
39621
40000
|
emitControl(opts, { op: "error", chatId, message: `Sideboard failed: ${message}` });
|
|
39622
40001
|
return;
|
|
40002
|
+
} finally {
|
|
40003
|
+
stopStream();
|
|
40004
|
+
finishPhoneTurn(chatId, generation);
|
|
39623
40005
|
}
|
|
39624
40006
|
if (chatGeneration.get(chatId) !== generation) return;
|
|
39625
40007
|
emitChatTurn(opts, chatId, thread.id);
|
|
@@ -39861,7 +40243,8 @@ function runRemoteHost(opts) {
|
|
|
39861
40243
|
handleRemoteInbound("stop", {
|
|
39862
40244
|
deviceId: opts.deviceId,
|
|
39863
40245
|
onLog: log,
|
|
39864
|
-
onOutbound: emit
|
|
40246
|
+
onOutbound: emit,
|
|
40247
|
+
onOpenWorktreeFile: opts.onOpenWorktreeFile
|
|
39865
40248
|
});
|
|
39866
40249
|
return;
|
|
39867
40250
|
}
|
|
@@ -39870,7 +40253,9 @@ function runRemoteHost(opts) {
|
|
|
39870
40253
|
deviceId: opts.deviceId,
|
|
39871
40254
|
onLog: log,
|
|
39872
40255
|
onOutbound: emit,
|
|
39873
|
-
transcribeWav: opts.transcribeWav
|
|
40256
|
+
transcribeWav: opts.transcribeWav,
|
|
40257
|
+
onOpenWorktreeFile: opts.onOpenWorktreeFile,
|
|
40258
|
+
onOpenArtifact: opts.onOpenArtifact
|
|
39874
40259
|
});
|
|
39875
40260
|
}
|
|
39876
40261
|
};
|
|
@@ -40269,9 +40654,74 @@ function sendJson2(res, status, body) {
|
|
|
40269
40654
|
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
40270
40655
|
res.end(JSON.stringify(body));
|
|
40271
40656
|
}
|
|
40657
|
+
function relayOAuthReturnPage(title, body) {
|
|
40658
|
+
return `<!DOCTYPE html>
|
|
40659
|
+
<html lang="en">
|
|
40660
|
+
<head>
|
|
40661
|
+
<meta charset="utf-8">
|
|
40662
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
40663
|
+
<meta name="robots" content="noindex">
|
|
40664
|
+
<title>${escapeHtml2(title)}</title>
|
|
40665
|
+
<style>
|
|
40666
|
+
:root {
|
|
40667
|
+
--bg: #09090b;
|
|
40668
|
+
--card: #16171b;
|
|
40669
|
+
--line: rgba(255, 255, 255, 0.08);
|
|
40670
|
+
--text: #f4f5f7;
|
|
40671
|
+
--muted: #9aa3ad;
|
|
40672
|
+
--blue: #3b9eff;
|
|
40673
|
+
--blue-fill: #0f7ed4;
|
|
40674
|
+
}
|
|
40675
|
+
* { box-sizing: border-box }
|
|
40676
|
+
body {
|
|
40677
|
+
margin: 0;
|
|
40678
|
+
min-height: 100vh;
|
|
40679
|
+
display: grid;
|
|
40680
|
+
place-items: center;
|
|
40681
|
+
padding: 48px 24px;
|
|
40682
|
+
background:
|
|
40683
|
+
radial-gradient(closest-side, rgba(15, 126, 212, 0.22), transparent 72%) center -120px / 720px 480px no-repeat,
|
|
40684
|
+
var(--bg);
|
|
40685
|
+
color: var(--text);
|
|
40686
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif;
|
|
40687
|
+
-webkit-font-smoothing: antialiased;
|
|
40688
|
+
}
|
|
40689
|
+
main {
|
|
40690
|
+
width: min(28rem, 100%);
|
|
40691
|
+
background: var(--card);
|
|
40692
|
+
border: 1px solid var(--line);
|
|
40693
|
+
border-radius: 16px;
|
|
40694
|
+
padding: 28px 28px 32px;
|
|
40695
|
+
}
|
|
40696
|
+
.brand {
|
|
40697
|
+
display: flex;
|
|
40698
|
+
align-items: center;
|
|
40699
|
+
gap: 10px;
|
|
40700
|
+
margin: 0 0 22px;
|
|
40701
|
+
font-weight: 650;
|
|
40702
|
+
font-size: 17px;
|
|
40703
|
+
letter-spacing: -0.02em;
|
|
40704
|
+
}
|
|
40705
|
+
.mark { position: relative; display: inline-block; width: 26px; height: 26px; flex: none }
|
|
40706
|
+
.mark i { position: absolute; width: 18px; height: 18px; border-radius: 3px; transform: rotate(14deg) }
|
|
40707
|
+
.mark i:first-child { left: 6px; top: 6px; background: var(--blue-fill) }
|
|
40708
|
+
.mark i:last-child { left: 1px; top: 1px; border: 2px solid #d7dbdf }
|
|
40709
|
+
h1 { margin: 0; font-size: 1.5rem; line-height: 1.2; letter-spacing: -0.03em; font-weight: 650 }
|
|
40710
|
+
p { margin: 10px 0 0; line-height: 1.55; color: var(--muted) }
|
|
40711
|
+
a { color: var(--blue) }
|
|
40712
|
+
</style>
|
|
40713
|
+
</head>
|
|
40714
|
+
<body>
|
|
40715
|
+
<main>
|
|
40716
|
+
<div class="brand"><span class="mark" aria-hidden="true"><i></i><i></i></span>Sideboard</div>
|
|
40717
|
+
${body}
|
|
40718
|
+
</main>
|
|
40719
|
+
</body>
|
|
40720
|
+
</html>`;
|
|
40721
|
+
}
|
|
40272
40722
|
function sendHtml(res, status, title, body) {
|
|
40273
40723
|
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
40274
|
-
res.end(
|
|
40724
|
+
res.end(relayOAuthReturnPage(title, body));
|
|
40275
40725
|
}
|
|
40276
40726
|
async function handleRelayAccountHttp(opts) {
|
|
40277
40727
|
const reqUrl = opts.req.url || "/";
|