@sideboard-ai/core 0.1.301 → 0.1.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/agents/claude-runner.cjs +28 -7
  2. package/dist/agents/claude-runner.js +22 -8
  3. package/dist/{agents-V2SRC2LH.js → agents-3GN4E2X3.js} +5 -5
  4. package/dist/{agents-ETD2LWP4.js → agents-XLHFVC64.js} +4 -4
  5. package/dist/{chunk-FUXOZHHF.js → chunk-3TZRSKFJ.js} +4 -4
  6. package/dist/{chunk-JTW4CZEQ.js → chunk-545DY7CQ.js} +13 -13
  7. package/dist/{chunk-3XDU7RIE.js → chunk-5MVOZLN7.js} +1 -1
  8. package/dist/{chunk-2KJE53ZJ.js → chunk-7AYOOG77.js} +5 -4
  9. package/dist/{chunk-7SEWZXPT.js → chunk-BRFDPV7Z.js} +2 -2
  10. package/dist/{chunk-EP4IIKP4.js → chunk-DW6NNYUJ.js} +4 -4
  11. package/dist/{chunk-FO3NKZVJ.js → chunk-F2PVRF3B.js} +9 -0
  12. package/dist/{chunk-KIWICTLW.js → chunk-GEFH4QFX.js} +1 -1
  13. package/dist/{chunk-FPJFK4LB.js → chunk-GS6O65Q2.js} +11 -11
  14. package/dist/{chunk-WV33I2GC.js → chunk-P2GHFJOB.js} +1 -1
  15. package/dist/{chunk-OJKSDRZU.js → chunk-RAGDMEIB.js} +4 -4
  16. package/dist/{chunk-LZ744H2I.js → chunk-YCDHLK2Z.js} +2 -2
  17. package/dist/{conductor-57UKACQA.js → conductor-2AVFGSQJ.js} +4 -4
  18. package/dist/{conductor-57KZLWTB.js → conductor-ATST6OMC.js} +5 -5
  19. package/dist/{coordinator-prompt-VOWUJRVJ.js → coordinator-prompt-CQPUNMRX.js} +4 -4
  20. package/dist/{coordinator-prompt-2TBRFJZ5.js → coordinator-prompt-TIAI6A47.js} +3 -3
  21. package/dist/{global-workspace-V6SOEETA.js → global-workspace-7YABMLQG.js} +4 -4
  22. package/dist/{global-workspace-T5C7EMVV.js → global-workspace-UZJUHGQP.js} +3 -3
  23. package/dist/index.cjs +851 -367
  24. package/dist/index.d.cts +104 -2
  25. package/dist/index.d.ts +104 -2
  26. package/dist/index.js +825 -354
  27. package/dist/mcp/run-stdio.js +6 -6
  28. package/dist/{orchestrator-C43M3JDK.js → orchestrator-COX6QO2B.js} +6 -6
  29. package/dist/{orchestrator-JEF7JC3S.js → orchestrator-CZPRDLHV.js} +7 -7
  30. package/dist/{thread-store-POAYB2ID.js → thread-store-PNX4HVLO.js} +2 -2
  31. package/dist/{thread-store-BMNJEJUH.js → thread-store-VC4AIKN3.js} +1 -1
  32. package/dist/{workspaces-EWVIEJ4O.js → workspaces-6DJL4IST.js} +4 -4
  33. package/dist/{workspaces-7ZYTTB4A.js → workspaces-GZ7F5EJR.js} +3 -3
  34. package/dist/{worktree-2Q6J2VWZ.js → worktree-2BYVRYPJ.js} +3 -3
  35. package/dist/{worktree-ZAJGYKCB.js → worktree-VXPOY2LE.js} +2 -2
  36. package/package.json +1 -1
  37. /package/dist/{chunk-FBZODCO4.js → chunk-FA3V56KF.js} +0 -0
