@symerian/symi 3.5.16 → 3.5.17

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 (46) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/bundled/boot-md/handler.js +4 -4
  3. package/dist/bundled/session-memory/handler.js +4 -4
  4. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  5. package/dist/{chrome-OTJg3QKn.js → chrome-CmQwGAuL.js} +7 -7
  6. package/dist/{command-registry-2hfSOaGL.js → command-registry-Bmzf59Qv.js} +4 -4
  7. package/dist/{completion-cli-TYU7Y_XZ.js → completion-cli-BM4kZs0m.js} +2 -2
  8. package/dist/{completion-cli-BEO2AB41.js → completion-cli-CtUZzw-P.js} +1 -1
  9. package/dist/control-ui/css/style.css +166 -0
  10. package/dist/control-ui/js/symframe.js +246 -20
  11. package/dist/{deliver-BiWlR84Y.js → deliver-B04yNX82.js} +4 -4
  12. package/dist/{doctor-completion-CvaSyxyp.js → doctor-completion-Df7Hv_IA.js} +1 -1
  13. package/dist/{doctor-completion-DgNgQFQQ.js → doctor-completion-Do20Q4ZM.js} +1 -1
  14. package/dist/entry.js +1 -1
  15. package/dist/{gateway-cli-DCTPCoQx.js → gateway-cli-CKlpsjYJ.js} +1 -1
  16. package/dist/{gateway-cli-BNhURE6O.js → gateway-cli-CwuURJSh.js} +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/llm-slug-generator.js +4 -4
  19. package/dist/{manager-DjhE8yLr.js → manager-B-wPs7cb.js} +1 -1
  20. package/dist/{onboard-DRn6pFfO.js → onboard-3FdfkbGm.js} +1 -1
  21. package/dist/{onboard-fBxK2Bky.js → onboard-DFbgAa-t.js} +1 -1
  22. package/dist/{onboarding-CLOZkMhc.js → onboarding-BigIUHoh.js} +1 -1
  23. package/dist/{onboarding-i1rA1XGe.js → onboarding-fq_-89Jc.js} +1 -1
  24. package/dist/{onboarding.finalize-BV76YUaD.js → onboarding.finalize-BGDBP9G7.js} +4 -4
  25. package/dist/{onboarding.finalize-gk9ot6lb.js → onboarding.finalize-C2Dt95Rn.js} +3 -3
  26. package/dist/plugin-sdk/index.js +6 -6
  27. package/dist/{program-Qug3oi2C.js → program-FBDhJLrg.js} +2 -2
  28. package/dist/{program-context-CCIGjsQL.js → program-context-CZYb4zB2.js} +7 -7
  29. package/dist/{prompt-select-styled-ARnCLf3Q.js → prompt-select-styled-C3LUG3Be.js} +1 -1
  30. package/dist/{prompt-select-styled-BdNTADj2.js → prompt-select-styled-DQyZ6VHH.js} +1 -1
  31. package/dist/{pw-ai-DY_6l11g.js → pw-ai-DLsdzdgc.js} +1 -1
  32. package/dist/{register.maintenance-CTF70v5h.js → register.maintenance-81d4KKmA.js} +5 -5
  33. package/dist/{register.maintenance-BncnnneX.js → register.maintenance-FWHasNw7.js} +4 -4
  34. package/dist/{register.onboard-O0P6rCuX.js → register.onboard-BjPPudfS.js} +2 -2
  35. package/dist/{register.onboard-DhcpKFgU.js → register.onboard-COyX1xSR.js} +2 -2
  36. package/dist/{register.setup-CZfWa9-Z.js → register.setup-C_ds_5fl.js} +2 -2
  37. package/dist/{register.setup-D3XQyELS.js → register.setup-DDMtRxcF.js} +2 -2
  38. package/dist/{register.subclis-Dv8bf2ox.js → register.subclis-B16w8wxz.js} +4 -4
  39. package/dist/{run-main-BsVhPYM4.js → run-main-CUMiBvgm.js} +3 -3
  40. package/dist/{symframe-cli-CsA_x7U9.js → symframe-cli-DLbhzXNU.js} +16 -2
  41. package/dist/{symframe-cli-BKrtoG8H.js → symframe-cli-OqTUw6Mt.js} +16 -2
  42. package/dist/{synthesis-Dv6bJWYX.js → synthesis-CG0rRaLA.js} +4 -4
  43. package/dist/{unified-runner-ByJSAveb.js → unified-runner-ChkYfsxy.js} +10 -10
  44. package/dist/{update-cli-y4Q__h4E.js → update-cli-BhI88CVk.js} +5 -5
  45. package/dist/{update-cli-CrP5Kvo4.js → update-cli-jxM3sFLP.js} +4 -4
  46. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.5.16",
