@sideboard-ai/core 0.1.276 → 0.1.278
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 +178 -21
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +168 -11
- 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
|
}
|
|
@@ -38802,6 +38802,144 @@ init_workspaces();
|
|
|
38802
38802
|
init_chat_tabs();
|
|
38803
38803
|
init_create();
|
|
38804
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
|
|
38805
38943
|
var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
|
|
38806
38944
|
var PHONE_USER_PREFIX = "Phone\n\n";
|
|
38807
38945
|
var TRANSCRIPT_LIMIT = 40;
|
|
@@ -38856,6 +38994,7 @@ function parsePhoneControl(value) {
|
|
|
38856
38994
|
const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
|
|
38857
38995
|
if (!chatId) return "invalid";
|
|
38858
38996
|
if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
|
|
38997
|
+
if (op === "open-artifact") return parseOpenArtifact(value, chatId);
|
|
38859
38998
|
if (op === "open-file" || op === "media") {
|
|
38860
38999
|
const path2 = textField(value, "path");
|
|
38861
39000
|
if (!path2 || path2.length > 2e3) return "invalid";
|
|
@@ -39033,7 +39172,8 @@ function summarize(thread) {
|
|
|
39033
39172
|
title: thread.title?.trim() || "Untitled",
|
|
39034
39173
|
status: thread.status,
|
|
39035
39174
|
preview: previewOf(thread),
|
|
39036
|
-
updatedAt: thread.updatedAt
|
|
39175
|
+
updatedAt: thread.updatedAt,
|
|
39176
|
+
agent: thread.agent
|
|
39037
39177
|
};
|
|
39038
39178
|
}
|
|
39039
39179
|
function previewOf(thread) {
|
|
@@ -39047,10 +39187,8 @@ function listPhoneMessages(thread) {
|
|
|
39047
39187
|
for (const message of thread.messages) {
|
|
39048
39188
|
if (message.role !== "user" && message.role !== "agent") continue;
|
|
39049
39189
|
const names = (message.attachments ?? []).map((item) => item.name.trim()).filter(Boolean);
|
|
39050
|
-
const
|
|
39051
|
-
|
|
39052
|
-
MESSAGE_LIMIT
|
|
39053
|
-
);
|
|
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);
|
|
39054
39192
|
if (!text6) continue;
|
|
39055
39193
|
out.push({ role: message.role, text: text6 });
|
|
39056
39194
|
}
|
|
@@ -39364,7 +39502,7 @@ init_message_parts();
|
|
|
39364
39502
|
var PHONE_STREAM_FLUSH_MS = 80;
|
|
39365
39503
|
function phoneStreamFrame(parts) {
|
|
39366
39504
|
return {
|
|
39367
|
-
text: visibleAssistantText(void 0, parts),
|
|
39505
|
+
text: phoneArtifactText(visibleAssistantText(void 0, parts), parts),
|
|
39368
39506
|
activity: liveActivitySummary(parts)
|
|
39369
39507
|
};
|
|
39370
39508
|
}
|
|
@@ -39494,7 +39632,12 @@ function emitChatTurn(opts, chatId, threadId) {
|
|
|
39494
39632
|
completedEmpty: () => "",
|
|
39495
39633
|
failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
|
|
39496
39634
|
});
|
|
39497
|
-
|
|
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
|
+
});
|
|
39498
39641
|
}
|
|
39499
39642
|
function phoneError(err) {
|
|
39500
39643
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -39797,6 +39940,19 @@ function handlePhoneControl(cmd, opts) {
|
|
|
39797
39940
|
});
|
|
39798
39941
|
return;
|
|
39799
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
|
+
}
|
|
39800
39956
|
if (cmd.op === "stop") {
|
|
39801
39957
|
if (!readPhoneThread(cmd.chatId)) {
|
|
39802
39958
|
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
@@ -40098,7 +40254,8 @@ function runRemoteHost(opts) {
|
|
|
40098
40254
|
onLog: log,
|
|
40099
40255
|
onOutbound: emit,
|
|
40100
40256
|
transcribeWav: opts.transcribeWav,
|
|
40101
|
-
onOpenWorktreeFile: opts.onOpenWorktreeFile
|
|
40257
|
+
onOpenWorktreeFile: opts.onOpenWorktreeFile,
|
|
40258
|
+
onOpenArtifact: opts.onOpenArtifact
|
|
40102
40259
|
});
|
|
40103
40260
|
}
|
|
40104
40261
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -7159,6 +7159,12 @@ interface IpcApi {
|
|
|
7159
7159
|
startLine?: number;
|
|
7160
7160
|
endLine?: number;
|
|
7161
7161
|
}) => void): () => void;
|
|
7162
|
+
/** Phone asked the Mac to open a chat artifact in the side column. */
|
|
7163
|
+
onPhoneOpenArtifact(listener: (request: {
|
|
7164
|
+
threadId: string;
|
|
7165
|
+
title: string;
|
|
7166
|
+
hint?: string;
|
|
7167
|
+
}) => void): () => void;
|
|
7162
7168
|
/** Subscribe to store directory changes (CLI threads appear live). */
|
|
7163
7169
|
onThreadsChanged(listener: () => void): () => void;
|
|
7164
7170
|
getRepoPath(): Promise<string>;
|
|
@@ -7775,6 +7781,12 @@ declare class RemoteHub {
|
|
|
7775
7781
|
private fromHost;
|
|
7776
7782
|
}
|
|
7777
7783
|
|
|
7784
|
+
type PhoneOpenArtifactRequest = {
|
|
7785
|
+
threadId: string;
|
|
7786
|
+
title: string;
|
|
7787
|
+
hint?: string;
|
|
7788
|
+
};
|
|
7789
|
+
|
|
7778
7790
|
type PhoneOpenFileRequest = {
|
|
7779
7791
|
threadId: string;
|
|
7780
7792
|
path: string;
|
|
@@ -7801,6 +7813,8 @@ interface RemoteHostClientOptions {
|
|
|
7801
7813
|
transcribeWav?: (wavBase64: string) => Promise<string>;
|
|
7802
7814
|
/** Open a worktree file or folder in the desktop window. */
|
|
7803
7815
|
onOpenWorktreeFile?: (request: PhoneOpenFileRequest) => void;
|
|
7816
|
+
/** Open a chat artifact in the desktop side column. */
|
|
7817
|
+
onOpenArtifact?: (request: PhoneOpenArtifactRequest) => void;
|
|
7804
7818
|
/** Tests: override keepalive cadence. */
|
|
7805
7819
|
pingIntervalMs?: number;
|
|
7806
7820
|
pongTimeoutMs?: number;
|
|
@@ -7894,6 +7908,7 @@ declare function handleRemoteInbound(text: string, opts: {
|
|
|
7894
7908
|
onLog?: (line: string) => void;
|
|
7895
7909
|
transcribeWav?: (wavBase64: string) => Promise<string>;
|
|
7896
7910
|
onOpenWorktreeFile?: (request: PhoneOpenFileRequest) => void;
|
|
7911
|
+
onOpenArtifact?: (request: PhoneOpenArtifactRequest) => void;
|
|
7897
7912
|
}): void;
|
|
7898
7913
|
|
|
7899
7914
|
declare function isRemoteAccountLoginCancelled(err: unknown): boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -7159,6 +7159,12 @@ interface IpcApi {
|
|
|
7159
7159
|
startLine?: number;
|
|
7160
7160
|
endLine?: number;
|
|
7161
7161
|
}) => void): () => void;
|
|
7162
|
+
/** Phone asked the Mac to open a chat artifact in the side column. */
|
|
7163
|
+
onPhoneOpenArtifact(listener: (request: {
|
|
7164
|
+
threadId: string;
|
|
7165
|
+
title: string;
|
|
7166
|
+
hint?: string;
|
|
7167
|
+
}) => void): () => void;
|
|
7162
7168
|
/** Subscribe to store directory changes (CLI threads appear live). */
|
|
7163
7169
|
onThreadsChanged(listener: () => void): () => void;
|
|
7164
7170
|
getRepoPath(): Promise<string>;
|
|
@@ -7775,6 +7781,12 @@ declare class RemoteHub {
|
|
|
7775
7781
|
private fromHost;
|
|
7776
7782
|
}
|
|
7777
7783
|
|
|
7784
|
+
type PhoneOpenArtifactRequest = {
|
|
7785
|
+
threadId: string;
|
|
7786
|
+
title: string;
|
|
7787
|
+
hint?: string;
|
|
7788
|
+
};
|
|
7789
|
+
|
|
7778
7790
|
type PhoneOpenFileRequest = {
|
|
7779
7791
|
threadId: string;
|
|
7780
7792
|
path: string;
|
|
@@ -7801,6 +7813,8 @@ interface RemoteHostClientOptions {
|
|
|
7801
7813
|
transcribeWav?: (wavBase64: string) => Promise<string>;
|
|
7802
7814
|
/** Open a worktree file or folder in the desktop window. */
|
|
7803
7815
|
onOpenWorktreeFile?: (request: PhoneOpenFileRequest) => void;
|
|
7816
|
+
/** Open a chat artifact in the desktop side column. */
|
|
7817
|
+
onOpenArtifact?: (request: PhoneOpenArtifactRequest) => void;
|
|
7804
7818
|
/** Tests: override keepalive cadence. */
|
|
7805
7819
|
pingIntervalMs?: number;
|
|
7806
7820
|
pongTimeoutMs?: number;
|
|
@@ -7894,6 +7908,7 @@ declare function handleRemoteInbound(text: string, opts: {
|
|
|
7894
7908
|
onLog?: (line: string) => void;
|
|
7895
7909
|
transcribeWav?: (wavBase64: string) => Promise<string>;
|
|
7896
7910
|
onOpenWorktreeFile?: (request: PhoneOpenFileRequest) => void;
|
|
7911
|
+
onOpenArtifact?: (request: PhoneOpenArtifactRequest) => void;
|
|
7897
7912
|
}): void;
|
|
7898
7913
|
|
|
7899
7914
|
declare function isRemoteAccountLoginCancelled(err: unknown): boolean;
|
package/dist/index.js
CHANGED
|
@@ -4098,8 +4098,8 @@ function crushMarkdown(text6, opts) {
|
|
|
4098
4098
|
}
|
|
4099
4099
|
const must = blocks.filter((b) => looksLikeMustKeep(b));
|
|
4100
4100
|
const rest = blocks.filter((b) => !looksLikeMustKeep(b));
|
|
4101
|
-
const
|
|
4102
|
-
let used = must.reduce((n, b) => n + b.length, 0) +
|
|
4101
|
+
const marker2 = `[\u2026crushed body; ${original.length} chars originally. Pass include=full to read the rest.]`;
|
|
4102
|
+
let used = must.reduce((n, b) => n + b.length, 0) + marker2.length;
|
|
4103
4103
|
const keptRest = [];
|
|
4104
4104
|
const headN = Math.max(1, Math.ceil(rest.length * 0.3));
|
|
4105
4105
|
const tailN = Math.max(1, Math.ceil(rest.length * 0.15));
|
|
@@ -4139,7 +4139,7 @@ function crushMarkdown(text6, opts) {
|
|
|
4139
4139
|
continue;
|
|
4140
4140
|
}
|
|
4141
4141
|
if (!inserted) {
|
|
4142
|
-
out.push(
|
|
4142
|
+
out.push(marker2);
|
|
4143
4143
|
inserted = true;
|
|
4144
4144
|
}
|
|
4145
4145
|
}
|
|
@@ -9426,6 +9426,144 @@ function phoneImageDataUrl(worktreePath, raw) {
|
|
|
9426
9426
|
|
|
9427
9427
|
// src/remote/phone-chats.ts
|
|
9428
9428
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
9429
|
+
|
|
9430
|
+
// src/remote/phone-artifact.ts
|
|
9431
|
+
var FENCE_RE = /```([a-zA-Z0-9_+-]*)[^\n]*\n([\s\S]*?)(?:```|$)/g;
|
|
9432
|
+
var DOC_LANGS = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "svg", "markdown", "md", "mdx", "jsx", "tsx", "react"]);
|
|
9433
|
+
var MIN_FENCE_CHARS = 40;
|
|
9434
|
+
var MIN_MD_CHARS = 120;
|
|
9435
|
+
function oneLine(value, max) {
|
|
9436
|
+
return value.replace(/[`\r\n]/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
|
|
9437
|
+
}
|
|
9438
|
+
function asRecord3(value) {
|
|
9439
|
+
if (typeof value === "string") {
|
|
9440
|
+
try {
|
|
9441
|
+
return asRecord3(JSON.parse(value));
|
|
9442
|
+
} catch {
|
|
9443
|
+
return void 0;
|
|
9444
|
+
}
|
|
9445
|
+
}
|
|
9446
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
9447
|
+
return void 0;
|
|
9448
|
+
}
|
|
9449
|
+
function str3(value) {
|
|
9450
|
+
return typeof value === "string" ? value : "";
|
|
9451
|
+
}
|
|
9452
|
+
function normalizedBody(content) {
|
|
9453
|
+
return content.trim().replace(/\s+/g, " ");
|
|
9454
|
+
}
|
|
9455
|
+
function artifactHint(content) {
|
|
9456
|
+
const body = normalizedBody(content);
|
|
9457
|
+
if (!body) return "";
|
|
9458
|
+
return fnv1a32(body).toString(36);
|
|
9459
|
+
}
|
|
9460
|
+
function fnv1a32(value) {
|
|
9461
|
+
let hash = 2166136261;
|
|
9462
|
+
for (let i = 0; i < value.length; i++) {
|
|
9463
|
+
hash ^= value.charCodeAt(i);
|
|
9464
|
+
hash = Math.imul(hash, 16777619);
|
|
9465
|
+
}
|
|
9466
|
+
return hash >>> 0;
|
|
9467
|
+
}
|
|
9468
|
+
function looksLikeHtml(content) {
|
|
9469
|
+
const head = content.trim().slice(0, 200).toLowerCase();
|
|
9470
|
+
return head.startsWith("<!doctype") || head.startsWith("<html") || head.startsWith("<svg");
|
|
9471
|
+
}
|
|
9472
|
+
function kindFor(language, content) {
|
|
9473
|
+
const lang = language.toLowerCase();
|
|
9474
|
+
if (!lang) {
|
|
9475
|
+
if (!looksLikeHtml(content) || content.trim().length < MIN_FENCE_CHARS) return null;
|
|
9476
|
+
return content.trim().toLowerCase().startsWith("<svg") ? "svg" : "html";
|
|
9477
|
+
}
|
|
9478
|
+
if (!DOC_LANGS.has(lang)) return null;
|
|
9479
|
+
const min = lang === "markdown" || lang === "md" || lang === "mdx" ? MIN_MD_CHARS : MIN_FENCE_CHARS;
|
|
9480
|
+
if (content.trim().length < min) return null;
|
|
9481
|
+
if (lang === "svg") return "svg";
|
|
9482
|
+
if (lang === "markdown" || lang === "md" || lang === "mdx") return "markdown";
|
|
9483
|
+
if (lang === "jsx" || lang === "tsx" || lang === "react") return "react";
|
|
9484
|
+
return "html";
|
|
9485
|
+
}
|
|
9486
|
+
function titleFor(kind, content) {
|
|
9487
|
+
if (kind === "html" || kind === "svg") {
|
|
9488
|
+
const title = /<title[^>]*>([^<]+)<\/title>/i.exec(content)?.[1]?.trim();
|
|
9489
|
+
if (title) return oneLine(title, 80);
|
|
9490
|
+
}
|
|
9491
|
+
if (kind === "markdown") {
|
|
9492
|
+
const heading = /^#\s+(.+)$/m.exec(content)?.[1]?.trim();
|
|
9493
|
+
if (heading) return oneLine(heading, 80);
|
|
9494
|
+
}
|
|
9495
|
+
if (kind === "react") {
|
|
9496
|
+
const name = /export\s+default\s+(?:function|class)\s+([A-Za-z_$][\w$]*)/.exec(content)?.[1];
|
|
9497
|
+
if (name) return name;
|
|
9498
|
+
}
|
|
9499
|
+
if (kind === "svg") return "SVG artifact";
|
|
9500
|
+
if (kind === "markdown") return "Document";
|
|
9501
|
+
if (kind === "react") return "React artifact";
|
|
9502
|
+
return "HTML artifact";
|
|
9503
|
+
}
|
|
9504
|
+
function marker(title, hint) {
|
|
9505
|
+
return hint ? `\`\`\`sb-artifact
|
|
9506
|
+
${title}
|
|
9507
|
+
${hint}
|
|
9508
|
+
\`\`\`` : `\`\`\`sb-artifact
|
|
9509
|
+
${title}
|
|
9510
|
+
\`\`\``;
|
|
9511
|
+
}
|
|
9512
|
+
function toolContent(input) {
|
|
9513
|
+
if (!input) return "";
|
|
9514
|
+
const direct = input.content;
|
|
9515
|
+
if (typeof direct === "string") return direct;
|
|
9516
|
+
const nested = asRecord3(direct);
|
|
9517
|
+
if (!nested) return "";
|
|
9518
|
+
for (const key of ["html", "markup", "text", "source", "content", "code"]) {
|
|
9519
|
+
if (typeof nested[key] === "string") return nested[key];
|
|
9520
|
+
}
|
|
9521
|
+
return "";
|
|
9522
|
+
}
|
|
9523
|
+
function isArtifactTool(name) {
|
|
9524
|
+
const short = name.split("__").pop() ?? name;
|
|
9525
|
+
return /^(present_artifact|create_artifact|update_artifact)$/i.test(short);
|
|
9526
|
+
}
|
|
9527
|
+
function phoneArtifactText(text6, parts, limit = Number.POSITIVE_INFINITY) {
|
|
9528
|
+
const hints = /* @__PURE__ */ new Set();
|
|
9529
|
+
const body = text6.replace(new RegExp(FENCE_RE.source, "g"), (full, language, content) => {
|
|
9530
|
+
const kind = kindFor(language, content);
|
|
9531
|
+
if (!kind) return full;
|
|
9532
|
+
const hint = artifactHint(content);
|
|
9533
|
+
hints.add(hint);
|
|
9534
|
+
return marker(titleFor(kind, content), hint);
|
|
9535
|
+
});
|
|
9536
|
+
const extras = [];
|
|
9537
|
+
for (const part of parts ?? []) {
|
|
9538
|
+
if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
|
|
9539
|
+
const input = asRecord3(part.input);
|
|
9540
|
+
const content = toolContent(input);
|
|
9541
|
+
const hint = artifactHint(content);
|
|
9542
|
+
if (hint && hints.has(hint)) continue;
|
|
9543
|
+
const kind = kindFor("", content);
|
|
9544
|
+
const title = oneLine(str3(input?.title), 80) || (kind ? titleFor(kind, content) : "Artifact");
|
|
9545
|
+
if (!str3(input?.title) && content.trim().length < MIN_FENCE_CHARS) continue;
|
|
9546
|
+
if (hint) hints.add(hint);
|
|
9547
|
+
extras.push(marker(title, hint));
|
|
9548
|
+
}
|
|
9549
|
+
const tail = extras.join("\n\n");
|
|
9550
|
+
const room = tail ? Math.max(0, limit - tail.length - 2) : limit;
|
|
9551
|
+
const prose = body.length > room ? `${body.slice(0, Math.max(0, room - 1))}\u2026` : body;
|
|
9552
|
+
if (!tail) return prose;
|
|
9553
|
+
return prose ? `${prose}
|
|
9554
|
+
|
|
9555
|
+
${tail}` : tail;
|
|
9556
|
+
}
|
|
9557
|
+
function parseOpenArtifact(value, chatId) {
|
|
9558
|
+
const record = asRecord3(value);
|
|
9559
|
+
if (!record) return "invalid";
|
|
9560
|
+
const title = oneLine(str3(record.title), 200);
|
|
9561
|
+
if (!title) return "invalid";
|
|
9562
|
+
const hint = oneLine(str3(record.hint), 200);
|
|
9563
|
+
return { op: "open-artifact", chatId, title, ...hint ? { hint } : {} };
|
|
9564
|
+
}
|
|
9565
|
+
|
|
9566
|
+
// src/remote/phone-chats.ts
|
|
9429
9567
|
var PHONE_CONTROL_PREFIX = "\0sb.phone\n";
|
|
9430
9568
|
var PHONE_USER_PREFIX = "Phone\n\n";
|
|
9431
9569
|
var TRANSCRIPT_LIMIT = 40;
|
|
@@ -9480,6 +9618,7 @@ function parsePhoneControl(value) {
|
|
|
9480
9618
|
const chatId = typeof value.chatId === "string" ? value.chatId.trim() : "";
|
|
9481
9619
|
if (!chatId) return "invalid";
|
|
9482
9620
|
if (op === "open" || op === "archive" || op === "restore" || op === "stop") return { op, chatId };
|
|
9621
|
+
if (op === "open-artifact") return parseOpenArtifact(value, chatId);
|
|
9483
9622
|
if (op === "open-file" || op === "media") {
|
|
9484
9623
|
const path2 = textField(value, "path");
|
|
9485
9624
|
if (!path2 || path2.length > 2e3) return "invalid";
|
|
@@ -9657,7 +9796,8 @@ function summarize(thread) {
|
|
|
9657
9796
|
title: thread.title?.trim() || "Untitled",
|
|
9658
9797
|
status: thread.status,
|
|
9659
9798
|
preview: previewOf(thread),
|
|
9660
|
-
updatedAt: thread.updatedAt
|
|
9799
|
+
updatedAt: thread.updatedAt,
|
|
9800
|
+
agent: thread.agent
|
|
9661
9801
|
};
|
|
9662
9802
|
}
|
|
9663
9803
|
function previewOf(thread) {
|
|
@@ -9671,10 +9811,8 @@ function listPhoneMessages(thread) {
|
|
|
9671
9811
|
for (const message of thread.messages) {
|
|
9672
9812
|
if (message.role !== "user" && message.role !== "agent") continue;
|
|
9673
9813
|
const names = (message.attachments ?? []).map((item) => item.name.trim()).filter(Boolean);
|
|
9674
|
-
const
|
|
9675
|
-
|
|
9676
|
-
MESSAGE_LIMIT
|
|
9677
|
-
);
|
|
9814
|
+
const visible = [phoneVisibleText(message.role, message.text), names.length ? names.join(", ") : ""].filter(Boolean).join("\n");
|
|
9815
|
+
const text6 = message.role === "agent" ? phoneArtifactText(visible, message.parts, MESSAGE_LIMIT) : clip(visible, MESSAGE_LIMIT);
|
|
9678
9816
|
if (!text6) continue;
|
|
9679
9817
|
out.push({ role: message.role, text: text6 });
|
|
9680
9818
|
}
|
|
@@ -9982,7 +10120,7 @@ async function listPhoneSources(repoPath, query) {
|
|
|
9982
10120
|
var PHONE_STREAM_FLUSH_MS = 80;
|
|
9983
10121
|
function phoneStreamFrame(parts) {
|
|
9984
10122
|
return {
|
|
9985
|
-
text: visibleAssistantText(void 0, parts),
|
|
10123
|
+
text: phoneArtifactText(visibleAssistantText(void 0, parts), parts),
|
|
9986
10124
|
activity: liveActivitySummary(parts)
|
|
9987
10125
|
};
|
|
9988
10126
|
}
|
|
@@ -10112,7 +10250,12 @@ function emitChatTurn(opts, chatId, threadId) {
|
|
|
10112
10250
|
completedEmpty: () => "",
|
|
10113
10251
|
failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
|
|
10114
10252
|
});
|
|
10115
|
-
|
|
10253
|
+
const agent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
10254
|
+
emitControl(opts, {
|
|
10255
|
+
op: "assistant",
|
|
10256
|
+
chatId,
|
|
10257
|
+
text: phoneArtifactText(reply.trim(), agent?.parts)
|
|
10258
|
+
});
|
|
10116
10259
|
}
|
|
10117
10260
|
function phoneError(err) {
|
|
10118
10261
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -10415,6 +10558,19 @@ function handlePhoneControl(cmd, opts) {
|
|
|
10415
10558
|
});
|
|
10416
10559
|
return;
|
|
10417
10560
|
}
|
|
10561
|
+
if (cmd.op === "open-artifact") {
|
|
10562
|
+
const thread = readPhoneThread(cmd.chatId);
|
|
10563
|
+
if (!thread) {
|
|
10564
|
+
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
10565
|
+
return;
|
|
10566
|
+
}
|
|
10567
|
+
opts.onOpenArtifact?.({
|
|
10568
|
+
threadId: thread.id,
|
|
10569
|
+
title: cmd.title,
|
|
10570
|
+
...cmd.hint ? { hint: cmd.hint } : {}
|
|
10571
|
+
});
|
|
10572
|
+
return;
|
|
10573
|
+
}
|
|
10418
10574
|
if (cmd.op === "stop") {
|
|
10419
10575
|
if (!readPhoneThread(cmd.chatId)) {
|
|
10420
10576
|
emitControl(opts, { op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
|
|
@@ -10716,7 +10872,8 @@ function runRemoteHost(opts) {
|
|
|
10716
10872
|
onLog: log,
|
|
10717
10873
|
onOutbound: emit,
|
|
10718
10874
|
transcribeWav: opts.transcribeWav,
|
|
10719
|
-
onOpenWorktreeFile: opts.onOpenWorktreeFile
|
|
10875
|
+
onOpenWorktreeFile: opts.onOpenWorktreeFile,
|
|
10876
|
+
onOpenArtifact: opts.onOpenArtifact
|
|
10720
10877
|
});
|
|
10721
10878
|
}
|
|
10722
10879
|
};
|