package/dist/index.cjs CHANGED
@@ -65,6 +65,14 @@ function parseMcpList(text7) {
65
65
  function sanitizeMcpServerName(name) {
66
66
  return name.replace(/[^A-Za-z0-9_-]/g, "_");
67
67
  }
68
+ function isClaudeAiConnectorMcp(toolName, serverName, serverSource) {
69
+ if (!toolName.startsWith("mcp__")) return false;
70
+ if (serverSource === "claudeai") return true;
71
+ if (serverName && /^claude\.ai\b/i.test(serverName.trim())) return true;
72
+ if (/^mcp__claude_ai_/i.test(toolName)) return true;
73
+ const id = mcpServerIdForPermission(toolName, serverName);
74
+ return id != null && id.startsWith("claude_ai_");
75
+ }
68
76
  function mcpServerIdForPermission(toolName, serverName) {
69
77
  if (toolName.startsWith("mcp__")) {
70
78
  const rest = toolName.slice("mcp__".length);
@@ -31334,6 +31342,7 @@ __export(index_exports, {
31334
31342
  clipAgentEventForPaint: () => clipAgentEventForPaint,
31335
31343
  clipThinkingForStore: () => clipThinkingForStore,
31336
31344
  cloneRepoIntoSideboard: () => cloneRepoIntoSideboard,
31345
+ closeMcpUiHost: () => closeMcpUiHost,
31337
31346
  codeRefRangeLabel: () => codeRefRangeLabel,
31338
31347
  codexAdapter: () => codexAdapter,
31339
31348
  codexSandboxWritableRootsArgs: () => codexSandboxWritableRootsArgs,
@@ -31556,6 +31565,7 @@ __export(index_exports, {
31556
31565
  getLinearApiKey: () => getLinearApiKey,
31557
31566
  getLinearAuthToken: () => getLinearAuthToken,
31558
31567
  getLinearIssue: () => getLinearIssue,
31568
+ getMcpUiHost: () => getMcpUiHost,
31559
31569
  getOrchestrator: () => getOrchestrator,
31560
31570
  getPr: () => getPr,
31561
31571
  getPrChecks: () => getPrChecks,
@@ -31621,6 +31631,7 @@ __export(index_exports, {
31621
31631
  isBrightsyConnected: () => isBrightsyConnected,
31622
31632
  isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
31623
31633
  isChatTextScale: () => isChatTextScale,
31634
+ isClaudeAiConnectorMcp: () => isClaudeAiConnectorMcp,
31624
31635
  isCloudCoordinatorThread: () => isCloudCoordinatorThread,
31625
31636
  isCompactToolName: () => isCompactToolName,
31626
31637
  isConductorBundledCli: () => isConductorBundledCli,
@@ -31775,6 +31786,7 @@ __export(index_exports, {
31775
31786
  mcpServerIdForPermission: () => mcpServerIdForPermission,
31776
31787
  mcpServerPermissionUpdate: () => mcpServerPermissionUpdate,
31777
31788
  mcpStatusThinkingFromClaudeInit: () => mcpStatusThinkingFromClaudeInit,
31789
+ mcpUiContentSecurityPolicy: () => mcpUiContentSecurityPolicy,
31778
31790
  mentionsPrGoal: () => mentionsPrGoal,
31779
31791
  mergeAgentGitAuthEnv: () => mergeAgentGitAuthEnv,
31780
31792
  mergePr: () => mergePr,
@@ -38886,6 +38898,362 @@ init_injected_mcp();
38886
38898
  init_user_mcp_config();
38887
38899
  init_orch_mcp_isolation();
38888
38900
 
38901
+ // src/mcp-ui/host.ts
38902
+ init_orch_mcp_isolation();
38903
+
38904
+ // src/mcp-ui/meta.ts
38905
+ var HTML_MAX = 15e5;
38906
+ function matchClaudeMcpTool(toolName, serverNames) {
38907
+ const name = toolName.trim();
38908
+ if (!name.startsWith("mcp__")) return null;
38909
+ const ranked = [...serverNames].filter((s) => s.trim()).sort((a, b) => b.length - a.length);
38910
+ for (const server of ranked) {
38911
+ const prefix = `mcp__${server}__`;
38912
+ if (!name.startsWith(prefix)) continue;
38913
+ const tool = name.slice(prefix.length).trim();
38914
+ if (!tool) continue;
38915
+ return { server, tool };
38916
+ }
38917
+ return null;
38918
+ }
38919
+ function parseMcpServerLaunch(entry) {
38920
+ const command = typeof entry.command === "string" ? entry.command.trim() : "";
38921
+ if (command) {
38922
+ const args = Array.isArray(entry.args) ? entry.args.filter((a) => typeof a === "string") : [];
38923
+ const env = stringRecord(entry.env);
38924
+ const cwd = typeof entry.cwd === "string" && entry.cwd.trim() ? entry.cwd.trim() : void 0;
38925
+ return {
38926
+ kind: "stdio",
38927
+ command,
38928
+ args,
38929
+ ...env ? { env } : {},
38930
+ ...cwd ? { cwd } : {}
38931
+ };
38932
+ }
38933
+ const url = typeof entry.url === "string" ? entry.url.trim() : "";
38934
+ if (!url) return null;
38935
+ let parsed;
38936
+ try {
38937
+ parsed = new URL(url);
38938
+ } catch {
38939
+ return null;
38940
+ }
38941
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
38942
+ const type = typeof entry.type === "string" ? entry.type.trim().toLowerCase() : "";
38943
+ const headers = stringRecord(entry.headers);
38944
+ return {
38945
+ kind: type === "sse" ? "sse" : "http",
38946
+ url: parsed.toString(),
38947
+ ...headers ? { headers } : {}
38948
+ };
38949
+ }
38950
+ function uiResourceUriFromMeta(meta) {
38951
+ const rec = asRecord15(meta);
38952
+ const nested = asRecord15(rec.ui);
38953
+ const fromNested = typeof nested.resourceUri === "string" ? nested.resourceUri.trim() : "";
38954
+ const legacy = typeof rec["ui/resourceUri"] === "string" ? rec["ui/resourceUri"].trim() : "";
38955
+ const uri = fromNested || legacy;
38956
+ return uri.startsWith("ui://") ? uri : null;
38957
+ }
38958
+ function cspFromMeta(meta) {
38959
+ const ui = asRecord15(asRecord15(meta).ui);
38960
+ const csp = asRecord15(ui.csp);
38961
+ const connectDomains = originList(csp.connectDomains);
38962
+ const resourceDomains = originList(csp.resourceDomains);
38963
+ const frameDomains = originList(csp.frameDomains);
38964
+ const baseUriDomains = originList(csp.baseUriDomains);
38965
+ if (!connectDomains?.length && !resourceDomains?.length && !frameDomains?.length && !baseUriDomains?.length) {
38966
+ return void 0;
38967
+ }
38968
+ return {
38969
+ ...connectDomains?.length ? { connectDomains } : {},
38970
+ ...resourceDomains?.length ? { resourceDomains } : {},
38971
+ ...frameDomains?.length ? { frameDomains } : {},
38972
+ ...baseUriDomains?.length ? { baseUriDomains } : {}
38973
+ };
38974
+ }
38975
+ function htmlFromResourceContent(content) {
38976
+ const uri = content.uri?.trim() ?? "";
38977
+ if (!uri.startsWith("ui://")) return null;
38978
+ const mimeType = content.mimeType?.trim() || "text/html";
38979
+ if (!isHtmlMime(mimeType)) return null;
38980
+ const html = htmlText(content.text, content.blob);
38981
+ if (!html) return null;
38982
+ return { uri, mimeType, html };
38983
+ }
38984
+ function mcpUiContentSecurityPolicy(csp) {
38985
+ const resource = (csp?.resourceDomains ?? []).join(" ");
38986
+ const connect = (csp?.connectDomains ?? []).join(" ");
38987
+ const frames = csp?.frameDomains?.length ? csp.frameDomains.join(" ") : "'none'";
38988
+ const base = csp?.baseUriDomains?.length ? csp.baseUriDomains.join(" ") : "'none'";
38989
+ return [
38990
+ "default-src 'none'",
38991
+ `script-src 'self' 'unsafe-inline' 'unsafe-eval' ${resource}`.trim(),
38992
+ `style-src 'self' 'unsafe-inline' ${resource}`.trim(),
38993
+ `img-src 'self' data: blob: ${resource}`.trim(),
38994
+ `font-src 'self' data: ${resource}`.trim(),
38995
+ `connect-src 'self' ${connect}`.trim(),
38996
+ `media-src 'self' data: blob: ${resource}`.trim(),
38997
+ `frame-src ${frames}`,
38998
+ `base-uri ${base}`,
38999
+ "object-src 'none'",
39000
+ "form-action 'self'"
39001
+ ].join("; ");
39002
+ }
39003
+ function htmlText(text7, blob) {
39004
+ if (typeof text7 === "string" && text7.trim()) {
39005
+ return text7.length <= HTML_MAX ? text7 : null;
39006
+ }
39007
+ if (typeof blob !== "string" || !blob.trim()) return null;
39008
+ try {
39009
+ const decoded = decodeBase64(blob.trim());
39010
+ if (!decoded.trim() || decoded.length > HTML_MAX) return null;
39011
+ return decoded;
39012
+ } catch {
39013
+ return null;
39014
+ }
39015
+ }
39016
+ function decodeBase64(blob) {
39017
+ if (typeof Buffer !== "undefined") return Buffer.from(blob, "base64").toString("utf8");
39018
+ const bin = atob(blob);
39019
+ const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));
39020
+ return new TextDecoder().decode(bytes);
39021
+ }
39022
+ function isHtmlMime(mime) {
39023
+ const base = mime.split(";")[0]?.trim().toLowerCase() ?? "";
39024
+ return base === "text/html";
39025
+ }
39026
+ function originList(value) {
39027
+ if (!Array.isArray(value)) return void 0;
39028
+ const out = [];
39029
+ for (const item of value) {
39030
+ if (typeof item !== "string") continue;
39031
+ const origin = originToken(item);
39032
+ if (origin && !out.includes(origin)) out.push(origin);
39033
+ }
39034
+ return out;
39035
+ }
39036
+ function originToken(value) {
39037
+ try {
39038
+ const url = new URL(value.trim());
39039
+ if (url.protocol !== "https:" && url.protocol !== "http:" && url.protocol !== "wss:" && url.protocol !== "ws:") {
39040
+ return null;
39041
+ }
39042
+ return url.origin;
39043
+ } catch {
39044
+ return null;
39045
+ }
39046
+ }
39047
+ function stringRecord(value) {
39048
+ const rec = asRecord15(value);
39049
+ const out = {};
39050
+ for (const [key, item] of Object.entries(rec)) {
39051
+ if (typeof item === "string") out[key] = item;
39052
+ }
39053
+ return Object.keys(out).length > 0 ? out : void 0;
39054
+ }
39055
+ function asRecord15(value) {
39056
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
39057
+ return value;
39058
+ }
39059
+
39060
+ // src/mcp-ui/session.ts
39061
+ var import_client3 = require("@modelcontextprotocol/sdk/client/index.js");
39062
+ var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
39063
+ var import_stdio2 = require("@modelcontextprotocol/sdk/client/stdio.js");
39064
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
39065
+ var REQUEST_TIMEOUT_MS = 2e4;
39066
+ var UI_CAPABILITIES = {
39067
+ extensions: {
39068
+ "io.modelcontextprotocol/ui": {
39069
+ mimeTypes: ["text/html;profile=mcp-app"]
39070
+ }
39071
+ }
39072
+ };
39073
+ async function openMcpUiSession(launch) {
39074
+ return openMcpUiSessionFromTransport(transportForLaunch(launch));
39075
+ }
39076
+ async function openMcpUiSessionFromTransport(transport) {
39077
+ const client = new import_client3.Client(
39078
+ { name: "sideboard", version: "1.0.0" },
39079
+ { capabilities: UI_CAPABILITIES }
39080
+ );
39081
+ await client.connect(transport, { timeout: REQUEST_TIMEOUT_MS });
39082
+ return {
39083
+ async listTools() {
39084
+ const listed = await client.listTools(void 0, { timeout: REQUEST_TIMEOUT_MS });
39085
+ return listed.tools.map((tool) => {
39086
+ const meta = tool._meta;
39087
+ return {
39088
+ name: tool.name,
39089
+ ...tool.description ? { description: tool.description } : {},
39090
+ ...tool.inputSchema ? { inputSchema: tool.inputSchema } : {},
39091
+ resourceUri: uiResourceUriFromMeta(meta),
39092
+ ...cspFromMeta(meta) ? { csp: cspFromMeta(meta) } : {}
39093
+ };
39094
+ });
39095
+ },
39096
+ async readResource(uri) {
39097
+ const result = await client.readResource({ uri }, { timeout: REQUEST_TIMEOUT_MS });
39098
+ const content = result.contents?.[0];
39099
+ if (!content) return null;
39100
+ const text7 = "text" in content && typeof content.text === "string" ? content.text : void 0;
39101
+ const blob = "blob" in content && typeof content.blob === "string" ? content.blob : void 0;
39102
+ return {
39103
+ uri: content.uri,
39104
+ ...content.mimeType ? { mimeType: content.mimeType } : {},
39105
+ ...text7 != null ? { text: text7 } : {},
39106
+ ...blob != null ? { blob } : {},
39107
+ ...cspFromMeta(content._meta) ? { csp: cspFromMeta(content._meta) } : {}
39108
+ };
39109
+ },
39110
+ async callTool(name, args) {
39111
+ return client.callTool({ name, arguments: args }, void 0, { timeout: REQUEST_TIMEOUT_MS });
39112
+ },
39113
+ async close() {
39114
+ await client.close();
39115
+ }
39116
+ };
39117
+ }
39118
+ function transportForLaunch(launch) {
39119
+ if (launch.kind === "stdio") {
39120
+ return new import_stdio2.StdioClientTransport({
39121
+ command: launch.command,
39122
+ args: launch.args,
39123
+ env: { ...(0, import_stdio2.getDefaultEnvironment)(), ...launch.env },
39124
+ stderr: "ignore",
39125
+ ...launch.cwd ? { cwd: launch.cwd } : {}
39126
+ });
39127
+ }
39128
+ const url = new URL(launch.url);
39129
+ const requestInit = launch.headers ? { headers: launch.headers } : void 0;
39130
+ if (launch.kind === "sse") {
39131
+ return new import_sse.SSEClientTransport(url, requestInit ? { requestInit } : void 0);
39132
+ }
39133
+ return new import_streamableHttp.StreamableHTTPClientTransport(url, requestInit ? { requestInit } : void 0);
39134
+ }
39135
+
39136
+ // src/mcp-ui/host.ts
39137
+ var TOOL_TTL_MS = 5 * 60 * 1e3;
39138
+ var McpUiHost = class {
39139
+ constructor(opts) {
39140
+ this.opts = opts;
39141
+ }
39142
+ opts;
39143
+ sessions = /* @__PURE__ */ new Map();
39144
+ toolLists = /* @__PURE__ */ new Map();
39145
+ async viewForClaudeTool(toolName) {
39146
+ const entries = this.userEntries();
39147
+ const match = matchClaudeMcpTool(toolName, Object.keys(entries));
39148
+ if (!match) return null;
39149
+ const launch = parseMcpServerLaunch(entries[match.server] ?? {});
39150
+ if (!launch) return null;
39151
+ try {
39152
+ const session = await this.sessionFor(match.server, launch);
39153
+ const tools = await this.toolsFor(match.server, launch, session);
39154
+ const tool = tools.find((item) => item.name === match.tool);
39155
+ if (!tool?.resourceUri) return null;
39156
+ const resource = await session.readResource(tool.resourceUri);
39157
+ if (!resource) return null;
39158
+ const html = htmlFromResourceContent(resource);
39159
+ if (!html) return null;
39160
+ const csp = resource.csp ?? tool.csp;
39161
+ return {
39162
+ server: match.server,
39163
+ tool: match.tool,
39164
+ uri: html.uri,
39165
+ mimeType: html.mimeType,
39166
+ html: html.html,
39167
+ csp: mcpUiContentSecurityPolicy(csp)
39168
+ };
39169
+ } catch (err) {
39170
+ console.error(
39171
+ `[mcp-ui] ${match.server}/${match.tool}:`,
39172
+ err instanceof Error ? err.message : err
39173
+ );
39174
+ return null;
39175
+ }
39176
+ }
39177
+ async callTool(server, tool, args) {
39178
+ const session = await this.sessionForName(server);
39179
+ if (!session) throw new Error(`MCP server ${server} is not available`);
39180
+ return session.callTool(tool, args);
39181
+ }
39182
+ async readResource(server, uri) {
39183
+ const session = await this.sessionForName(server);
39184
+ if (!session) throw new Error(`MCP server ${server} is not available`);
39185
+ const resource = await session.readResource(uri);
39186
+ if (!resource) throw new Error(`Resource ${uri} was empty`);
39187
+ return {
39188
+ contents: [
39189
+ {
39190
+ uri: resource.uri,
39191
+ ...resource.mimeType ? { mimeType: resource.mimeType } : {},
39192
+ ...resource.text != null ? { text: resource.text } : {},
39193
+ ...resource.blob != null ? { blob: resource.blob } : {}
39194
+ }
39195
+ ]
39196
+ };
39197
+ }
39198
+ async closeAll() {
39199
+ const pending = [...this.sessions.values()];
39200
+ this.sessions.clear();
39201
+ this.toolLists.clear();
39202
+ await Promise.all(
39203
+ pending.map((session) => session.then((open) => open.close()).catch(() => void 0))
39204
+ );
39205
+ }
39206
+ userEntries() {
39207
+ const out = {};
39208
+ for (const [name, entry] of Object.entries(this.opts.entries())) {
39209
+ if (!name.trim() || isInjectedOrchMcpName(name) || !entry) continue;
39210
+ out[name] = entry;
39211
+ }
39212
+ return out;
39213
+ }
39214
+ async sessionForName(server) {
39215
+ const entry = this.userEntries()[server];
39216
+ if (!entry) return null;
39217
+ const launch = parseMcpServerLaunch(entry);
39218
+ if (!launch) return null;
39219
+ return this.sessionFor(server, launch);
39220
+ }
39221
+ sessionFor(server, launch) {
39222
+ const key = `${server}
39223
+ ${JSON.stringify(launch)}`;
39224
+ const existing = this.sessions.get(key);
39225
+ if (existing) return existing;
39226
+ const open = this.opts.openSession ?? openMcpUiSession;
39227
+ const pending = open(launch).catch((err) => {
39228
+ this.sessions.delete(key);
39229
+ throw err;
39230
+ });
39231
+ this.sessions.set(key, pending);
39232
+ return pending;
39233
+ }
39234
+ async toolsFor(server, launch, session) {
39235
+ const key = `${server}
39236
+ ${JSON.stringify(launch)}`;
39237
+ const cached2 = this.toolLists.get(key);
39238
+ if (cached2 && cached2.key === key && Date.now() - cached2.at < TOOL_TTL_MS) return cached2.tools;
39239
+ const tools = await session.listTools();
39240
+ this.toolLists.set(key, { at: Date.now(), key, tools });
39241
+ return tools;
39242
+ }
39243
+ };
39244
+ var singleton2;
39245
+ function getMcpUiHost() {
39246
+ if (!singleton2) {
39247
+ singleton2 = new McpUiHost({ entries: () => listUserClaudeMcpServerEntries() });
39248
+ }
39249
+ return singleton2;
39250
+ }
39251
+ function closeMcpUiHost() {
39252
+ const host = singleton2;
39253
+ singleton2 = void 0;
39254
+ if (host) void host.closeAll();
39255
+ }
39256
+
38889
39257
  // src/slack/workspaces.ts
38890
39258
  var import_node_path69 = require("path");
38891
39259
  init_paths();
@@ -40972,132 +41340,21 @@ var import_ws5 = require("ws");
40972
41340
  init_orchestrator_capable();
40973
41341
  init_stage_files();
40974
41342
  init_orchestrator();
40975
- init_app_settings();
40976
- init_implied_first_prompt();
40977
- init_message_parts();
40978
- init_turn_live();
40979
41343
 
40980
- // src/remote/phone-media.ts
40981
- var import_node_fs71 = require("fs");
40982
- var import_node_path71 = require("path");
40983
- init_diff();
40984
- var PHONE_IMAGE_MAX_BYTES = 15e5;
40985
- var PHONE_TEXT_MAX_BYTES = 18e4;
40986
- var PHONE_DIR_MAX = 200;
40987
- var IMAGE_MIME = {
40988
- png: "image/png",
40989
- jpg: "image/jpeg",
40990
- jpeg: "image/jpeg",
40991
- gif: "image/gif",
40992
- webp: "image/webp",
40993
- bmp: "image/bmp"
40994
- };
40995
- function phoneWorktreeRelative(worktreePath, raw) {
40996
- let text7 = raw.trim();
40997
- if (!text7) return null;
40998
- if (/^file:\/\//i.test(text7)) {
40999
- try {
41000
- text7 = decodeURIComponent(new URL(text7).pathname);
41001
- } catch {
41002
- return null;
41003
- }
41004
- }
41005
- text7 = (text7.split("#")[0] ?? text7).replace(/\\/g, "/");
41006
- if (!text7 || text7.includes("\0") || text7.split("/").includes("..")) return null;
41007
- const root = worktreePath.replace(/\/+$/, "");
41008
- if (!root || root === "/") return null;
41009
- if (text7.startsWith("/")) {
41010
- if (text7 !== root && !text7.startsWith(`${root}/`)) return null;
41011
- text7 = text7 === root ? "" : text7.slice(root.length + 1);
41012
- }
41013
- text7 = text7.replace(/^\.\//, "").replace(/\/+$/, "");
41014
- if (!text7 || text7 === "." || text7.startsWith("/")) return null;
41015
- return text7;
41016
- }
41017
- function phoneImageMime(relativePath) {
41018
- const ext = relativePath.split(".").pop()?.toLowerCase() ?? "";
41019
- return IMAGE_MIME[ext] ?? null;
41020
- }
41021
- function phoneImageDataUrl(worktreePath, raw) {
41022
- const relative = phoneWorktreeRelative(worktreePath, raw);
41023
- if (!relative) return null;
41024
- const mime = phoneImageMime(relative);
41025
- if (!mime) return null;
41026
- try {
41027
- const file = readWorktreeFileForUpload(worktreePath, relative, { maxBytes: PHONE_IMAGE_MAX_BYTES });
41028
- return `data:${mime};base64,${file.contentBase64}`;
41029
- } catch {
41030
- return null;
41031
- }
41032
- }
41033
- function phoneDirectoryListing(worktreePath, relative) {
41034
- try {
41035
- const entries = (0, import_node_fs71.readdirSync)((0, import_node_path71.join)(worktreePath, relative), { withFileTypes: true });
41036
- const dirs = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
41037
- const names = entries.map((entry) => entry.name).filter((name) => name && !name.includes("/") && !name.includes("\0")).sort((a, b) => a.localeCompare(b));
41038
- const shown = names.slice(0, PHONE_DIR_MAX);
41039
- const lines = shown.map((name) => dirs.has(name) ? `${name}/` : name);
41040
- const truncated = names.length > shown.length;
41041
- if (truncated) lines.push(`\u2026 ${names.length - shown.length} more`);
41042
- return { content: lines.join("\n"), truncated };
41043
- } catch {
41044
- return null;
41045
- }
41046
- }
41047
- function phoneFilePreview(worktreePath, relative, kind) {
41048
- if (kind === "dir") {
41049
- const listing = phoneDirectoryListing(worktreePath, relative);
41050
- if (!listing) return null;
41051
- return {
41052
- content: listing.content,
41053
- encoding: "utf8",
41054
- binary: false,
41055
- directory: true,
41056
- truncated: listing.truncated
41057
- };
41058
- }
41059
- if (relative.toLowerCase().endsWith(".svg")) {
41060
- try {
41061
- const buf = (0, import_node_fs71.readFileSync)((0, import_node_path71.join)(worktreePath, relative));
41062
- const truncated = buf.length > PHONE_TEXT_MAX_BYTES;
41063
- return {
41064
- content: buf.subarray(0, PHONE_TEXT_MAX_BYTES).toString("utf8"),
41065
- encoding: "utf8",
41066
- binary: false,
41067
- directory: false,
41068
- truncated,
41069
- mime: "image/svg+xml"
41070
- };
41071
- } catch {
41072
- return null;
41073
- }
41074
- }
41075
- const mime = phoneImageMime(relative);
41076
- if (mime) {
41077
- const dataUrl = phoneImageDataUrl(worktreePath, relative);
41078
- if (!dataUrl) return null;
41079
- return {
41080
- content: dataUrl,
41081
- encoding: "base64",
41082
- binary: true,
41083
- directory: false,
41084
- truncated: false,
41085
- mime
41086
- };
41087
- }
41088
- try {
41089
- const file = readWorktreeFile(worktreePath, relative, { maxBytes: PHONE_TEXT_MAX_BYTES });
41090
- return {
41091
- content: file.content,
41092
- encoding: file.encoding,
41093
- binary: file.binary,
41094
- directory: false,
41095
- truncated: file.truncated
41096
- };
41097
- } catch {
41098
- return null;
41099
- }
41100
- }
41344
+ // src/remote/phone-chats.ts
41345
+ var import_node_crypto27 = require("crypto");
41346
+ init_list_models();
41347
+ init_home_board();
41348
+ init_stage_files();
41349
+ init_worktree_labels();
41350
+ init_thinking_effort();
41351
+ init_app_settings();
41352
+ init_global_workspace();
41353
+ init_thread_store();
41354
+ init_workspaces();
41355
+ init_chat_tabs();
41356
+ init_create();
41357
+ init_orchestrator();
41101
41358
 
41102
41359
  // src/remote/phone-artifact.ts
41103
41360
  var FENCE_RE = /```([a-zA-Z0-9_+-]*)[^\n]*\n([\s\S]*?)(?:```|$)/g;
@@ -41107,10 +41364,10 @@ var MIN_MD_CHARS = 120;
41107
41364
  function oneLine2(value, max) {
41108
41365
  return value.replace(/[`\r\n]/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
41109
41366
  }
41110
- function asRecord15(value) {
41367
+ function asRecord16(value) {
41111
41368
  if (typeof value === "string") {
41112
41369
  try {
41113
- return asRecord15(JSON.parse(value));
41370
+ return asRecord16(JSON.parse(value));
41114
41371
  } catch {
41115
41372
  return void 0;
41116
41373
  }
@@ -41185,7 +41442,7 @@ function toolContent(input) {
41185
41442
  if (!input) return "";
41186
41443
  const direct = input.content;
41187
41444
  if (typeof direct === "string") return direct;
41188
- const nested = asRecord15(direct);
41445
+ const nested = asRecord16(direct);
41189
41446
  if (!nested) return "";
41190
41447
  for (const key of ["html", "markup", "text", "source", "content", "code"]) {
41191
41448
  if (typeof nested[key] === "string") return nested[key];
@@ -41208,7 +41465,7 @@ function phoneArtifactText(text7, parts, limit = Number.POSITIVE_INFINITY) {
41208
41465
  const extras = [];
41209
41466
  for (const part of parts ?? []) {
41210
41467
  if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
41211
- const input = asRecord15(part.input);
41468
+ const input = asRecord16(part.input);
41212
41469
  const content = toolContent(input);
41213
41470
  const hint = artifactHint(content);
41214
41471
  if (hint && hints.has(hint)) continue;
@@ -41227,7 +41484,7 @@ function phoneArtifactText(text7, parts, limit = Number.POSITIVE_INFINITY) {
41227
41484
  ${tail}` : tail;
41228
41485
  }
41229
41486
  function parseOpenArtifact(value, chatId) {
41230
- const record = asRecord15(value);
41487
+ const record = asRecord16(value);
41231
41488
  if (!record) return "invalid";
41232
41489
  const title = oneLine2(str7(record.title), 200);
41233
41490
  if (!title) return "invalid";
@@ -41250,7 +41507,7 @@ function phoneArtifactDocuments(sources) {
41250
41507
  }
41251
41508
  for (const part of source.parts ?? []) {
41252
41509
  if (part.type !== "tool" || part.parentId || !isArtifactTool(part.name)) continue;
41253
- const input = asRecord15(part.input);
41510
+ const input = asRecord16(part.input);
41254
41511
  const content = toolContent(input);
41255
41512
  const hint = artifactHint(content);
41256
41513
  if (hint && hints.has(hint)) continue;
@@ -41300,137 +41557,6 @@ function pickArtifactMatch(items, request) {
41300
41557
  return null;
41301
41558
  }
41302
41559
 
41303
- // src/remote/phone-panes.ts
41304
- init_orchestrator();
41305
- var PHONE_CHANGE_SCOPES = ["last_turn", "uncommitted", "staged", "unstaged", "commits"];
41306
- var PHONE_CHANGE_SCOPE_DEFAULT = "commits";
41307
- function phoneChangeScope(value) {
41308
- return typeof value === "string" && PHONE_CHANGE_SCOPES.includes(value) ? value : void 0;
41309
- }
41310
- function phoneCommitSha(value) {
41311
- return typeof value === "string" && /^[0-9a-f]{4,40}$/i.test(value) ? value : void 0;
41312
- }
41313
- var MAX_ROWS = 200;
41314
- var MAX_QUERY = 200;
41315
- function parsePhonePane(value, chatId) {
41316
- const pane = value && typeof value === "object" ? value.pane : void 0;
41317
- if (pane !== "files" && pane !== "changes" && pane !== "checks") return "invalid";
41318
- const scope = phoneChangeScope(value.scope);
41319
- const commitSha = phoneCommitSha(value.commitSha);
41320
- const queryRaw = value.query;
41321
- const query = typeof queryRaw === "string" ? queryRaw.trim().slice(0, MAX_QUERY) : "";
41322
- return {
41323
- op: "pane",
41324
- chatId,
41325
- pane,
41326
- ...scope && pane === "changes" ? { scope } : {},
41327
- ...commitSha && pane === "changes" && scope === "commits" ? { commitSha } : {},
41328
- ...query && pane === "files" ? { query } : {}
41329
- };
41330
- }
41331
- function filterPhoneFiles(paths, query) {
41332
- const q = query.trim().toLowerCase();
41333
- if (!q) return paths;
41334
- return paths.filter((path2) => path2.toLowerCase().includes(q));
41335
- }
41336
- async function readPhonePane(thread, pane, scope, query, commitSha) {
41337
- try {
41338
- if (pane === "files") return await readFiles(thread.id, query);
41339
- if (pane === "changes") return await readChanges(thread.id, scope ?? PHONE_CHANGE_SCOPE_DEFAULT, commitSha);
41340
- return await readChecks(thread.id);
41341
- } catch (err) {
41342
- const message = err instanceof Error ? err.message : String(err);
41343
- return { rows: [], note: message };
41344
- }
41345
- }
41346
- async function readFiles(threadId, query) {
41347
- const paths = filterPhoneFiles(await getOrchestrator().listFiles(threadId), query ?? "");
41348
- const shown = paths.slice(0, MAX_ROWS);
41349
- const extra = paths.length - shown.length;
41350
- const note = extra > 0 ? query?.trim() ? `${extra} more matching files` : `${extra} more files on the Mac` : void 0;
41351
- return {
41352
- rows: shown.map((path2) => ({ label: path2, path: path2 })),
41353
- ...note ? { note } : {}
41354
- };
41355
- }
41356
- async function readChanges(threadId, scope, commitSha) {
41357
- const diff = await getOrchestrator().diff(threadId, {
41358
- includePatches: false,
41359
- scope,
41360
- commitSha: scope === "commits" ? commitSha ?? null : null
41361
- });
41362
- const shown = diff.files.slice(0, MAX_ROWS);
41363
- const selected = diff.commits.find((commit) => commit.sha === commitSha || commit.shortSha === commitSha);
41364
- return {
41365
- rows: shown.map((file) => {
41366
- const detail = changeDetail(file.additions, file.deletions);
41367
- return {
41368
- label: file.path,
41369
- path: file.path,
41370
- ...file.status ? { status: file.status } : {},
41371
- ...detail ? { detail } : {}
41372
- };
41373
- }),
41374
- ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {},
41375
- scope: diff.scope,
41376
- commitSha: selected?.sha ?? null,
41377
- ...diff.base ? { base: diff.base } : {},
41378
- hasLastTurnBase: diff.hasLastTurnBase,
41379
- commits: diff.commits.slice(0, 40),
41380
- scopeStats: diff.scopeStats
41381
- };
41382
- }
41383
- function changeDetail(additions, deletions) {
41384
- const add = additions ?? 0;
41385
- const del = deletions ?? 0;
41386
- if (add === 0 && del === 0) return void 0;
41387
- return `+${add} \u2212${del}`;
41388
- }
41389
- async function readPhoneChangePatch(threadId, path2, scope = PHONE_CHANGE_SCOPE_DEFAULT, commitSha) {
41390
- const diff = await getOrchestrator().diff(threadId, {
41391
- path: path2,
41392
- scope,
41393
- commitSha: scope === "commits" ? commitSha ?? null : null,
41394
- includePatches: true,
41395
- includeMeta: false,
41396
- includeUntracked: scope === "uncommitted" || scope === "unstaged" || scope === "last_turn" || scope === "commits" && !commitSha
41397
- });
41398
- const file = diff.files.find((row2) => row2.path === path2) ?? diff.files[0];
41399
- if (!file) return null;
41400
- const content = file.patch ?? "";
41401
- return { content, truncated: content.includes("\u2026 truncated") };
41402
- }
41403
- async function readChecks(threadId) {
41404
- const checks = await getOrchestrator().getPrChecks(threadId);
41405
- if (!checks) return { rows: [], note: "No pull request on this branch." };
41406
- const shown = checks.slice(0, MAX_ROWS);
41407
- return {
41408
- rows: shown.map((check) => ({
41409
- label: check.name,
41410
- detail: check.bucket || check.state,
41411
- ...check.link ? { href: check.link } : {}
41412
- }))
41413
- };
41414
- }
41415
-
41416
- // src/remote/phone-workspace-ops.ts
41417
- init_orchestrator();
41418
-
41419
- // src/remote/phone-chats.ts
41420
- var import_node_crypto27 = require("crypto");
41421
- init_list_models();
41422
- init_home_board();
41423
- init_stage_files();
41424
- init_worktree_labels();
41425
- init_thinking_effort();
41426
- init_app_settings();
41427
- init_global_workspace();
41428
- init_thread_store();
41429
- init_workspaces();
41430
- init_chat_tabs();
41431
- init_create();
41432
- init_orchestrator();
41433
-
41434
41560
  // src/remote/phone-header.ts
41435
41561
  init_orchestrator();
41436
41562
  init_global_workspace();
@@ -41594,6 +41720,119 @@ function parsePhoneCodeRefs(value) {
41594
41720
  return refs;
41595
41721
  }
41596
41722
 
41723
+ // src/remote/phone-panes.ts
41724
+ init_orchestrator();
41725
+ var PHONE_CHANGE_SCOPES = ["last_turn", "uncommitted", "staged", "unstaged", "commits"];
41726
+ var PHONE_CHANGE_SCOPE_DEFAULT = "commits";
41727
+ function phoneChangeScope(value) {
41728
+ return typeof value === "string" && PHONE_CHANGE_SCOPES.includes(value) ? value : void 0;
41729
+ }
41730
+ function phoneCommitSha(value) {
41731
+ return typeof value === "string" && /^[0-9a-f]{4,40}$/i.test(value) ? value : void 0;
41732
+ }
41733
+ var MAX_ROWS = 200;
41734
+ var MAX_QUERY = 200;
41735
+ function parsePhonePane(value, chatId) {
41736
+ const pane = value && typeof value === "object" ? value.pane : void 0;
41737
+ if (pane !== "files" && pane !== "changes" && pane !== "checks") return "invalid";
41738
+ const scope = phoneChangeScope(value.scope);
41739
+ const commitSha = phoneCommitSha(value.commitSha);
41740
+ const queryRaw = value.query;
41741
+ const query = typeof queryRaw === "string" ? queryRaw.trim().slice(0, MAX_QUERY) : "";
41742
+ return {
41743
+ op: "pane",
41744
+ chatId,
41745
+ pane,
41746
+ ...scope && pane === "changes" ? { scope } : {},
41747
+ ...commitSha && pane === "changes" && scope === "commits" ? { commitSha } : {},
41748
+ ...query && pane === "files" ? { query } : {}
41749
+ };
41750
+ }
41751
+ function filterPhoneFiles(paths, query) {
41752
+ const q = query.trim().toLowerCase();
41753
+ if (!q) return paths;
41754
+ return paths.filter((path2) => path2.toLowerCase().includes(q));
41755
+ }
41756
+ async function readPhonePane(thread, pane, scope, query, commitSha) {
41757
+ try {
41758
+ if (pane === "files") return await readFiles(thread.id, query);
41759
+ if (pane === "changes") return await readChanges(thread.id, scope ?? PHONE_CHANGE_SCOPE_DEFAULT, commitSha);
41760
+ return await readChecks(thread.id);
41761
+ } catch (err) {
41762
+ const message = err instanceof Error ? err.message : String(err);
41763
+ return { rows: [], note: message };
41764
+ }
41765
+ }
41766
+ async function readFiles(threadId, query) {
41767
+ const paths = filterPhoneFiles(await getOrchestrator().listFiles(threadId), query ?? "");
41768
+ const shown = paths.slice(0, MAX_ROWS);
41769
+ const extra = paths.length - shown.length;
41770
+ const note = extra > 0 ? query?.trim() ? `${extra} more matching files` : `${extra} more files on the Mac` : void 0;
41771
+ return {
41772
+ rows: shown.map((path2) => ({ label: path2, path: path2 })),
41773
+ ...note ? { note } : {}
41774
+ };
41775
+ }
41776
+ async function readChanges(threadId, scope, commitSha) {
41777
+ const diff = await getOrchestrator().diff(threadId, {
41778
+ includePatches: false,
41779
+ scope,
41780
+ commitSha: scope === "commits" ? commitSha ?? null : null
41781
+ });
41782
+ const shown = diff.files.slice(0, MAX_ROWS);
41783
+ const selected = diff.commits.find((commit) => commit.sha === commitSha || commit.shortSha === commitSha);
41784
+ return {
41785
+ rows: shown.map((file) => {
41786
+ const detail = changeDetail(file.additions, file.deletions);
41787
+ return {
41788
+ label: file.path,
41789
+ path: file.path,
41790
+ ...file.status ? { status: file.status } : {},
41791
+ ...detail ? { detail } : {}
41792
+ };
41793
+ }),
41794
+ ...diff.files.length > shown.length ? { note: `${diff.files.length - shown.length} more changed files` } : {},
41795
+ scope: diff.scope,
41796
+ commitSha: selected?.sha ?? null,
41797
+ ...diff.base ? { base: diff.base } : {},
41798
+ hasLastTurnBase: diff.hasLastTurnBase,
41799
+ commits: diff.commits.slice(0, 40),
41800
+ scopeStats: diff.scopeStats
41801
+ };
41802
+ }
41803
+ function changeDetail(additions, deletions) {
41804
+ const add = additions ?? 0;
41805
+ const del = deletions ?? 0;
41806
+ if (add === 0 && del === 0) return void 0;
41807
+ return `+${add} \u2212${del}`;
41808
+ }
41809
+ async function readPhoneChangePatch(threadId, path2, scope = PHONE_CHANGE_SCOPE_DEFAULT, commitSha) {
41810
+ const diff = await getOrchestrator().diff(threadId, {
41811
+ path: path2,
41812
+ scope,
41813
+ commitSha: scope === "commits" ? commitSha ?? null : null,
41814
+ includePatches: true,
41815
+ includeMeta: false,
41816
+ includeUntracked: scope === "uncommitted" || scope === "unstaged" || scope === "last_turn" || scope === "commits" && !commitSha
41817
+ });
41818
+ const file = diff.files.find((row2) => row2.path === path2) ?? diff.files[0];
41819
+ if (!file) return null;
41820
+ const content = file.patch ?? "";
41821
+ return { content, truncated: content.includes("\u2026 truncated") };
41822
+ }
41823
+ async function readChecks(threadId) {
41824
+ const checks = await getOrchestrator().getPrChecks(threadId);
41825
+ if (!checks) return { rows: [], note: "No pull request on this branch." };
41826
+ const shown = checks.slice(0, MAX_ROWS);
41827
+ return {
41828
+ rows: shown.map((check) => ({
41829
+ label: check.name,
41830
+ detail: check.bucket || check.state,
41831
+ ...check.link ? { href: check.link } : {}
41832
+ }))
41833
+ };
41834
+ }
41835
+
41597
41836
  // src/remote/phone-defaults.ts
41598
41837
  init_app_settings();
41599
41838
  function row(resolved) {
@@ -41638,6 +41877,63 @@ function refreshPhoneViewerLogin(force = false) {
41638
41877
  init_wait_for_job();
41639
41878
  init_turn_live();
41640
41879
 
41880
+ // src/remote/phone-mcp-ui.ts
41881
+ var MAX_APPS = 4;
41882
+ function phoneMcpApps(agent, parts) {
41883
+ if (agent !== "claude" || !parts?.length) return [];
41884
+ const out = [];
41885
+ for (const part of parts) {
41886
+ if (part.type !== "tool" || part.parentId || part.status === "running") continue;
41887
+ if (!part.name.startsWith("mcp__") || part.name.startsWith("mcp__sideboard")) continue;
41888
+ out.push({ id: part.id, tool: part.name });
41889
+ if (out.length >= MAX_APPS) break;
41890
+ }
41891
+ return out;
41892
+ }
41893
+ function controlText(value, key) {
41894
+ const raw = value[key];
41895
+ return typeof raw === "string" ? raw.trim() : "";
41896
+ }
41897
+ function parsePhoneMcpUi(value, op, chatId) {
41898
+ if (op === "mcp-ui") {
41899
+ const tool2 = controlText(value, "tool");
41900
+ const callId = controlText(value, "callId");
41901
+ const reqId2 = controlText(value, "reqId");
41902
+ if (!tool2.startsWith("mcp__") || tool2.length > 200 || !callId || !reqId2 || reqId2.length > 40) {
41903
+ return "invalid";
41904
+ }
41905
+ return { op: "mcp-ui", chatId, tool: tool2, callId, reqId: reqId2 };
41906
+ }
41907
+ if (op !== "mcp-ui-call" && op !== "mcp-ui-read") return "invalid";
41908
+ const reqId = controlText(value, "reqId");
41909
+ const server = controlText(value, "server");
41910
+ if (!reqId || reqId.length > 40 || !server || server.length > 120) return "invalid";
41911
+ if (op === "mcp-ui-read") {
41912
+ const uri = controlText(value, "uri");
41913
+ if (!uri.startsWith("ui://") || uri.length > 500) return "invalid";
41914
+ return { op, chatId, reqId, server, uri };
41915
+ }
41916
+ const tool = controlText(value, "tool");
41917
+ const args = value.args;
41918
+ if (!tool || tool.length > 200 || !args || typeof args !== "object" || Array.isArray(args)) {
41919
+ return "invalid";
41920
+ }
41921
+ return { op, chatId, reqId, server, tool, args };
41922
+ }
41923
+ function phoneMcpToolPayload(parts, tool, callId) {
41924
+ if (!parts) return null;
41925
+ const matches = parts.filter(
41926
+ (part2) => part2.type === "tool" && part2.name === tool && !part2.parentId
41927
+ );
41928
+ const part = matches.find((row2) => row2.id === callId) ?? matches[matches.length - 1];
41929
+ if (!part) return null;
41930
+ return {
41931
+ input: part.input ?? {},
41932
+ resultText: part.result ?? "",
41933
+ isError: part.status === "error"
41934
+ };
41935
+ }
41936
+
41641
41937
  // src/remote/phone-stream.ts
41642
41938
  init_message_parts();
41643
41939
  var PHONE_STREAM_FLUSH_MS = 80;
@@ -41697,7 +41993,8 @@ function listPhoneMessages(thread) {
41697
41993
  const visible = [phoneVisibleText(message.role, message.text), names.length ? names.join(", ") : ""].filter(Boolean).join("\n");
41698
41994
  const text7 = message.role === "agent" ? phoneArtifactText(visible, message.parts, MESSAGE_LIMIT) : clip(visible, MESSAGE_LIMIT);
41699
41995
  if (!text7) continue;
41700
- out.push({ role: message.role, text: text7 });
41996
+ const apps = message.role === "agent" ? phoneMcpApps(thread.agent, message.parts) : [];
41997
+ out.push({ role: message.role, text: text7, ...apps.length ? { apps } : {} });
41701
41998
  }
41702
41999
  const live = withLiveTail(thread, out);
41703
42000
  for (const prompt of thread.queue) {
@@ -41827,6 +42124,7 @@ function parsePhoneControl(value) {
41827
42124
  }
41828
42125
  if (op === "pane") return parsePhonePane(value, chatId);
41829
42126
  if (op === "open-artifact") return parseOpenArtifact(value, chatId);
42127
+ if (op === "mcp-ui" || op === "mcp-ui-call" || op === "mcp-ui-read") return parsePhoneMcpUi(value, op, chatId);
41830
42128
  if (op === "open-file" || op === "media") {
41831
42129
  const path2 = textField2(value, "path");
41832
42130
  if (!path2 || path2.length > 2e3) return "invalid";
@@ -42242,109 +42540,6 @@ function archivePhoneChat(chatId) {
42242
42540
  return true;
42243
42541
  }
42244
42542
 
42245
- // src/remote/phone-workspace-ops.ts
42246
- function dispatchPhoneWorkspace(cmd, emit, publishOpened) {
42247
- if (cmd.op === "pane") {
42248
- const thread = readPhoneThread(cmd.chatId);
42249
- if (!thread) {
42250
- emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
42251
- return;
42252
- }
42253
- void readPhonePane(thread, cmd.pane, cmd.scope, cmd.query, cmd.commitSha).then((body) => {
42254
- emit({
42255
- op: "pane",
42256
- chatId: cmd.chatId,
42257
- pane: cmd.pane,
42258
- rows: body.rows,
42259
- ...body.note ? { note: body.note } : {},
42260
- ...cmd.pane === "files" ? { query: cmd.query ?? "" } : {},
42261
- ...cmd.pane === "changes" ? {
42262
- scope: body.scope,
42263
- commitSha: body.commitSha ?? null,
42264
- ...body.base ? { base: body.base } : {},
42265
- hasLastTurnBase: body.hasLastTurnBase,
42266
- commits: body.commits ?? [],
42267
- ...body.scopeStats ? { scopeStats: body.scopeStats } : {}
42268
- } : {}
42269
- });
42270
- });
42271
- return;
42272
- }
42273
- if (cmd.op === "header") {
42274
- const thread = readPhoneThread(cmd.chatId);
42275
- if (!thread) {
42276
- emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
42277
- return;
42278
- }
42279
- void readPhoneHeader(thread).then((header) => {
42280
- emit({ op: "header", chatId: cmd.chatId, ...header });
42281
- });
42282
- return;
42283
- }
42284
- if (cmd.op === "git") {
42285
- void getOrchestrator().askGit(cmd.chatId, cmd.action).then((tab) => publishOpened(tab.id)).catch((err) => {
42286
- emit({
42287
- op: "error",
42288
- chatId: cmd.chatId,
42289
- message: err instanceof Error ? err.message : String(err)
42290
- });
42291
- });
42292
- return;
42293
- }
42294
- void getOrchestrator().requestReview(cmd.chatId).then((tab) => publishOpened(tab.id)).catch((err) => {
42295
- emit({
42296
- op: "error",
42297
- chatId: cmd.chatId,
42298
- message: err instanceof Error ? err.message : String(err)
42299
- });
42300
- });
42301
- }
42302
-
42303
- // src/remote/phone-sources.ts
42304
- init_worktree();
42305
- function sourceWarning(label, err) {
42306
- const message = err instanceof Error ? err.message : String(err);
42307
- return message ? `${label}: ${message}` : `${label} failed.`;
42308
- }
42309
- async function listPhoneSources(repoPath, query) {
42310
- const path2 = assertKnownProject(repoPath);
42311
- const q = query?.trim() ?? "";
42312
- const warnings = [];
42313
- const [prs, branches, issues] = await Promise.all([
42314
- listPrs(path2, { state: "open", limit: 40, ...q ? { query: q } : {} }).catch((err) => {
42315
- warnings.push(sourceWarning("Pull requests", err));
42316
- return [];
42317
- }),
42318
- listBranches(path2).catch((err) => {
42319
- warnings.push(sourceWarning("Branches", err));
42320
- return [];
42321
- }),
42322
- listIssues(path2, { limit: 40, ...q ? { query: q } : {} }).catch((err) => {
42323
- warnings.push(sourceWarning("Issues", err));
42324
- return { issues: [] };
42325
- })
42326
- ]);
42327
- const needle = q.toLowerCase();
42328
- const branchRows = branches.filter((branch) => !needle || branch.name.toLowerCase().includes(needle)).slice(0, 50).map((branch) => ({
42329
- ref: branch.name,
42330
- title: branch.current ? `${branch.name} (current)` : branch.name
42331
- }));
42332
- return {
42333
- prs: prs.slice(0, 40).map((pr) => ({
42334
- ref: String(pr.number),
42335
- title: clip(pr.title || `PR #${pr.number}`, 140),
42336
- ...pr.url ? { url: pr.url } : {}
42337
- })),
42338
- branches: branchRows,
42339
- issues: (issues.issues ?? []).slice(0, 40).map((issue) => ({
42340
- ref: issue.identifier,
42341
- title: clip(issue.title || issue.identifier, 140),
42342
- ...issue.url ? { url: issue.url } : {}
42343
- })),
42344
- ...warnings.length ? { warnings } : {}
42345
- };
42346
- }
42347
-
42348
42543
  // src/remote/phone-live.ts
42349
42544
  init_orchestrator();
42350
42545
  init_ask_user();
@@ -42475,10 +42670,12 @@ function emitChatTurn(opts, chatId, threadId) {
42475
42670
  failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
42476
42671
  });
42477
42672
  const agent = [...thread.messages].reverse().find((m) => m.role === "agent");
42673
+ const apps = phoneMcpApps(thread.agent, agent?.parts);
42478
42674
  emitControl(opts, {
42479
42675
  op: "assistant",
42480
42676
  chatId,
42481
- text: phoneArtifactText(reply.trim(), agent?.parts)
42677
+ text: phoneArtifactText(reply.trim(), agent?.parts),
42678
+ ...apps.length ? { apps } : {}
42482
42679
  });
42483
42680
  }
42484
42681
  function phoneError(err) {
@@ -42511,6 +42708,288 @@ function deliverPhoneTurn(opts, chatId) {
42511
42708
  });
42512
42709
  }
42513
42710
 
42711
+ // src/remote/phone-mcp-ui-listen.ts
42712
+ var PHONE_HTML_CAP = 7e5;
42713
+ function dispatchPhoneMcpUi(cmd, opts) {
42714
+ if (cmd.op !== "mcp-ui" && cmd.op !== "mcp-ui-call" && cmd.op !== "mcp-ui-read") return false;
42715
+ const thread = readPhoneThread(cmd.chatId);
42716
+ const reqId = cmd.reqId;
42717
+ const chatId = cmd.chatId;
42718
+ if (!thread || thread.agent !== "claude") {
42719
+ if (cmd.op === "mcp-ui") emitControl(opts, { op: "mcp-ui", chatId, reqId, view: null });
42720
+ else emitControl(opts, { op: cmd.op, chatId, reqId, error: "This chat is not using Claude." });
42721
+ return true;
42722
+ }
42723
+ if (cmd.op === "mcp-ui-call") {
42724
+ void getMcpUiHost().callTool(cmd.server, cmd.tool, cmd.args).then((result) => emitControl(opts, { op: "mcp-ui-call", chatId, reqId, result })).catch(
42725
+ (err) => emitControl(opts, { op: "mcp-ui-call", chatId, reqId, error: phoneError(err) })
42726
+ );
42727
+ return true;
42728
+ }
42729
+ if (cmd.op === "mcp-ui-read") {
42730
+ void getMcpUiHost().readResource(cmd.server, cmd.uri).then((result) => emitControl(opts, { op: "mcp-ui-read", chatId, reqId, result })).catch(
42731
+ (err) => emitControl(opts, { op: "mcp-ui-read", chatId, reqId, error: phoneError(err) })
42732
+ );
42733
+ return true;
42734
+ }
42735
+ const agentMessage = [...thread.messages].reverse().find((message) => message.role === "agent");
42736
+ const payload = phoneMcpToolPayload(agentMessage?.parts, cmd.tool, cmd.callId);
42737
+ void getMcpUiHost().viewForClaudeTool(cmd.tool).then((view) => {
42738
+ if (!view || view.html.length > PHONE_HTML_CAP) {
42739
+ emitControl(opts, { op: "mcp-ui", chatId, reqId, view: null });
42740
+ return;
42741
+ }
42742
+ emitControl(opts, {
42743
+ op: "mcp-ui",
42744
+ chatId,
42745
+ reqId,
42746
+ view: {
42747
+ server: view.server,
42748
+ tool: view.tool,
42749
+ html: view.html,
42750
+ csp: view.csp,
42751
+ uri: view.uri,
42752
+ input: payload?.input ?? {},
42753
+ resultText: payload?.resultText ?? "",
42754
+ isError: payload?.isError ?? false
42755
+ }
42756
+ });
42757
+ }).catch((err) => emitControl(opts, { op: "error", chatId, message: phoneError(err) }));
42758
+ return true;
42759
+ }
42760
+
42761
+ // src/remote/listen.ts
42762
+ init_app_settings();
42763
+ init_implied_first_prompt();
42764
+ init_message_parts();
42765
+ init_turn_live();
42766
+
42767
+ // src/remote/phone-media.ts
42768
+ var import_node_fs71 = require("fs");
42769
+ var import_node_path71 = require("path");
42770
+ init_diff();
42771
+ var PHONE_IMAGE_MAX_BYTES = 15e5;
42772
+ var PHONE_TEXT_MAX_BYTES = 18e4;
42773
+ var PHONE_DIR_MAX = 200;
42774
+ var IMAGE_MIME = {
42775
+ png: "image/png",
42776
+ jpg: "image/jpeg",
42777
+ jpeg: "image/jpeg",
42778
+ gif: "image/gif",
42779
+ webp: "image/webp",
42780
+ bmp: "image/bmp"
42781
+ };
42782
+ function phoneWorktreeRelative(worktreePath, raw) {
42783
+ let text7 = raw.trim();
42784
+ if (!text7) return null;
42785
+ if (/^file:\/\//i.test(text7)) {
42786
+ try {
42787
+ text7 = decodeURIComponent(new URL(text7).pathname);
42788
+ } catch {
42789
+ return null;
42790
+ }
42791
+ }
42792
+ text7 = (text7.split("#")[0] ?? text7).replace(/\\/g, "/");
42793
+ if (!text7 || text7.includes("\0") || text7.split("/").includes("..")) return null;
42794
+ const root = worktreePath.replace(/\/+$/, "");
42795
+ if (!root || root === "/") return null;
42796
+ if (text7.startsWith("/")) {
42797
+ if (text7 !== root && !text7.startsWith(`${root}/`)) return null;
42798
+ text7 = text7 === root ? "" : text7.slice(root.length + 1);
42799
+ }
42800
+ text7 = text7.replace(/^\.\//, "").replace(/\/+$/, "");
42801
+ if (!text7 || text7 === "." || text7.startsWith("/")) return null;
42802
+ return text7;
42803
+ }
42804
+ function phoneImageMime(relativePath) {
42805
+ const ext = relativePath.split(".").pop()?.toLowerCase() ?? "";
42806
+ return IMAGE_MIME[ext] ?? null;
42807
+ }
42808
+ function phoneImageDataUrl(worktreePath, raw) {
42809
+ const relative = phoneWorktreeRelative(worktreePath, raw);
42810
+ if (!relative) return null;
42811
+ const mime = phoneImageMime(relative);
42812
+ if (!mime) return null;
42813
+ try {
42814
+ const file = readWorktreeFileForUpload(worktreePath, relative, { maxBytes: PHONE_IMAGE_MAX_BYTES });
42815
+ return `data:${mime};base64,${file.contentBase64}`;
42816
+ } catch {
42817
+ return null;
42818
+ }
42819
+ }
42820
+ function phoneDirectoryListing(worktreePath, relative) {
42821
+ try {
42822
+ const entries = (0, import_node_fs71.readdirSync)((0, import_node_path71.join)(worktreePath, relative), { withFileTypes: true });
42823
+ const dirs = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
42824
+ const names = entries.map((entry) => entry.name).filter((name) => name && !name.includes("/") && !name.includes("\0")).sort((a, b) => a.localeCompare(b));
42825
+ const shown = names.slice(0, PHONE_DIR_MAX);
42826
+ const lines = shown.map((name) => dirs.has(name) ? `${name}/` : name);
42827
+ const truncated = names.length > shown.length;
42828
+ if (truncated) lines.push(`\u2026 ${names.length - shown.length} more`);
42829
+ return { content: lines.join("\n"), truncated };
42830
+ } catch {
42831
+ return null;
42832
+ }
42833
+ }
42834
+ function phoneFilePreview(worktreePath, relative, kind) {
42835
+ if (kind === "dir") {
42836
+ const listing = phoneDirectoryListing(worktreePath, relative);
42837
+ if (!listing) return null;
42838
+ return {
42839
+ content: listing.content,
42840
+ encoding: "utf8",
42841
+ binary: false,
42842
+ directory: true,
42843
+ truncated: listing.truncated
42844
+ };
42845
+ }
42846
+ if (relative.toLowerCase().endsWith(".svg")) {
42847
+ try {
42848
+ const buf = (0, import_node_fs71.readFileSync)((0, import_node_path71.join)(worktreePath, relative));
42849
+ const truncated = buf.length > PHONE_TEXT_MAX_BYTES;
42850
+ return {
42851
+ content: buf.subarray(0, PHONE_TEXT_MAX_BYTES).toString("utf8"),
42852
+ encoding: "utf8",
42853
+ binary: false,
42854
+ directory: false,
42855
+ truncated,
42856
+ mime: "image/svg+xml"
42857
+ };
42858
+ } catch {
42859
+ return null;
42860
+ }
42861
+ }
42862
+ const mime = phoneImageMime(relative);
42863
+ if (mime) {
42864
+ const dataUrl = phoneImageDataUrl(worktreePath, relative);
42865
+ if (!dataUrl) return null;
42866
+ return {
42867
+ content: dataUrl,
42868
+ encoding: "base64",
42869
+ binary: true,
42870
+ directory: false,
42871
+ truncated: false,
42872
+ mime
42873
+ };
42874
+ }
42875
+ try {
42876
+ const file = readWorktreeFile(worktreePath, relative, { maxBytes: PHONE_TEXT_MAX_BYTES });
42877
+ return {
42878
+ content: file.content,
42879
+ encoding: file.encoding,
42880
+ binary: file.binary,
42881
+ directory: false,
42882
+ truncated: file.truncated
42883
+ };
42884
+ } catch {
42885
+ return null;
42886
+ }
42887
+ }
42888
+
42889
+ // src/remote/phone-workspace-ops.ts
42890
+ init_orchestrator();
42891
+ function dispatchPhoneWorkspace(cmd, emit, publishOpened) {
42892
+ if (cmd.op === "pane") {
42893
+ const thread = readPhoneThread(cmd.chatId);
42894
+ if (!thread) {
42895
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
42896
+ return;
42897
+ }
42898
+ void readPhonePane(thread, cmd.pane, cmd.scope, cmd.query, cmd.commitSha).then((body) => {
42899
+ emit({
42900
+ op: "pane",
42901
+ chatId: cmd.chatId,
42902
+ pane: cmd.pane,
42903
+ rows: body.rows,
42904
+ ...body.note ? { note: body.note } : {},
42905
+ ...cmd.pane === "files" ? { query: cmd.query ?? "" } : {},
42906
+ ...cmd.pane === "changes" ? {
42907
+ scope: body.scope,
42908
+ commitSha: body.commitSha ?? null,
42909
+ ...body.base ? { base: body.base } : {},
42910
+ hasLastTurnBase: body.hasLastTurnBase,
42911
+ commits: body.commits ?? [],
42912
+ ...body.scopeStats ? { scopeStats: body.scopeStats } : {}
42913
+ } : {}
42914
+ });
42915
+ });
42916
+ return;
42917
+ }
42918
+ if (cmd.op === "header") {
42919
+ const thread = readPhoneThread(cmd.chatId);
42920
+ if (!thread) {
42921
+ emit({ op: "error", chatId: cmd.chatId, message: "That agent is not on this Mac." });
42922
+ return;
42923
+ }
42924
+ void readPhoneHeader(thread).then((header) => {
42925
+ emit({ op: "header", chatId: cmd.chatId, ...header });
42926
+ });
42927
+ return;
42928
+ }
42929
+ if (cmd.op === "git") {
42930
+ void getOrchestrator().askGit(cmd.chatId, cmd.action).then((tab) => publishOpened(tab.id)).catch((err) => {
42931
+ emit({
42932
+ op: "error",
42933
+ chatId: cmd.chatId,
42934
+ message: err instanceof Error ? err.message : String(err)
42935
+ });
42936
+ });
42937
+ return;
42938
+ }
42939
+ void getOrchestrator().requestReview(cmd.chatId).then((tab) => publishOpened(tab.id)).catch((err) => {
42940
+ emit({
42941
+ op: "error",
42942
+ chatId: cmd.chatId,
42943
+ message: err instanceof Error ? err.message : String(err)
42944
+ });
42945
+ });
42946
+ }
42947
+
42948
+ // src/remote/phone-sources.ts
42949
+ init_worktree();
42950
+ function sourceWarning(label, err) {
42951
+ const message = err instanceof Error ? err.message : String(err);
42952
+ return message ? `${label}: ${message}` : `${label} failed.`;
42953
+ }
42954
+ async function listPhoneSources(repoPath, query) {
42955
+ const path2 = assertKnownProject(repoPath);
42956
+ const q = query?.trim() ?? "";
42957
+ const warnings = [];
42958
+ const [prs, branches, issues] = await Promise.all([
42959
+ listPrs(path2, { state: "open", limit: 40, ...q ? { query: q } : {} }).catch((err) => {
42960
+ warnings.push(sourceWarning("Pull requests", err));
42961
+ return [];
42962
+ }),
42963
+ listBranches(path2).catch((err) => {
42964
+ warnings.push(sourceWarning("Branches", err));
42965
+ return [];
42966
+ }),
42967
+ listIssues(path2, { limit: 40, ...q ? { query: q } : {} }).catch((err) => {
42968
+ warnings.push(sourceWarning("Issues", err));
42969
+ return { issues: [] };
42970
+ })
42971
+ ]);
42972
+ const needle = q.toLowerCase();
42973
+ const branchRows = branches.filter((branch) => !needle || branch.name.toLowerCase().includes(needle)).slice(0, 50).map((branch) => ({
42974
+ ref: branch.name,
42975
+ title: branch.current ? `${branch.name} (current)` : branch.name
42976
+ }));
42977
+ return {
42978
+ prs: prs.slice(0, 40).map((pr) => ({
42979
+ ref: String(pr.number),
42980
+ title: clip(pr.title || `PR #${pr.number}`, 140),
42981
+ ...pr.url ? { url: pr.url } : {}
42982
+ })),
42983
+ branches: branchRows,
42984
+ issues: (issues.issues ?? []).slice(0, 40).map((issue) => ({
42985
+ ref: issue.identifier,
42986
+ title: clip(issue.title || issue.identifier, 140),
42987
+ ...issue.url ? { url: issue.url } : {}
42988
+ })),
42989
+ ...warnings.length ? { warnings } : {}
42990
+ };
42991
+ }
42992
+
42514
42993
  // src/remote/phone-turn.ts
42515
42994
  init_orchestrator_capable();
42516
42995
  init_orchestrator();
@@ -42865,6 +43344,7 @@ function handlePhoneControl(cmd, opts) {
42865
43344
  });
42866
43345
  return;
42867
43346
  }
43347
+ if (dispatchPhoneMcpUi(cmd, opts)) return;
42868
43348
  if (cmd.op === "open-file") {
42869
43349
  const thread = readPhoneThread(cmd.chatId);
42870
43350
  const relative = thread ? phoneWorktreeRelative(thread.worktreePath, cmd.path) : null;
@@ -45538,6 +46018,7 @@ async function pollSlackOutboundWatches(opts) {
45538
46018
  clipAgentEventForPaint,
45539
46019
  clipThinkingForStore,
45540
46020
  cloneRepoIntoSideboard,
46021
+ closeMcpUiHost,
45541
46022
  codeRefRangeLabel,
45542
46023
  codexAdapter,
45543
46024
  codexSandboxWritableRootsArgs,
@@ -45760,6 +46241,7 @@ async function pollSlackOutboundWatches(opts) {
45760
46241
  getLinearApiKey,
45761
46242
  getLinearAuthToken,
45762
46243
  getLinearIssue,
46244
+ getMcpUiHost,
45763
46245
  getOrchestrator,
45764
46246
  getPr,
45765
46247
  getPrChecks,
@@ -45825,6 +46307,7 @@ async function pollSlackOutboundWatches(opts) {
45825
46307
  isBrightsyConnected,
45826
46308
  isBrightsyNdjsonLine,
45827
46309
  isChatTextScale,
46310
+ isClaudeAiConnectorMcp,
45828
46311
  isCloudCoordinatorThread,
45829
46312
  isCompactToolName,
45830
46313
  isConductorBundledCli,
@@ -45979,6 +46462,7 @@ async function pollSlackOutboundWatches(opts) {
45979
46462
  mcpServerIdForPermission,
45980
46463
  mcpServerPermissionUpdate,
45981
46464
  mcpStatusThinkingFromClaudeInit,
46465
+ mcpUiContentSecurityPolicy,
45982
46466
  mentionsPrGoal,
45983
46467
  mergeAgentGitAuthEnv,
45984
46468
  mergePr,