3
- "commit": "820f004a3dfdb5436de913edaa9cfcdfa81baa24",
4
- "builtAt": "2026-05-06T17:31:50.406Z"
2
+ "version": "3.5.17",
3
+ "commit": "e6354a3c26a29ca562ee7ec77906021c84235a0e",
4
+ "builtAt": "2026-05-06T17:41:46.852Z"
5
5
  }
@@ -4,16 +4,16 @@ import { E as shouldLogVerbose, G as isRecord, r as DEFAULT_CHAT_CHANNEL, v as i
4
4
  import { a as resolveAgentSkillsFilter, c as resolveEffectiveModelFallbacks, i as resolveAgentModelPrimary, l as resolveSessionAgentId, o as resolveAgentWorkspaceDir, r as resolveAgentDir, t as listAgentIds, u as resolveSessionAgentIds } from "../../agent-scope-B1Ina6jV.js";
5
5
  import { r as defaultRuntime, t as createSubsystemLogger } from "../../subsystem-DzRUKS9f.js";
6
6
  import { l as ensureAgentWorkspace } from "../../workspace-CsaDUuDn.js";
7
- import { A as runWithModelFallback, C as resolveSymiDocsPath, D as resolveRunWorkspaceDir, E as redactRunIdentifier, M as resolveFailoverStatus, O as deriveSessionTotalTokens, S as buildAgentSystemPrompt, T as resolveBootstrapContextForRun, _ as AGENT_LANE_NESTED, a as resolveSessionDeliveryTarget, b as emitAgentEvent, c as detectRuntimeShell, d as applyModelOverrideToSessionEntry, f as applyVerboseOverride, g as resolveAgentTimeoutMs, i as resolveOutboundTarget, j as FailoverError, k as hasNonzeroUsage, l as resolveSendPolicy, m as clearSessionAuthProfileOverride, n as buildModelAliasLines, o as buildSystemPromptParams, p as loadModelCatalog, s as getProcessSupervisor, t as runAgentTurn, u as lookupContextTokens, v as AGENT_LANE_SUBAGENT, w as makeBootstrapWarn, x as registerAgentRunContext, y as clearAgentRunContext } from "../../unified-runner-ByJSAveb.js";
7
+ import { A as runWithModelFallback, C as resolveSymiDocsPath, D as resolveRunWorkspaceDir, E as redactRunIdentifier, M as resolveFailoverStatus, O as deriveSessionTotalTokens, S as buildAgentSystemPrompt, T as resolveBootstrapContextForRun, _ as AGENT_LANE_NESTED, a as resolveSessionDeliveryTarget, b as emitAgentEvent, c as detectRuntimeShell, d as applyModelOverrideToSessionEntry, f as applyVerboseOverride, g as resolveAgentTimeoutMs, i as resolveOutboundTarget, j as FailoverError, k as hasNonzeroUsage, l as resolveSendPolicy, m as clearSessionAuthProfileOverride, n as buildModelAliasLines, o as buildSystemPromptParams, p as loadModelCatalog, s as getProcessSupervisor, t as runAgentTurn, u as lookupContextTokens, v as AGENT_LANE_SUBAGENT, w as makeBootstrapWarn, x as registerAgentRunContext, y as clearAgentRunContext } from "../../unified-runner-ChkYfsxy.js";
8
8
  import { d as ensureAuthProfileStore } from "../../auth-profiles-BSw0aQND.js";
9
9
  import { t as formatCliCommand } from "../../command-format-BrrHNE8r.js";
10
10
  import { _ as DEFAULT_MODEL, a as isCliProvider, c as normalizeProviderId, d as resolveDefaultModelForAgent, g as DEFAULT_CONTEXT_TOKENS, m as resolveThinkingDefault, o as modelKey, s as normalizeModelRef, t as buildAllowedModelSet, u as resolveConfiguredModelRef, v as DEFAULT_PROVIDER } from "../../model-selection-BqshlIZc.js";
11
- import { At as resolveSessionResetPolicy, Dt as resolveSessionKey, It as resolveAgentMainSessionKey, Lt as resolveExplicitAgentSessionKey, M as classifyFailoverReason, Ot as evaluateSessionFreshness, Rt as resolveMainSessionKey, U as isFailoverErrorMessage, a as normalizeOutboundPayloadsForJson, at as loadSessionStore, i as normalizeOutboundPayloads, it as resolveAndPersistSessionFile, jt as resolveSessionResetType, kt as resolveChannelResetConfig, lt as updateSessionStore, nt as parseSessionThreadInfo, r as formatOutboundPayloadLog, t as deliverOutboundPayloads, vt as normalizeAccountId } from "../../deliver-BiWlR84Y.js";
11
+ import { At as resolveSessionResetPolicy, Dt as resolveSessionKey, It as resolveAgentMainSessionKey, Lt as resolveExplicitAgentSessionKey, M as classifyFailoverReason, Ot as evaluateSessionFreshness, Rt as resolveMainSessionKey, U as isFailoverErrorMessage, a as normalizeOutboundPayloadsForJson, at as loadSessionStore, i as normalizeOutboundPayloads, it as resolveAndPersistSessionFile, jt as resolveSessionResetType, kt as resolveChannelResetConfig, lt as updateSessionStore, nt as parseSessionThreadInfo, r as formatOutboundPayloadLog, t as deliverOutboundPayloads, vt as normalizeAccountId } from "../../deliver-B04yNX82.js";
12
12
  import { c as loadConfig } from "../../ssrf-DNhyFMRW.js";
13
13
  import "../../boolean-BsqeuxE6.js";
14
14
  import { s as isTruthyEnvValue } from "../../shell-env-B1lDWz4t.js";
15
15
  import "../../manifest-registry-BGJu2aC-.js";
16
- import "../../chrome-OTJg3QKn.js";
16
+ import "../../chrome-CmQwGAuL.js";
17
17
  import "../../frontmatter-CJEX1BrH.js";
18
18
  import { n as buildWorkspaceSkillSnapshot } from "../../skills-Dm4gX4Tl.js";
19
19
  import "../../redact-CSGZUFxa.js";
@@ -27,7 +27,7 @@ import { i as resolveSessionTranscriptPath, n as resolveSessionFilePath, s as re
27
27
  import { t as SILENT_REPLY_TOKEN } from "../../tokens-D_vCJSzF.js";
28
28
  import "../../diagnostic-BALvP9wI.js";
29
29
  import "../../diagnostic-session-state-BDfanaOY.js";
30
- import "../../manager-DjhE8yLr.js";
30
+ import "../../manager-B-wPs7cb.js";
31
31
  import "../../github-copilot-token-uhEBNQfj.js";
32
32
  import "../../pi-auth-json-DCGR3yfh.js";
33
33
  import "../../reply-prefix-D0NzzC4I.js";
@@ -4,15 +4,15 @@ import "../../registry-C-JddWwo.js";
4
4
  import { o as resolveAgentWorkspaceDir } from "../../agent-scope-B1Ina6jV.js";
5
5
  import { t as createSubsystemLogger } from "../../subsystem-DzRUKS9f.js";
6
6
  import "../../workspace-CsaDUuDn.js";
7
- import "../../unified-runner-ByJSAveb.js";
7
+ import "../../unified-runner-ChkYfsxy.js";
8
8
  import "../../auth-profiles-BSw0aQND.js";
9
9
  import "../../model-selection-BqshlIZc.js";
10
- import { Tt as hasInterSessionUserProvenance } from "../../deliver-BiWlR84Y.js";
10
+ import { Tt as hasInterSessionUserProvenance } from "../../deliver-B04yNX82.js";
11
11
  import "../../ssrf-DNhyFMRW.js";
12
12
  import "../../boolean-BsqeuxE6.js";
13
13
  import "../../shell-env-B1lDWz4t.js";
14
14
  import "../../manifest-registry-BGJu2aC-.js";
15
- import "../../chrome-OTJg3QKn.js";
15
+ import "../../chrome-CmQwGAuL.js";
16
16
  import "../../frontmatter-CJEX1BrH.js";
17
17
  import "../../skills-Dm4gX4Tl.js";
18
18
  import "../../redact-CSGZUFxa.js";
@@ -26,7 +26,7 @@ import "../../paths-BsT3BvfH.js";
26
26
  import "../../tokens-D_vCJSzF.js";
27
27
  import "../../diagnostic-BALvP9wI.js";
28
28
  import "../../diagnostic-session-state-BDfanaOY.js";
29
- import "../../manager-DjhE8yLr.js";
29
+ import "../../manager-B-wPs7cb.js";
30
30
  import "../../github-copilot-token-uhEBNQfj.js";
31
31
  import "../../pi-auth-json-DCGR3yfh.js";
32
32
  import "../../reply-prefix-D0NzzC4I.js";
@@ -1 +1 @@
1
- 8541c326f67f2faaedef364e66a9aaa745e5471d29b0c0f99af2304bf1dcca9d
1
+ c9d3271c9b49843128f1170d40c1f7e0a23bd45b3480f9a6f294ffd294c04151
@@ -9,7 +9,7 @@ import fs$1 from "node:fs/promises";
9
9
  import { execFileSync, spawn } from "node:child_process";
10
10
  import net from "node:net";
11
11
  import { createServer } from "node:http";
12
- import WebSocket$1, { WebSocketServer } from "ws";
12
+ import WebSocket, { WebSocketServer } from "ws";
13
13
  import { Buffer as Buffer$1 } from "node:buffer";
14
14
 
15
15
  //#region src/browser/constants.ts
@@ -177,7 +177,7 @@ async function ensureChromeExtensionRelayServer(opts) {
177
177
  let nextExtensionId = 1;
178
178
  const sendToExtension = async (payload) => {
179
179
  const ws = extensionWs;
180
- if (!ws || ws.readyState !== WebSocket$1.OPEN) throw new Error("Chrome extension not connected");
180
+ if (!ws || ws.readyState !== WebSocket.OPEN) throw new Error("Chrome extension not connected");
181
181
  ws.send(JSON.stringify(payload));
182
182
  return await new Promise((resolve, reject) => {
183
183
  const timer = setTimeout(() => {
@@ -194,12 +194,12 @@ async function ensureChromeExtensionRelayServer(opts) {
194
194
  const broadcastToCdpClients = (evt) => {
195
195
  const msg = JSON.stringify(evt);
196
196
  for (const ws of cdpClients) {
197
- if (ws.readyState !== WebSocket$1.OPEN) continue;
197
+ if (ws.readyState !== WebSocket.OPEN) continue;
198
198
  ws.send(msg);
199
199
  }
200
200
  };
201
201
  const sendResponseToCdp = (ws, res) => {
202
- if (ws.readyState !== WebSocket$1.OPEN) return;
202
+ if (ws.readyState !== WebSocket.OPEN) return;
203
203
  ws.send(JSON.stringify(res));
204
204
  };
205
205
  const ensureTargetEventsForClient = (ws, mode) => {
@@ -424,7 +424,7 @@ async function ensureChromeExtensionRelayServer(opts) {
424
424
  wssExtension.on("connection", (ws) => {
425
425
  extensionWs = ws;
426
426
  const ping = setInterval(() => {
427
- if (ws.readyState !== WebSocket$1.OPEN) return;
427
+ if (ws.readyState !== WebSocket.OPEN) return;
428
428
  ws.send(JSON.stringify({ method: "ping" }));
429
429
  }, 5e3);
430
430
  ws.on("message", (data) => {
@@ -742,7 +742,7 @@ async function fetchOk(url, timeoutMs = 1500, init) {
742
742
  }
743
743
  async function withCdpSocket(wsUrl, fn, opts) {
744
744
  const headers = getHeadersWithAuth(wsUrl, opts?.headers ?? {});
745
- const ws = new WebSocket$1(wsUrl, {
745
+ const ws = new WebSocket(wsUrl, {
746
746
  handshakeTimeout: typeof opts?.handshakeTimeoutMs === "number" && Number.isFinite(opts.handshakeTimeoutMs) ? Math.max(1, Math.floor(opts.handshakeTimeoutMs)) : 5e3,
747
747
  ...Object.keys(headers).length ? { headers } : {}
748
748
  });
@@ -1636,7 +1636,7 @@ async function getChromeWebSocketUrl(cdpUrl, timeoutMs = 500) {
1636
1636
  async function canOpenWebSocket(wsUrl, timeoutMs = 800) {
1637
1637
  return await new Promise((resolve) => {
1638
1638
  const headers = getHeadersWithAuth(wsUrl);
1639
- const ws = new WebSocket$1(wsUrl, {
1639
+ const ws = new WebSocket(wsUrl, {
1640
1640
  handshakeTimeout: timeoutMs,
1641
1641
  ...Object.keys(headers).length ? { headers } : {}
1642
1642
  });
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-Cbj13DAv.js";
2
2
  import { Qt as getPrimaryCommand, tn as hasHelpOrVersion } from "./entry.js";
3
- import { i as registerSubCliCommands, o as reparseProgramFromActionArgs } from "./register.subclis-Dv8bf2ox.js";
3
+ import { i as registerSubCliCommands, o as reparseProgramFromActionArgs } from "./register.subclis-B16w8wxz.js";
4
4
 
5
5
  //#region src/cli/program/command-registry.ts
6
6
  var command_registry_exports = /* @__PURE__ */ __exportAll({
@@ -22,7 +22,7 @@ const coreEntries = [
22
22
  hasSubcommands: false
23
23
  }],
24
24
  register: async ({ program }) => {
25
- (await import("./register.setup-D3XQyELS.js")).registerSetupCommand(program);
25
+ (await import("./register.setup-DDMtRxcF.js")).registerSetupCommand(program);
26
26
  }
27
27
  },
28
28
  {
@@ -32,7 +32,7 @@ const coreEntries = [
32
32
  hasSubcommands: false
33
33
  }],
34
34
  register: async ({ program }) => {
35
- (await import("./register.onboard-O0P6rCuX.js")).registerOnboardCommand(program);
35
+ (await import("./register.onboard-BjPPudfS.js")).registerOnboardCommand(program);
36
36
  }
37
37
  },
38
38
  {
@@ -79,7 +79,7 @@ const coreEntries = [
79
79
  }
80
80
  ],
81
81
  register: async ({ program }) => {
82
- (await import("./register.maintenance-CTF70v5h.js")).registerMaintenanceCommands(program);
82
+ (await import("./register.maintenance-81d4KKmA.js")).registerMaintenanceCommands(program);
83
83
  }
84
84
  },
85
85
  {
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-Cbj13DAv.js";
2
2
  import { Mt as pathExists, l as routeLogsToStderr, yt as resolveStateDir } from "./entry.js";
3
- import { n as getSubCliEntries, r as registerSubCliByName } from "./register.subclis-Dv8bf2ox.js";
4
- import { i as registerCoreCliByName, n as getCoreCliCommandNames } from "./command-registry-2hfSOaGL.js";
3
+ import { n as getSubCliEntries, r as registerSubCliByName } from "./register.subclis-B16w8wxz.js";
4
+ import { i as registerCoreCliByName, n as getCoreCliCommandNames } from "./command-registry-Bmzf59Qv.js";
5
5
  import { t as getProgramContext } from "./program-context-CGKRxOBU.js";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-Cbj13DAv.js";
2
2
  import { _ as resolveStateDir } from "./paths-By0XjHBk.js";
3
3
  import { f as pathExists } from "./utils-CTPsqyE_.js";
4
4
  import { i as routeLogsToStderr } from "./subsystem-CHbO_DkH.js";
5
- import { a as registerCoreCliByName, c as getSubCliEntries, l as registerSubCliByName, r as getCoreCliCommandNames, t as getProgramContext } from "./program-context-CCIGjsQL.js";
5
+ import { a as registerCoreCliByName, c as getSubCliEntries, l as registerSubCliByName, r as getCoreCliCommandNames, t as getProgramContext } from "./program-context-CZYb4zB2.js";
6
6
  import path from "node:path";
7
7
  import os from "node:os";
8
8
  import fs from "node:fs/promises";
@@ -987,6 +987,172 @@ body {
987
987
  box-shadow: 0 0 8px rgba(0, 212, 255, 0.18);
988
988
  }
989
989
 
990
+ /* ── Phase 4: type-specific body renderers ───────────────────────── */
991
+
992
+ /* Text + markdown: inherit from .symframe-card-body. Add minimal
993
+ markdown rules — most users will want headings and code blocks
994
+ readable but compact. */
995
+ .symframe-body-markdown h1,
996
+ .symframe-body-markdown h2,
997
+ .symframe-body-markdown h3,
998
+ .symframe-body-markdown h4 {
999
+ font-size: 12px;
1000
+ font-weight: 600;
1001
+ margin: 8px 0 4px;
1002
+ color: var(--text);
1003
+ }
1004
+ .symframe-body-markdown p {
1005
+ margin: 4px 0;
1006
+ }
1007
+ .symframe-body-markdown ul,
1008
+ .symframe-body-markdown ol {
1009
+ padding-left: 18px;
1010
+ margin: 4px 0;
1011
+ }
1012
+ .symframe-body-markdown code {
1013
+ font-family: var(--font-mono);
1014
+ font-size: 10px;
1015
+ padding: 1px 4px;
1016
+ background: rgba(255, 255, 255, 0.04);
1017
+ border-radius: 3px;
1018
+ }
1019
+ .symframe-body-markdown pre {
1020
+ background: rgba(0, 0, 0, 0.25);
1021
+ border: 1px solid rgba(255, 255, 255, 0.05);
1022
+ border-radius: 5px;
1023
+ padding: 8px;
1024
+ overflow-x: auto;
1025
+ margin: 6px 0;
1026
+ }
1027
+ .symframe-body-markdown pre code {
1028
+ background: transparent;
1029
+ padding: 0;
1030
+ font-size: 10px;
1031
+ }
1032
+
1033
+ /* Code body — pre/code wrapper with hljs theme-friendly chrome. */
1034
+ .symframe-body-code {
1035
+ background: rgba(0, 0, 0, 0.3);
1036
+ border: 1px solid rgba(255, 255, 255, 0.06);
1037
+ border-radius: 5px;
1038
+ padding: 8px 10px;
1039
+ margin: 0;
1040
+ overflow-x: auto;
1041
+ max-height: 320px;
1042
+ overflow-y: auto;
1043
+ }
1044
+ .symframe-body-code code {
1045
+ font-family: var(--font-mono);
1046
+ font-size: 10px;
1047
+ line-height: 1.45;
1048
+ color: var(--text);
1049
+ background: transparent;
1050
+ }
1051
+
1052
+ /* Email-draft — From/To/Subject header rows + body. */
1053
+ .symframe-body-email {
1054
+ display: flex;
1055
+ flex-direction: column;
1056
+ gap: 4px;
1057
+ }
1058
+ .symframe-email-row {
1059
+ display: flex;
1060
+ gap: 6px;
1061
+ font-family: var(--font-mono);
1062
+ font-size: 10px;
1063
+ letter-spacing: 0.02em;
1064
+ padding: 2px 0;
1065
+ border-bottom: 1px dashed rgba(255, 255, 255, 0.05);
1066
+ }
1067
+ .symframe-email-row:last-of-type {
1068
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
1069
+ margin-bottom: 4px;
1070
+ padding-bottom: 4px;
1071
+ }
1072
+ .symframe-email-label {
1073
+ flex: 0 0 auto;
1074
+ color: var(--text-dim);
1075
+ text-transform: uppercase;
1076
+ letter-spacing: 0.06em;
1077
+ font-size: 9px;
1078
+ min-width: 48px;
1079
+ opacity: 0.7;
1080
+ }
1081
+ .symframe-email-value {
1082
+ color: var(--text);
1083
+ word-break: break-word;
1084
+ }
1085
+ .symframe-email-body {
1086
+ font-family: var(--font-mono);
1087
+ font-size: 11px;
1088
+ line-height: 1.5;
1089
+ color: var(--text);
1090
+ white-space: pre-wrap;
1091
+ word-break: break-word;
1092
+ margin-top: 4px;
1093
+ }
1094
+
1095
+ /* PDF embed */
1096
+ .symframe-body-pdf {
1097
+ display: flex;
1098
+ flex-direction: column;
1099
+ gap: 6px;
1100
+ }
1101
+ .symframe-pdf-iframe {
1102
+ width: 100%;
1103
+ height: 280px;
1104
+ border: 1px solid rgba(255, 255, 255, 0.08);
1105
+ border-radius: 5px;
1106
+ background: rgba(0, 0, 0, 0.2);
1107
+ }
1108
+
1109
+ /* Audio + video shared chrome */
1110
+ .symframe-body-audio,
1111
+ .symframe-body-video {
1112
+ display: flex;
1113
+ flex-direction: column;
1114
+ gap: 6px;
1115
+ }
1116
+ .symframe-audio,
1117
+ .symframe-video {
1118
+ width: 100%;
1119
+ border-radius: 5px;
1120
+ background: rgba(0, 0, 0, 0.2);
1121
+ }
1122
+ .symframe-video {
1123
+ max-height: 280px;
1124
+ object-fit: contain;
1125
+ }
1126
+
1127
+ /* Empty-media placeholder when url is missing */
1128
+ .symframe-body-empty {
1129
+ font-family: var(--font-mono);
1130
+ font-size: 10px;
1131
+ color: var(--text-dim);
1132
+ opacity: 0.6;
1133
+ font-style: italic;
1134
+ }
1135
+
1136
+ /* Download link — used by pdf/audio/video renderers */
1137
+ .symframe-download {
1138
+ display: inline-block;
1139
+ padding: 4px 8px;
1140
+ border: 1px solid var(--glass-border);
1141
+ border-radius: 4px;
1142
+ font-family: var(--font-mono);
1143
+ font-size: 10px;
1144
+ letter-spacing: 0.04em;
1145
+ color: var(--text-dim);
1146
+ text-decoration: none;
1147
+ transition: all 0.15s ease;
1148
+ align-self: flex-start;
1149
+ }
1150
+ .symframe-download:hover {
1151
+ border-color: var(--accent-cyan);
1152
+ color: var(--accent-cyan);
1153
+ background: rgba(0, 212, 255, 0.06);
1154
+ }
1155
+
990
1156
  /* ── Response Area ────────────────────────────────────────────────── */
991
1157
  .response-area {
992
1158
  position: fixed;
@@ -57,33 +57,261 @@
57
57
  countEl.textContent = total === 1 ? "1 card" : `${total} cards`;
58
58
  }
59
59
 
60
- // Per-type renderers transform a raw card into HTML the body slot
61
- // accepts. Phase 3 ships `text` (plain) and `markdown` (via the bundled
62
- // marked.min.js). Future types (`code`, `email-draft`, `pdf`, etc.) plug
63
- // in here without touching the rest of the registry.
60
+ // ── Per-type body renderers ──
61
+ // Each renderer takes a card and returns an HTMLElement (or
62
+ // DocumentFragment) that gets appended to the body slot. New types
63
+ // plug in by adding to the renderers map below — no other code path
64
+ // changes required.
65
+
66
+ function renderTextBody(card) {
67
+ const div = document.createElement("div");
68
+ div.className = "symframe-body-text";
69
+ if (card.bodyHtml) {
70
+ div.innerHTML = card.bodyHtml;
71
+ } else {
72
+ div.textContent = card.body || "";
73
+ }
74
+ return div;
75
+ }
76
+
77
+ function renderMarkdownBody(card) {
78
+ const div = document.createElement("div");
79
+ div.className = "symframe-body-markdown";
80
+ if (typeof window.marked?.parse === "function") {
81
+ div.innerHTML = window.marked.parse(card.body || "");
82
+ } else {
83
+ div.textContent = card.body || "";
84
+ }
85
+ return div;
86
+ }
87
+
88
+ function renderCodeBody(card) {
89
+ const pre = document.createElement("pre");
90
+ pre.className = "symframe-body-code";
91
+ const code = document.createElement("code");
92
+ if (card.language) {
93
+ code.classList.add(`language-${card.language}`);
94
+ }
95
+ code.textContent = card.body || "";
96
+ pre.appendChild(code);
97
+ // hljs is bundled as window.hljs via vendor/highlight.min.js. Best-effort
98
+ // highlight; if hljs throws (unknown language, etc.) the plain pre/code
99
+ // already renders fine.
100
+ if (typeof window.hljs?.highlightElement === "function") {
101
+ try {
102
+ window.hljs.highlightElement(code);
103
+ } catch (err) {
104
+ console.warn("[symframe] hljs.highlightElement failed:", err);
105
+ }
106
+ }
107
+ return pre;
108
+ }
109
+
110
+ function renderEmailDraftBody(card) {
111
+ const wrap = document.createElement("div");
112
+ wrap.className = "symframe-body-email";
113
+ const email = card.email || {};
114
+ const headerRows = [
115
+ ["From", email.from],
116
+ ["To", email.to],
117
+ ["Cc", email.cc],
118
+ ["Bcc", email.bcc],
119
+ ["Subject", email.subject],
120
+ ];
121
+ for (const [label, value] of headerRows) {
122
+ if (!value) {
123
+ continue;
124
+ }
125
+ const row = document.createElement("div");
126
+ row.className = "symframe-email-row";
127
+ const lbl = document.createElement("span");
128
+ lbl.className = "symframe-email-label";
129
+ lbl.textContent = `${label}:`;
130
+ const val = document.createElement("span");
131
+ val.className = "symframe-email-value";
132
+ val.textContent = String(value);
133
+ row.appendChild(lbl);
134
+ row.appendChild(val);
135
+ wrap.appendChild(row);
136
+ }
137
+ if (email.body) {
138
+ const body = document.createElement("div");
139
+ body.className = "symframe-email-body";
140
+ body.textContent = email.body;
141
+ wrap.appendChild(body);
142
+ }
143
+ return wrap;
144
+ }
145
+
146
+ function renderEmptyMedia(reason) {
147
+ const div = document.createElement("div");
148
+ div.className = "symframe-body-empty";
149
+ div.textContent = reason;
150
+ return div;
151
+ }
152
+
153
+ function renderPdfBody(card) {
154
+ if (!card.url) {
155
+ return renderEmptyMedia("(no url provided)");
156
+ }
157
+ const wrap = document.createElement("div");
158
+ wrap.className = "symframe-body-pdf";
159
+ const frame = document.createElement("iframe");
160
+ frame.src = card.url;
161
+ frame.className = "symframe-pdf-iframe";
162
+ frame.setAttribute("title", card.title || "PDF document");
163
+ wrap.appendChild(frame);
164
+ wrap.appendChild(buildDownloadLink(card.url, card.downloadName, "Download PDF"));
165
+ return wrap;
166
+ }
167
+
168
+ function renderAudioBody(card) {
169
+ if (!card.url) {
170
+ return renderEmptyMedia("(no url provided)");
171
+ }
172
+ const wrap = document.createElement("div");
173
+ wrap.className = "symframe-body-audio";
174
+ const audio = document.createElement("audio");
175
+ audio.controls = true;
176
+ audio.preload = "metadata";
177
+ audio.className = "symframe-audio";
178
+ if (card.mimeType) {
179
+ const source = document.createElement("source");
180
+ source.src = card.url;
181
+ source.type = card.mimeType;
182
+ audio.appendChild(source);
183
+ } else {
184
+ audio.src = card.url;
185
+ }
186
+ wrap.appendChild(audio);
187
+ wrap.appendChild(buildDownloadLink(card.url, card.downloadName, "Download"));
188
+ return wrap;
189
+ }
190
+
191
+ function renderVideoBody(card) {
192
+ if (!card.url) {
193
+ return renderEmptyMedia("(no url provided)");
194
+ }
195
+ const wrap = document.createElement("div");
196
+ wrap.className = "symframe-body-video";
197
+ const video = document.createElement("video");
198
+ video.controls = true;
199
+ video.preload = "metadata";
200
+ video.className = "symframe-video";
201
+ video.playsInline = true;
202
+ if (card.poster) {
203
+ video.poster = card.poster;
204
+ }
205
+ if (card.mimeType) {
206
+ const source = document.createElement("source");
207
+ source.src = card.url;
208
+ source.type = card.mimeType;
209
+ video.appendChild(source);
210
+ } else {
211
+ video.src = card.url;
212
+ }
213
+ wrap.appendChild(video);
214
+ wrap.appendChild(buildDownloadLink(card.url, card.downloadName, "Download"));
215
+ return wrap;
216
+ }
217
+
218
+ function buildDownloadLink(url, downloadName, label) {
219
+ const a = document.createElement("a");
220
+ a.href = url;
221
+ if (downloadName) {
222
+ a.download = downloadName;
223
+ } else {
224
+ a.setAttribute("download", "");
225
+ }
226
+ a.target = "_blank";
227
+ a.rel = "noopener noreferrer";
228
+ a.className = "symframe-download";
229
+ a.textContent = `⬇ ${label}`;
230
+ return a;
231
+ }
232
+
233
+ const renderers = {
234
+ text: renderTextBody,
235
+ markdown: renderMarkdownBody,
236
+ code: renderCodeBody,
237
+ "email-draft": renderEmailDraftBody,
238
+ pdf: renderPdfBody,
239
+ audio: renderAudioBody,
240
+ video: renderVideoBody,
241
+ };
242
+
64
243
  function renderBodyForType(card) {
65
244
  const type = card.type || "text";
66
- if (type === "markdown" && typeof window.marked?.parse === "function") {
67
- return { html: window.marked.parse(card.body || "") };
245
+ const renderer = renderers[type] || renderers.text;
246
+ return renderer(card);
247
+ }
248
+
249
+ // True iff the card has any body-shaped data — body, bodyHtml, or
250
+ // type-specific structured fields. Used to decide whether to render
251
+ // the body slot at all.
252
+ function hasBodyContent(card) {
253
+ if (card.body || card.bodyHtml) {
254
+ return true;
255
+ }
256
+ if (card.url) {
257
+ return true;
258
+ }
259
+ if (card.email && typeof card.email === "object") {
260
+ return true;
261
+ }
262
+ return false;
263
+ }
264
+
265
+ // Choose what `Copy` would copy for this card. Honors body/bodyHtml first,
266
+ // then type-specific shapes (email-draft renders a plain-text email;
267
+ // url-based media types copy the URL).
268
+ function resolveCopyTarget(card) {
269
+ if (card.body) {
270
+ return card.body;
68
271
  }
69
- // Fallback: text body (escaped via textContent in caller) or trusted HTML.
70
272
  if (card.bodyHtml) {
71
- return { html: card.bodyHtml };
273
+ return card.bodyHtml;
274
+ }
275
+ if (card.email && typeof card.email === "object") {
276
+ const lines = [];
277
+ if (card.email.from) {
278
+ lines.push(`From: ${card.email.from}`);
279
+ }
280
+ if (card.email.to) {
281
+ lines.push(`To: ${card.email.to}`);
282
+ }
283
+ if (card.email.cc) {
284
+ lines.push(`Cc: ${card.email.cc}`);
285
+ }
286
+ if (card.email.bcc) {
287
+ lines.push(`Bcc: ${card.email.bcc}`);
288
+ }
289
+ if (card.email.subject) {
290
+ lines.push(`Subject: ${card.email.subject}`);
291
+ }
292
+ if (card.email.body) {
293
+ lines.push("");
294
+ lines.push(card.email.body);
295
+ }
296
+ return lines.join("\n");
297
+ }
298
+ if (card.url) {
299
+ return card.url;
72
300
  }
73
- return { text: card.body || "" };
301
+ return "";
74
302
  }
75
303
 
76
- // Default Copy action when the card has a body and no explicit actions.
77
- // Wraps card.body (the original text the producer pushed, not the
78
- // rendered HTML) into a clipboard.writeText call.
304
+ // Default Copy action when the card has copyable content and no explicit
305
+ // actions. Custom actions take precedence server-pushed cards or
306
+ // tool-pushed cards with their own buttons get those instead.
79
307
  function buildDefaultActions(card) {
80
308
  if (Array.isArray(card.actions) && card.actions.length > 0) {
81
309
  return card.actions;
82
310
  }
83
- if (!card.body && !card.bodyHtml) {
311
+ const copyTarget = resolveCopyTarget(card);
312
+ if (!copyTarget) {
84
313
  return [];
85
314
  }
86
- const copyTarget = card.body ?? card.bodyHtml ?? "";
87
315
  return [
88
316
  {
89
317
  label: "Copy",
@@ -133,15 +361,13 @@
133
361
  }
134
362
  el.appendChild(header);
135
363
 
136
- // Body via type-specific renderer
137
- if (card.bodyHtml || card.body) {
364
+ // Body via type-specific renderer (returns an Element)
365
+ if (hasBodyContent(card)) {
138
366
  const body = document.createElement("div");
139
367
  body.className = "symframe-card-body";
140
368
  const rendered = renderBodyForType(card);
141
- if (rendered.html !== undefined) {
142
- body.innerHTML = rendered.html;
143
- } else {
144
- body.textContent = rendered.text;
369
+ if (rendered) {
370
+ body.appendChild(rendered);
145
371
  }
146
372
  el.appendChild(body);
147
373
  }