@symerian/symi 3.5.25 → 3.5.27

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 (161) hide show
  1. package/dist/{agent-DgVWcnlD.js → agent-CB5yb-7V.js} +1 -1
  2. package/dist/{agent-CBfp75J4.js → agent-DxRVDqSg.js} +1 -1
  3. package/dist/{agents-Dlcpc1K3.js → agents-DtRd3yEb.js} +3 -3
  4. package/dist/{audit-Ce-u6aaR.js → audit-C_3fAFeS.js} +2 -2
  5. package/dist/{audit-CPloCzEZ.js → audit-DyncRBKq.js} +2 -2
  6. package/dist/{auth-choice--DymOp5O.js → auth-choice-5SLfZiQ7.js} +2 -2
  7. package/dist/{auth-choice-CUvx59kv.js → auth-choice-DQplW60s.js} +2 -2
  8. package/dist/{banner-CY_9VW7E.js → banner-B_P_FrmA.js} +1 -1
  9. package/dist/{browser-cli-C5VVk6C9.js → browser-cli-CcCYZtag.js} +3 -3
  10. package/dist/{browser-cli-Ibgld3mA.js → browser-cli-D6xFqw9w.js} +3 -3
  11. package/dist/build-info.json +3 -3
  12. package/dist/bundled/boot-md/handler.js +2 -2
  13. package/dist/bundled/session-memory/handler.js +2 -2
  14. package/dist/{call-xdEE97oU.js → call-AZ9vNhST.js} +1 -1
  15. package/dist/{call-C2urR5Fj.js → call-DTshgdlW.js} +1 -1
  16. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  17. package/dist/{channel-options-rFgJaKzJ.js → channel-options-8NGYZjfd.js} +1 -1
  18. package/dist/{channel-options-DCmWOAc0.js → channel-options-DnWkDAWi.js} +1 -1
  19. package/dist/{channels-cli-QMHCRAb5.js → channels-cli-Bx90nSmB.js} +9 -9
  20. package/dist/{channels-cli-Cs1Z0dn8.js → channels-cli-CGCyzahW.js} +9 -9
  21. package/dist/{cli-BafqEuaj.js → cli-DHG9iZJi.js} +6 -6
  22. package/dist/{cli-CadU09SC.js → cli-DZAtyHzb.js} +6 -6
  23. package/dist/{client-yVTJ5jx5.js → client-BOd5o3Kp.js} +9 -1
  24. package/dist/{client-Cs9Bh-G0.js → client-DH75FlQF.js} +9 -1
  25. package/dist/{command-registry-CLm8TAH3.js → command-registry-DBbzZvh6.js} +11 -11
  26. package/dist/{completion-cli-Dd56ibFZ.js → completion-cli-Cbf0-8ob.js} +1 -1
  27. package/dist/{completion-cli-DOBXkVwG.js → completion-cli-DoQrkCAd.js} +2 -2
  28. package/dist/{config-cli-BgHYqFH1.js → config-cli-9a5qBcdo.js} +1 -1
  29. package/dist/{config-cli-GwUJEapt.js → config-cli-D4RmoOxe.js} +1 -1
  30. package/dist/{configure-5md67tCP.js → configure-kaf3aurx.js} +6 -6
  31. package/dist/{configure-DmzM0S7L.js → configure-ryB8EvyP.js} +6 -6
  32. package/dist/control-ui/css/style.css +311 -0
  33. package/dist/control-ui/index.html +42 -0
  34. package/dist/control-ui/js/symframe-modes.js +65 -0
  35. package/dist/control-ui/js/symframe.js +139 -0
  36. package/dist/{cron-cli-BNBs3zue.js → cron-cli-DsGkkt1s.js} +3 -3
  37. package/dist/{cron-cli-CE9bV9Et.js → cron-cli-VH_t2D1o.js} +3 -3
  38. package/dist/{daemon-cli-BYiG4GOU.js → daemon-cli-3PFXkheJ.js} +2 -2
  39. package/dist/{daemon-cli-DDqeG2nd.js → daemon-cli-dbAPTgGm.js} +2 -2
  40. package/dist/daemon-cli.js +9 -1
  41. package/dist/{devices-cli-Bhr6jGtH.js → devices-cli-Bhi1rWnT.js} +2 -2
  42. package/dist/{devices-cli-BJ3rVWOw.js → devices-cli-CIAuYH4P.js} +2 -2
  43. package/dist/{doctor-completion-BpUGRguQ.js → doctor-completion--j-3qqz6.js} +1 -1
  44. package/dist/{doctor-completion-k0HiL05o.js → doctor-completion-Bk55g4MP.js} +1 -1
  45. package/dist/entry.js +1 -1
  46. package/dist/{exec-approvals-cli-Cau1vPQI.js → exec-approvals-cli-BiXeyPPz.js} +4 -4
  47. package/dist/{exec-approvals-cli-CzvIqBUd.js → exec-approvals-cli-xTOKcyZh.js} +4 -4
  48. package/dist/extensionAPI.js +2 -2
  49. package/dist/{gateway-cli-D1d5YAt0.js → gateway-cli-B3FuQctH.js} +16 -16
  50. package/dist/{gateway-cli-DgmGygc6.js → gateway-cli-BGMVOW2G.js} +16 -16
  51. package/dist/{gateway-rpc-BTGT1SuZ.js → gateway-rpc-C6lKFbor.js} +1 -1
  52. package/dist/{gateway-rpc-Kv0-8W2E.js → gateway-rpc-DpRnrv_i.js} +1 -1
  53. package/dist/{glass-ui-ws-BH9WH_VN.js → glass-ui-ws-BJcI_IdV.js} +13 -13
  54. package/dist/{glass-ui-ws-Dl45d7eU.js → glass-ui-ws-Z-W33_mF.js} +13 -13
  55. package/dist/{health-DpE9GWGn.js → health-7UqIGG8U.js} +1 -1
  56. package/dist/{health-znS85U-F.js → health-Dkwuq8ta.js} +1 -1
  57. package/dist/{hooks-cli-Cb9BzCOY.js → hooks-cli-Bn6x6Jnj.js} +7 -7
  58. package/dist/{hooks-cli-D7xxbEwI.js → hooks-cli-D43GEG-W.js} +7 -7
  59. package/dist/index.js +10 -10
  60. package/dist/llm-slug-generator.js +2 -2
  61. package/dist/{logs-cli-DvIVuW0d.js → logs-cli-BTU7zrkU.js} +3 -3
  62. package/dist/{logs-cli-LiAjkog2.js → logs-cli-CJLJ745C.js} +3 -3
  63. package/dist/{manager-V4UCf0Av.js → manager-BHddoe4T.js} +1 -1
  64. package/dist/{manager-CoCX7L5u.js → manager-Cw7IJGbB.js} +1 -1
  65. package/dist/{manager-CceVgXHV.js → manager-DH00nlW8.js} +1 -1
  66. package/dist/{manager-Cv4xI8sP.js → manager-DbVMwpDc.js} +1 -1
  67. package/dist/{memory-B067LTL9.js → memory-BZ24UkLW.js} +2 -2
  68. package/dist/{memory-A2D5e8dL.js → memory-C02Znuee.js} +2 -2
  69. package/dist/{memory-cli-1zjVJITN.js → memory-cli-083UJtVJ.js} +2 -2
  70. package/dist/{memory-cli-D8Z_5-Nz.js → memory-cli-0HHYm-B8.js} +2 -2
  71. package/dist/{models-BYev6YAG.js → models-Bh_frCup.js} +3 -3
  72. package/dist/{models-cli-Fa5r1QYP.js → models-cli-BhBu4Dwm.js} +9 -9
  73. package/dist/{models-cli-C979SN3Z.js → models-cli-C2EXmrYH.js} +8 -8
  74. package/dist/{node-cli-Cv7x1gGu.js → node-cli-CBW_ECu_.js} +1 -1
  75. package/dist/{node-cli-DGybtex_.js → node-cli-CWbA6YCw.js} +1 -1
  76. package/dist/{nodes-cli-C-mEw2av.js → nodes-cli-BfHcv62i.js} +3 -3
  77. package/dist/{nodes-cli-DaHOBbd4.js → nodes-cli-CJ0gxF5n.js} +3 -3
  78. package/dist/{onboard-D-m6WTZl.js → onboard-BSxqEQfo.js} +3 -3
  79. package/dist/{onboard-CotuAZi6.js → onboard-DnwWawQO.js} +3 -3
  80. package/dist/{onboard-channels-Z-Jkrk5-.js → onboard-channels-BkR3MKMp.js} +1 -1
  81. package/dist/{onboard-channels-DSyynpou.js → onboard-channels-qDThNkyj.js} +1 -1
  82. package/dist/{onboard-helpers-CLXJ8zp0.js → onboard-helpers-7IGQ8nAM.js} +1 -1
  83. package/dist/{onboard-helpers-Dhr98nj8.js → onboard-helpers-t7tI9KSA.js} +1 -1
  84. package/dist/{onboard-remote-DZdMxd1Q.js → onboard-remote-DH3bPYbE.js} +1 -1
  85. package/dist/{onboard-remote-BYBai0w3.js → onboard-remote-Dr75eqOQ.js} +1 -1
  86. package/dist/{onboard-skills-2SUv-W7i.js → onboard-skills-Bjf7kWvg.js} +1 -1
  87. package/dist/{onboard-skills-BxJlS1Bk.js → onboard-skills-uO7HZwjc.js} +1 -1
  88. package/dist/{onboarding-BckHLQMV.js → onboarding-CrCGoZ4H.js} +7 -7
  89. package/dist/{onboarding-BnoGiOh3.js → onboarding-DxZAGVkD.js} +7 -7
  90. package/dist/{onboarding.finalize-CbH_ShIW.js → onboarding.finalize-BYzduVFA.js} +9 -9
  91. package/dist/{onboarding.finalize-g86Q4433.js → onboarding.finalize-Dt-rcgpk.js} +8 -8
  92. package/dist/{onboarding.gateway-config-CEXjbEM0.js → onboarding.gateway-config-CkP7K4EN.js} +3 -3
  93. package/dist/{onboarding.gateway-config-sNSeEykB.js → onboarding.gateway-config-xWUSbtoY.js} +3 -3
  94. package/dist/{pi-embedded-B9rtlNMc.js → pi-embedded-DGnpyHm-.js} +50 -7
  95. package/dist/{plugin-registry-CS4WgHbu.js → plugin-registry-B74FBx4U.js} +1 -1
  96. package/dist/{plugin-registry-AsC9gydF.js → plugin-registry-D2_TLnOr.js} +1 -1
  97. package/dist/plugin-sdk/agents/tools/browser-tool.schema.d.ts +1 -1
  98. package/dist/plugin-sdk/gateway/protocol/schema/logs-chat.d.ts +5 -1
  99. package/dist/plugin-sdk/infra/symframe-broadcast.d.ts +7 -2
  100. package/dist/{plugins-cli-BY42hyHP.js → plugins-cli-Cmf3kRf5.js} +7 -7
  101. package/dist/{plugins-cli-CbByGz8z.js → plugins-cli-CqV1Q7ao.js} +7 -7
  102. package/dist/{program-P9F1DxBa.js → program-CGuYYumL.js} +12 -12
  103. package/dist/{program-context-CiyK6MfV.js → program-context-B0ovpbFU.js} +30 -30
  104. package/dist/{prompt-select-styled-DaNQ_GgB.js → prompt-select-styled-B5GrNfqR.js} +7 -7
  105. package/dist/{prompt-select-styled-B2ah467m.js → prompt-select-styled-b752FPLG.js} +7 -7
  106. package/dist/{provider-auth-helpers-Bgb_Bfp9.js → provider-auth-helpers-BKveYcEH.js} +1 -1
  107. package/dist/{provider-auth-helpers-D2FDvwT5.js → provider-auth-helpers-DU8RlQz7.js} +1 -1
  108. package/dist/{push-apns-va-DRu4U.js → push-apns-CyAPMMJC.js} +1 -1
  109. package/dist/{push-apns-DnSCjFu7.js → push-apns-HNg0rTkI.js} +1 -1
  110. package/dist/{register.agent-BqdQ0s30.js → register.agent-BKbQhrre.js} +11 -11
  111. package/dist/{register.agent-BuEcmVg0.js → register.agent-DbPYGG3u.js} +12 -12
  112. package/dist/{register.configure-YDKJ-KQg.js → register.configure-B0M15y31.js} +14 -14
  113. package/dist/{register.configure-Dhwi-4FG.js → register.configure-C4_ShaQV.js} +14 -14
  114. package/dist/{register.maintenance-iMjNw9uy.js → register.maintenance-BOYWTu9C.js} +12 -12
  115. package/dist/{register.maintenance-BiGvpEj-.js → register.maintenance-bzLNOqch.js} +13 -13
  116. package/dist/{register.message-BIuLbESM.js → register.message-Bfukmte_.js} +7 -7
  117. package/dist/{register.message-Ao_b9s2g.js → register.message-BtCthbpo.js} +7 -7
  118. package/dist/{register.onboard-C4vIASed.js → register.onboard-CDsSVoFI.js} +6 -6
  119. package/dist/{register.onboard-ymhSAezZ.js → register.onboard-D5GmuX8K.js} +6 -6
  120. package/dist/{register.setup-D-Jt2e1d.js → register.setup-Bp8rYeRx.js} +6 -6
  121. package/dist/{register.setup-CKs9mqtD.js → register.setup-DzospfSf.js} +6 -6
  122. package/dist/{register.status-health-sessions-BY9hMD1s.js → register.status-health-sessions-DE2db_Uo.js} +8 -8
  123. package/dist/{register.status-health-sessions-U-azRmpb.js → register.status-health-sessions-NVbWTQpZ.js} +8 -8
  124. package/dist/{register.subclis-Cibmeapc.js → register.subclis-QXO75pUm.js} +20 -20
  125. package/dist/{rpc-DjqFrbOc.js → rpc-B1czfnym.js} +1 -1
  126. package/dist/{rpc-BnAKPQDB.js → rpc-BBtNUyxN.js} +1 -1
  127. package/dist/{run-main-BhEQjKtM.js → run-main-MD8Z8ydF.js} +20 -20
  128. package/dist/{security-cli-CS-ZT1LE.js → security-cli-DZ-6QDRB.js} +3 -3
  129. package/dist/{security-cli-JKsf9SIu.js → security-cli-l59T6lAJ.js} +3 -3
  130. package/dist/{server-methods-B2tykucj.js → server-methods-Bl41VzMu.js} +18 -12
  131. package/dist/{server-methods-CWxr5b-w.js → server-methods-DTkVQH8Q.js} +18 -12
  132. package/dist/{server-node-events-ClDEcUTJ.js → server-node-events-C0smPxrR.js} +8 -8
  133. package/dist/{server-node-events-BKqszk_a.js → server-node-events-CxYKzrbU.js} +8 -8
  134. package/dist/{status-ojZB_Lrf.js → status-BEZotN01.js} +5 -5
  135. package/dist/{status-DHBVIMyL.js → status-D7QX7VDk.js} +5 -5
  136. package/dist/{status-DPGFZPpp.js → status-DrWy5OkC.js} +1 -1
  137. package/dist/{status-K1k4g3Ai.js → status-OTYFx93W.js} +1 -1
  138. package/dist/{subagent-registry-BOt7g9hn.js → subagent-registry-CVhLz4p3.js} +43 -8
  139. package/dist/{symframe-cli-CrUtJw_7.js → symframe-cli-BSAQ05fb.js} +22 -6
  140. package/dist/{symframe-cli-CX3dINPw.js → symframe-cli-BVmRLRid.js} +22 -6
  141. package/dist/{synthesis-Bc2QkGvt.js → synthesis-C1mZnGqD.js} +2 -2
  142. package/dist/{synthesis-yl24Ovw4.js → synthesis-CO5pr92g.js} +2 -2
  143. package/dist/{synthesis-BIQLrbys.js → synthesis-RhT991Ni.js} +6 -6
  144. package/dist/{synthesis-DmqicCsT.js → synthesis-ts2nG0tK.js} +6 -6
  145. package/dist/{system-cli-DUHzCGfK.js → system-cli-Du1GEeoO.js} +3 -3
  146. package/dist/{system-cli-CvN-Wq9U.js → system-cli-Dy6_0Qp7.js} +3 -3
  147. package/dist/{tui-DCqVZ6sC.js → tui-B2MY6Mj2.js} +2 -2
  148. package/dist/{tui-cli-DqvgN5ng.js → tui-cli-B-DKpOxE.js} +3 -3
  149. package/dist/{tui-cli-CI-P3sei.js → tui-cli-BMEnosW9.js} +3 -3
  150. package/dist/{tui-BxgdImL_.js → tui-h57L-YTX.js} +2 -2
  151. package/dist/{unified-runner-CznU7Ad0.js → unified-runner-4O0ifBsI.js} +43 -8
  152. package/dist/{unified-runner-BV5TdNFv.js → unified-runner-O3qR5Iie.js} +50 -7
  153. package/dist/{update-cli-DBoIBDNe.js → update-cli-BMURsYJU.js} +13 -13
  154. package/dist/{update-cli-Dn6QQZLd.js → update-cli-D9g9s187.js} +14 -14
  155. package/extensions/memory-core/package.json +1 -1
  156. package/extensions/msteams/CHANGELOG.md +12 -0
  157. package/extensions/msteams/package.json +1 -1
  158. package/extensions/open-prose/package.json +1 -1
  159. package/extensions/outlook/package.json +1 -1
  160. package/extensions/slack/package.json +1 -1
  161. package/package.json +1 -1
@@ -12,22 +12,22 @@ import { n as stylePromptMessage, r as stylePromptTitle, t as stylePromptHint }
12
12
  import { t as note$1 } from "./note-D9FXI2qW.js";
13
13
  import { t as WizardCancelledError } from "./prompts-CNzp9M83.js";
14
14
  import { t as createClackPrompter } from "./clack-prompter-BpcKjPIh.js";
15
- import { r as setupChannels, t as noteChannelStatus } from "./onboard-channels-DSyynpou.js";
15
+ import { r as setupChannels, t as noteChannelStatus } from "./onboard-channels-qDThNkyj.js";
16
16
  import { a as gatewayInstallErrorHint, i as buildGatewayInstallPlan, n as GATEWAY_DAEMON_RUNTIME_OPTIONS, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-DlQla_dA.js";
17
- import { a as ensureWorkspaceAndSessions, b as waitForGatewayReachable, g as resolveControlUiLinks, h as randomToken, m as probeGatewayReachable, n as applyWizardMetadata, p as printWizardHeader, s as guardCancel, t as DEFAULT_WORKSPACE, u as normalizeGatewayTokenInput, v as summarizeExistingConfig, y as validateGatewayPasswordInput } from "./onboard-helpers-CLXJ8zp0.js";
17
+ import { a as ensureWorkspaceAndSessions, b as waitForGatewayReachable, g as resolveControlUiLinks, h as randomToken, m as probeGatewayReachable, n as applyWizardMetadata, p as printWizardHeader, s as guardCancel, t as DEFAULT_WORKSPACE, u as normalizeGatewayTokenInput, v as summarizeExistingConfig, y as validateGatewayPasswordInput } from "./onboard-helpers-7IGQ8nAM.js";
18
18
  import { t as resolveGatewayService } from "./service-e6MzlLCd.js";
19
- import { r as healthCommand } from "./health-DpE9GWGn.js";
19
+ import { r as healthCommand } from "./health-7UqIGG8U.js";
20
20
  import { t as ensureControlUiAssetsBuilt } from "./control-ui-assets-CJQ97d4u.js";
21
21
  import { n as logConfigUpdated } from "./logging-DDnP1BTp.js";
22
22
  import { n as promptAuthChoiceGrouped } from "./auth-choice-prompt-BAFfYZeR.js";
23
- import { i as applyAuthChoice, n as resolvePreferredProviderForAuthChoice } from "./auth-choice-CUvx59kv.js";
23
+ import { i as applyAuthChoice, n as resolvePreferredProviderForAuthChoice } from "./auth-choice-DQplW60s.js";
24
24
  import { a as promptDefaultModel, n as applyModelFallbacksFromSelection, o as promptModelAllowlist, r as applyPrimaryModel, t as applyModelAllowlist } from "./model-picker-Cf6UgQEh.js";
25
25
  import { t as ensureSystemdUserLingerInteractive } from "./systemd-linger-BzYk7A6M.js";
26
26
  import { a as promptCustomApiConfig } from "./onboard-custom-BcRYreNG.js";
27
27
  import { i as TAILSCALE_MISSING_BIN_NOTE_LINES, n as TAILSCALE_DOCS_LINES, r as TAILSCALE_EXPOSURE_OPTIONS, t as validateIPv4AddressInput } from "./ipv4-C_NtZ3JP.js";
28
28
  import { t as formatHealthCheckFailure } from "./health-format-Bxg84_f4.js";
29
- import { n as promptRemoteGatewayConfig } from "./onboard-remote-BYBai0w3.js";
30
- import { n as setupSkills } from "./onboard-skills-BxJlS1Bk.js";
29
+ import { n as promptRemoteGatewayConfig } from "./onboard-remote-Dr75eqOQ.js";
30
+ import { n as setupSkills } from "./onboard-skills-uO7HZwjc.js";
31
31
  import { confirm, intro, outro, select, text } from "@clack/prompts";
32
32
 
33
33
  //#region src/commands/configure.shared.ts
@@ -10,22 +10,22 @@ import { n as stylePromptMessage, r as stylePromptTitle, t as stylePromptHint }
10
10
  import { t as note$1 } from "./note-BIAQpZxi.js";
11
11
  import { t as WizardCancelledError } from "./prompts-D4yOwSll.js";
12
12
  import { t as createClackPrompter } from "./clack-prompter-BMQT37oC.js";
13
- import { r as setupChannels, t as noteChannelStatus } from "./onboard-channels-Z-Jkrk5-.js";
13
+ import { r as setupChannels, t as noteChannelStatus } from "./onboard-channels-BkR3MKMp.js";
14
14
  import { a as gatewayInstallErrorHint, i as buildGatewayInstallPlan, n as GATEWAY_DAEMON_RUNTIME_OPTIONS, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-AkgOtHR6.js";
15
- import { a as ensureWorkspaceAndSessions, b as waitForGatewayReachable, g as resolveControlUiLinks, h as randomToken, m as probeGatewayReachable, n as applyWizardMetadata, p as printWizardHeader, s as guardCancel, t as DEFAULT_WORKSPACE, u as normalizeGatewayTokenInput, v as summarizeExistingConfig, y as validateGatewayPasswordInput } from "./onboard-helpers-Dhr98nj8.js";
15
+ import { a as ensureWorkspaceAndSessions, b as waitForGatewayReachable, g as resolveControlUiLinks, h as randomToken, m as probeGatewayReachable, n as applyWizardMetadata, p as printWizardHeader, s as guardCancel, t as DEFAULT_WORKSPACE, u as normalizeGatewayTokenInput, v as summarizeExistingConfig, y as validateGatewayPasswordInput } from "./onboard-helpers-t7tI9KSA.js";
16
16
  import { t as resolveGatewayService } from "./service-DYpU1ajf.js";
17
- import { r as healthCommand } from "./health-znS85U-F.js";
17
+ import { r as healthCommand } from "./health-Dkwuq8ta.js";
18
18
  import { t as ensureControlUiAssetsBuilt } from "./control-ui-assets-Dnz69KRU.js";
19
19
  import { n as logConfigUpdated } from "./logging-Ev3fNJEG.js";
20
20
  import { n as promptAuthChoiceGrouped } from "./auth-choice-prompt-D6A5uuj3.js";
21
- import { i as applyAuthChoice, n as resolvePreferredProviderForAuthChoice } from "./auth-choice--DymOp5O.js";
21
+ import { i as applyAuthChoice, n as resolvePreferredProviderForAuthChoice } from "./auth-choice-5SLfZiQ7.js";
22
22
  import { a as promptDefaultModel, n as applyModelFallbacksFromSelection, o as promptModelAllowlist, r as applyPrimaryModel, t as applyModelAllowlist } from "./model-picker-DvP8ViSf.js";
23
23
  import { t as ensureSystemdUserLingerInteractive } from "./systemd-linger-CctFkWNE.js";
24
24
  import { a as promptCustomApiConfig } from "./onboard-custom-CfKvdcJ5.js";
25
25
  import { i as TAILSCALE_MISSING_BIN_NOTE_LINES, n as TAILSCALE_DOCS_LINES, r as TAILSCALE_EXPOSURE_OPTIONS, t as validateIPv4AddressInput } from "./ipv4-qGjACkoi.js";
26
26
  import { t as formatHealthCheckFailure } from "./health-format-6tfMqkNX.js";
27
- import { n as promptRemoteGatewayConfig } from "./onboard-remote-DZdMxd1Q.js";
28
- import { n as setupSkills } from "./onboard-skills-2SUv-W7i.js";
27
+ import { n as promptRemoteGatewayConfig } from "./onboard-remote-DH3bPYbE.js";
28
+ import { n as setupSkills } from "./onboard-skills-Bjf7kWvg.js";
29
29
  import { confirm, intro, outro, select, text } from "@clack/prompts";
30
30
 
31
31
  //#region src/commands/configure.shared.ts
@@ -909,6 +909,158 @@ body {
909
909
  line-height: 1.4;
910
910
  }
911
911
 
912
+ /* ── Stage D (3.5.26) mode chips + per-mode layout ───────────────── */
913
+ .symframe-modes {
914
+ display: flex;
915
+ align-items: center;
916
+ gap: 6px;
917
+ margin-top: 6px;
918
+ }
919
+ .mode-chip {
920
+ width: 10px;
921
+ height: 10px;
922
+ border-radius: 50%;
923
+ border: 1px solid rgba(0, 212, 255, 0.32);
924
+ background: transparent;
925
+ padding: 0;
926
+ cursor: pointer;
927
+ transition:
928
+ background 0.2s ease,
929
+ border-color 0.2s ease,
930
+ transform 0.15s ease;
931
+ flex-shrink: 0;
932
+ }
933
+ .mode-chip:hover {
934
+ border-color: var(--accent-cyan);
935
+ background: rgba(0, 212, 255, 0.15);
936
+ transform: scale(1.15);
937
+ }
938
+ .mode-chip.active {
939
+ background: var(--accent-cyan);
940
+ border-color: var(--accent-cyan);
941
+ box-shadow: 0 0 6px rgba(0, 212, 255, 0.5);
942
+ }
943
+ .mode-auto {
944
+ margin-left: auto;
945
+ padding: 2px 8px;
946
+ font-family: var(--font-mono);
947
+ font-size: 8px;
948
+ font-weight: 700;
949
+ letter-spacing: 0.12em;
950
+ color: var(--accent-cyan);
951
+ background: rgba(0, 212, 255, 0.08);
952
+ border: 1px solid rgba(0, 212, 255, 0.32);
953
+ border-radius: 8px;
954
+ cursor: pointer;
955
+ transition:
956
+ background 0.15s ease,
957
+ color 0.15s ease;
958
+ }
959
+ .mode-auto:hover {
960
+ background: rgba(0, 212, 255, 0.2);
961
+ color: var(--text);
962
+ }
963
+
964
+ /* ── Per-mode layout overrides ───────────────────────────────── */
965
+ /* Default (ambient): inherits the base .symframe grid above —
966
+ auto / 1fr / auto rows. No override needed. */
967
+
968
+ /* Focus mode: hide AWAITING; dim substrate to a soft texture; the
969
+ foreground card takes the visual stage. If a focusedId is set, the
970
+ matching card gets a cyan halo + others fade out. */
971
+ .symframe[data-mode="focus"] {
972
+ grid-template-rows: auto 1fr 0;
973
+ }
974
+ .symframe[data-mode="focus"] .symframe-awaiting {
975
+ display: none;
976
+ }
977
+ .symframe[data-mode="focus"] .symframe-substrate {
978
+ opacity: 0.2;
979
+ pointer-events: none;
980
+ filter: blur(2px);
981
+ transition:
982
+ opacity 0.4s ease,
983
+ filter 0.4s ease;
984
+ }
985
+ /* When focusedId is set, dim non-matching cards. */
986
+ .symframe[data-mode="focus"][data-focus-id] .symframe-card-dynamic {
987
+ opacity: 0.25;
988
+ transition: opacity 0.4s ease;
989
+ }
990
+ .symframe[data-mode="focus"][data-focus-id] .symframe-card-dynamic:hover {
991
+ opacity: 0.6;
992
+ }
993
+ /* The focused card gets a halo and stays sharp. Selector is built
994
+ dynamically by symframe.js via the [data-focus-id] attribute and
995
+ the card's own [data-card-id]; CSS can't directly match across
996
+ them, so we use a generic "the card whose id matches" pattern via
997
+ :has() (when supported) plus a fallback class added by JS. */
998
+ .symframe[data-mode="focus"] .symframe-card-focused {
999
+ opacity: 1 !important;
1000
+ box-shadow:
1001
+ 0 0 24px rgba(0, 212, 255, 0.35),
1002
+ inset 0 1px 0 rgba(255, 255, 255, 0.06),
1003
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1004
+ border-color: var(--accent-cyan) !important;
1005
+ transform: scale(1.02);
1006
+ transform-origin: center;
1007
+ transition:
1008
+ box-shadow 0.4s ease,
1009
+ border-color 0.4s ease,
1010
+ transform 0.4s ease;
1011
+ }
1012
+
1013
+ /* Awaiting mode: footer dominant; cards collapse to title strips;
1014
+ substrate dims to a status band. */
1015
+ .symframe[data-mode="awaiting"] {
1016
+ grid-template-rows: auto minmax(60px, 1fr) 4fr;
1017
+ }
1018
+ .symframe[data-mode="awaiting"] .symframe-substrate {
1019
+ opacity: 0.35;
1020
+ }
1021
+ .symframe[data-mode="awaiting"] .symframe-card-dynamic {
1022
+ max-height: 38px;
1023
+ overflow: hidden;
1024
+ transition:
1025
+ max-height 0.4s ease,
1026
+ opacity 0.4s ease;
1027
+ }
1028
+ .symframe[data-mode="awaiting"] .symframe-card-dynamic .symframe-card-body,
1029
+ .symframe[data-mode="awaiting"] .symframe-card-dynamic .symframe-card-actions {
1030
+ display: none;
1031
+ }
1032
+ .symframe[data-mode="awaiting"] .symframe-awaiting {
1033
+ max-height: none;
1034
+ background: rgba(0, 212, 255, 0.04);
1035
+ border-radius: 10px;
1036
+ margin-top: 8px;
1037
+ }
1038
+ .symframe[data-mode="awaiting"] .awaiting-list {
1039
+ max-height: none;
1040
+ }
1041
+
1042
+ /* Quiet mode: only the substrate breathes. Cards + AWAITING hidden.
1043
+ This is the "deep think" or "nothing to surface" posture. */
1044
+ .symframe[data-mode="quiet"] {
1045
+ grid-template-rows: auto 1fr;
1046
+ }
1047
+ .symframe[data-mode="quiet"] .symframe-cards,
1048
+ .symframe[data-mode="quiet"] .symframe-awaiting {
1049
+ display: none;
1050
+ }
1051
+
1052
+ /* Reduced-motion: drop the per-mode transitions. The data-mode
1053
+ attribute still flips; just no animated handoff between modes. */
1054
+ @media (prefers-reduced-motion: reduce) {
1055
+ .symframe[data-mode="focus"] .symframe-substrate,
1056
+ .symframe[data-mode="focus"][data-focus-id] .symframe-card-dynamic,
1057
+ .symframe[data-mode="focus"] .symframe-card-focused,
1058
+ .symframe[data-mode="awaiting"] .symframe-substrate,
1059
+ .symframe[data-mode="awaiting"] .symframe-card-dynamic {
1060
+ transition: none;
1061
+ }
1062
+ }
1063
+
912
1064
  /* The stack — substrate + cards share a single grid cell so they
913
1065
  overlap on the z-axis. Cards on top (z:1) with backdrop-filter let
914
1066
  the substrate (z:0) show through.
@@ -1504,6 +1656,165 @@ body {
1504
1656
  box-shadow: 0 0 8px rgba(0, 212, 255, 0.18);
1505
1657
  }
1506
1658
 
1659
+ /* ── Stage D.1 (3.5.27): intent-aware visual treatment ──────────── */
1660
+ /* The data-intent attribute has been on every card since Stage A but
1661
+ was metadata only. Stage D.1 makes the agent's mental categorization
1662
+ visible: each intent gets a distinct color stripe running down the
1663
+ left edge of the card + a small corner ribbon at the top-left with
1664
+ the intent label. Some intents add subtle behavior (producing
1665
+ pulses; drafted emphasizes action buttons; reference desaturates).
1666
+
1667
+ No JS changes — purely a CSS layer on top of the existing
1668
+ .symframe-card-dynamic[data-intent="..."] hook. */
1669
+
1670
+ /* Base treatment shared across all intents: the left stripe is via
1671
+ inset box-shadow so it doesn't disturb the card's border-radius.
1672
+ The ribbon is via ::before pseudo-element at the top-left corner.
1673
+ Cards without data-intent (legacy / pre-Stage-A pushes) get neither. */
1674
+
1675
+ .symframe-card-dynamic[data-intent]::before {
1676
+ content: attr(data-intent);
1677
+ position: absolute;
1678
+ top: 0;
1679
+ left: 0;
1680
+ padding: 2px 8px 2px 6px;
1681
+ font-family: var(--font-mono);
1682
+ font-size: 8px;
1683
+ font-weight: 700;
1684
+ letter-spacing: 0.14em;
1685
+ text-transform: uppercase;
1686
+ background: rgba(0, 0, 0, 0.55);
1687
+ border-radius: 10px 0 6px 0;
1688
+ border-right: 1px solid currentColor;
1689
+ border-bottom: 1px solid currentColor;
1690
+ pointer-events: none;
1691
+ z-index: 2;
1692
+ opacity: 0.85;
1693
+ }
1694
+ /* Reset the base card-padding so the ribbon hugs the corner */
1695
+ .symframe-card-dynamic[data-intent] {
1696
+ /* Slight top padding so the ribbon doesn't crowd the title */
1697
+ padding-top: 22px;
1698
+ }
1699
+
1700
+ /* holding — cyan; working memory */
1701
+ .symframe-card-dynamic[data-intent="holding"] {
1702
+ box-shadow:
1703
+ inset 3px 0 0 0 rgba(0, 212, 255, 0.65),
1704
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1705
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1706
+ }
1707
+ .symframe-card-dynamic[data-intent="holding"]::before {
1708
+ color: var(--accent-cyan);
1709
+ }
1710
+
1711
+ /* weighing — amber; an option under consideration */
1712
+ .symframe-card-dynamic[data-intent="weighing"] {
1713
+ box-shadow:
1714
+ inset 3px 0 0 0 #f5b73a,
1715
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1716
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1717
+ }
1718
+ .symframe-card-dynamic[data-intent="weighing"]::before {
1719
+ color: #f5b73a;
1720
+ }
1721
+
1722
+ /* producing — green; actively drafting, slow pulse on the stripe */
1723
+ .symframe-card-dynamic[data-intent="producing"] {
1724
+ animation: intent-producing-pulse 2.6s ease-in-out infinite;
1725
+ }
1726
+ @keyframes intent-producing-pulse {
1727
+ 0%,
1728
+ 100% {
1729
+ box-shadow:
1730
+ inset 3px 0 0 0 rgba(52, 211, 153, 0.55),
1731
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1732
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1733
+ }
1734
+ 50% {
1735
+ box-shadow:
1736
+ inset 3px 0 0 0 rgba(52, 211, 153, 1),
1737
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1738
+ 0 4px 12px rgba(0, 0, 0, 0.25),
1739
+ 0 0 14px rgba(52, 211, 153, 0.2);
1740
+ }
1741
+ }
1742
+ .symframe-card-dynamic[data-intent="producing"]::before {
1743
+ content: "● producing";
1744
+ color: #34d399;
1745
+ }
1746
+
1747
+ /* drafted — violet; finished artifact ready for user review */
1748
+ .symframe-card-dynamic[data-intent="drafted"] {
1749
+ box-shadow:
1750
+ inset 3px 0 0 0 #8b5cf6,
1751
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1752
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1753
+ }
1754
+ .symframe-card-dynamic[data-intent="drafted"]::before {
1755
+ color: #8b5cf6;
1756
+ }
1757
+ /* Emphasize action buttons on drafted cards — these cards exist to
1758
+ be acted on (Send / Copy / Open). Make the buttons more clickable. */
1759
+ .symframe-card-dynamic[data-intent="drafted"] .symframe-card-action {
1760
+ border-color: rgba(139, 92, 246, 0.55);
1761
+ color: var(--text);
1762
+ font-weight: 600;
1763
+ }
1764
+ .symframe-card-dynamic[data-intent="drafted"] .symframe-card-action:hover {
1765
+ background: rgba(139, 92, 246, 0.12);
1766
+ border-color: #8b5cf6;
1767
+ }
1768
+ .symframe-card-dynamic[data-intent="drafted"] .symframe-card-action-primary {
1769
+ background: rgba(139, 92, 246, 0.22);
1770
+ border-color: #8b5cf6;
1771
+ color: var(--text);
1772
+ }
1773
+ .symframe-card-dynamic[data-intent="drafted"] .symframe-card-action-primary:hover {
1774
+ background: rgba(139, 92, 246, 0.32);
1775
+ box-shadow: 0 0 10px rgba(139, 92, 246, 0.25);
1776
+ }
1777
+
1778
+ /* reference — muted gray; passive read-only context */
1779
+ .symframe-card-dynamic[data-intent="reference"] {
1780
+ box-shadow:
1781
+ inset 3px 0 0 0 rgba(255, 255, 255, 0.22),
1782
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1783
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1784
+ opacity: 0.88;
1785
+ filter: saturate(0.75);
1786
+ }
1787
+ .symframe-card-dynamic[data-intent="reference"]::before {
1788
+ color: var(--text-dim);
1789
+ }
1790
+
1791
+ /* awaiting — if a card is mistakenly pushed with this intent (it
1792
+ belongs in the AWAITING footer, not the foreground), flag with a
1793
+ pink stripe and a "→ footer" hint so the user/agent notices the
1794
+ miscategorization. */
1795
+ .symframe-card-dynamic[data-intent="awaiting"] {
1796
+ box-shadow:
1797
+ inset 3px 0 0 0 rgba(244, 114, 182, 0.55),
1798
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1799
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1800
+ }
1801
+ .symframe-card-dynamic[data-intent="awaiting"]::before {
1802
+ content: "awaiting → footer";
1803
+ color: rgba(244, 114, 182, 0.85);
1804
+ }
1805
+
1806
+ /* Reduced-motion: skip the producing pulse but keep the static
1807
+ green stripe so the intent is still visible. */
1808
+ @media (prefers-reduced-motion: reduce) {
1809
+ .symframe-card-dynamic[data-intent="producing"] {
1810
+ animation: none;
1811
+ box-shadow:
1812
+ inset 3px 0 0 0 rgba(52, 211, 153, 0.75),
1813
+ inset 0 1px 0 rgba(255, 255, 255, 0.04),
1814
+ 0 4px 12px rgba(0, 0, 0, 0.25);
1815
+ }
1816
+ }
1817
+
1507
1818
  /* ── Phase 4: type-specific body renderers ───────────────────────── */
1508
1819
 
1509
1820
  /* Text + markdown: inherit from .symframe-card-body. Add minimal
@@ -202,6 +202,47 @@
202
202
  <span class="reasoning-live-dot" id="reasoning-live-dot"></span>
203
203
  <span class="symframe-count" id="symframe-count"></span>
204
204
  </div>
205
+ <!-- Stage D (3.5.26): mode selector chips. Agent picks via -->
206
+ <!-- display_artifact setMode; user can override by clicking a chip. -->
207
+ <!-- The AUTO pill appears only while userOverride is active; click -->
208
+ <!-- it to release control back to the agent. -->
209
+ <div class="symframe-modes" id="symframe-modes" role="tablist" aria-label="Panel mode">
210
+ <button
211
+ type="button"
212
+ class="mode-chip"
213
+ data-mode="ambient"
214
+ role="tab"
215
+ title="Ambient — substrate + cards + awaiting visible"
216
+ aria-label="Ambient mode"
217
+ ></button>
218
+ <button
219
+ type="button"
220
+ class="mode-chip"
221
+ data-mode="focus"
222
+ role="tab"
223
+ title="Focus — one card centered, substrate dim, footer hidden"
224
+ aria-label="Focus mode"
225
+ ></button>
226
+ <button
227
+ type="button"
228
+ class="mode-chip"
229
+ data-mode="awaiting"
230
+ role="tab"
231
+ title="Awaiting — footer prominent, cards collapsed"
232
+ aria-label="Awaiting mode"
233
+ ></button>
234
+ <button
235
+ type="button"
236
+ class="mode-chip"
237
+ data-mode="quiet"
238
+ role="tab"
239
+ title="Quiet — substrate only, cards + footer hidden"
240
+ aria-label="Quiet mode"
241
+ ></button>
242
+ <button type="button" class="mode-auto" id="mode-auto" hidden title="Release to agent">
243
+ AUTO
244
+ </button>
245
+ </div>
205
246
  <div class="symframe-sub">window into the agent's mind</div>
206
247
  </div>
207
248
  <div class="symframe-stack">
@@ -987,5 +1028,6 @@
987
1028
  <script src="js/symframe.js"></script>
988
1029
  <script src="js/symframe-substrate.js"></script>
989
1030
  <script src="js/symframe-awaiting.js"></script>
1031
+ <script src="js/symframe-modes.js"></script>
990
1032
  </body>
991
1033
  </html>
@@ -0,0 +1,65 @@
1
+ // ── Symframe Mode Chips ─────────────────────────────────────────────
2
+ // Stage D of the Dynamic Glass Panel arc — see ~/Documents/dynamic-glass-panel.md §5 Stage D.
3
+ //
4
+ // Wires the four mode chips in the symframe header to
5
+ // window.symframe.setMode() and updates the AUTO pill visibility +
6
+ // active-chip indicator in response to mode changes (from either the
7
+ // user clicking a chip OR the agent calling setMode via display_artifact).
8
+ //
9
+ // Mode-vs-user resolution:
10
+ // - User click → setMode(mode, { user: true })
11
+ // → modeState.userOverride = true
12
+ // → AUTO pill becomes visible
13
+ // → Agent setMode calls are stored as pendingAgent
14
+ // but NOT applied to the DOM
15
+ // - AUTO click → clearUserOverride()
16
+ // → userOverride = false, pendingAgent is applied
17
+ // → AUTO pill hides
18
+ // ─────────────────────────────────────────────────────────────────────
19
+
20
+ "use strict";
21
+
22
+ (function () {
23
+ const modesContainer = document.getElementById("symframe-modes");
24
+ const autoPill = document.getElementById("mode-auto");
25
+ if (!modesContainer || typeof window.symframe?.setMode !== "function") {
26
+ return;
27
+ }
28
+
29
+ const chips = modesContainer.querySelectorAll(".mode-chip");
30
+
31
+ function syncChips(state) {
32
+ for (const chip of chips) {
33
+ const mode = chip.getAttribute("data-mode");
34
+ const active = mode === state.current;
35
+ chip.classList.toggle("active", active);
36
+ chip.setAttribute("aria-selected", active ? "true" : "false");
37
+ }
38
+ if (autoPill) {
39
+ autoPill.hidden = !state.userOverride;
40
+ }
41
+ }
42
+
43
+ // Initial sync from current mode state
44
+ syncChips(window.symframe.getMode());
45
+
46
+ // Listen for future mode changes (agent OR user)
47
+ window.symframe.onModeChange(syncChips);
48
+
49
+ // Wire chip clicks → user-initiated setMode
50
+ for (const chip of chips) {
51
+ chip.addEventListener("click", () => {
52
+ const mode = chip.getAttribute("data-mode");
53
+ if (mode) {
54
+ window.symframe.setMode(mode, { user: true });
55
+ }
56
+ });
57
+ }
58
+
59
+ // AUTO pill → release the override
60
+ if (autoPill) {
61
+ autoPill.addEventListener("click", () => {
62
+ window.symframe.clearUserOverride();
63
+ });
64
+ }
65
+ })();
@@ -508,6 +508,12 @@
508
508
  cards.set(id, entry);
509
509
  insertElement(entry, el);
510
510
  updateCount();
511
+ // Stage D: if focus mode is active and this is the focused card,
512
+ // apply the halo class immediately so the card renders focused
513
+ // on first paint (otherwise the user briefly sees a dim card).
514
+ if (modeState.current === "focus" && modeState.focusedId === id) {
515
+ el.classList.add("symframe-card-focused");
516
+ }
511
517
  return entry;
512
518
  }
513
519
 
@@ -548,6 +554,11 @@
548
554
  merged.element = newEl;
549
555
  existing.element.replaceWith(newEl);
550
556
  cards.set(id, merged);
557
+ // Stage D: re-apply the focus class if this is the focused card
558
+ // (replaceWith drops classes on the old element).
559
+ if (modeState.current === "focus" && modeState.focusedId === id) {
560
+ newEl.classList.add("symframe-card-focused");
561
+ }
551
562
  return merged;
552
563
  }
553
564
 
@@ -579,6 +590,104 @@
579
590
  return true;
580
591
  }
581
592
 
593
+ // ── Stage D: modes ─────────────────────────────────────────────────
594
+ // The agent picks a panel posture per task via display_artifact
595
+ // action="setMode". Mode is applied as a data-mode attribute on the
596
+ // #symframe root; per-mode CSS rules in style.css handle the layout
597
+ // shift (substrate/foreground/awaiting proportions, focused-card
598
+ // highlight). No JS layout math — all visual changes are CSS-driven.
599
+ //
600
+ // User override: clicking a mode chip in the header sets userOverride
601
+ // = true. While true, agent setMode calls are stored as
602
+ // pendingAgentMode but NOT applied to the DOM — the user's choice
603
+ // wins. An AUTO chip becomes visible; clicking it clears the
604
+ // override and applies whatever the agent last asked for.
605
+ const MODES = ["ambient", "focus", "awaiting", "quiet"];
606
+ const DEFAULT_MODE = "ambient";
607
+ /** @type {{ current: string, userOverride: boolean, pendingAgent: string | null, focusedId: string | null }} */
608
+ const modeState = {
609
+ current: DEFAULT_MODE,
610
+ userOverride: false,
611
+ pendingAgent: null,
612
+ focusedId: null,
613
+ };
614
+ /** @type {Set<(state: typeof modeState) => void>} */
615
+ const modeListeners = new Set();
616
+ const symframeRoot = document.getElementById("symframe");
617
+
618
+ function applyMode() {
619
+ if (!symframeRoot) {
620
+ return;
621
+ }
622
+ symframeRoot.setAttribute("data-mode", modeState.current);
623
+ if (modeState.focusedId) {
624
+ symframeRoot.setAttribute("data-focus-id", modeState.focusedId);
625
+ } else {
626
+ symframeRoot.removeAttribute("data-focus-id");
627
+ }
628
+ if (modeState.userOverride) {
629
+ symframeRoot.setAttribute("data-user-override", "true");
630
+ } else {
631
+ symframeRoot.removeAttribute("data-user-override");
632
+ }
633
+ for (const fn of modeListeners) {
634
+ try {
635
+ fn({ ...modeState });
636
+ } catch (err) {
637
+ console.warn("[symframe] mode listener threw:", err);
638
+ }
639
+ }
640
+ }
641
+
642
+ function setMode(nextMode, opts = {}) {
643
+ if (!MODES.includes(nextMode)) {
644
+ console.warn(`[symframe] setMode: unknown mode '${nextMode}'`);
645
+ return false;
646
+ }
647
+ const fromUser = opts.user === true;
648
+ if (fromUser) {
649
+ modeState.userOverride = true;
650
+ modeState.current = nextMode;
651
+ if (typeof opts.focusedId === "string") {
652
+ modeState.focusedId = opts.focusedId;
653
+ } else if (opts.focusedId === null) {
654
+ modeState.focusedId = null;
655
+ }
656
+ } else {
657
+ // Agent setMode: respect user override
658
+ modeState.pendingAgent = nextMode;
659
+ if (modeState.userOverride) {
660
+ return true; // ack but don't apply
661
+ }
662
+ modeState.current = nextMode;
663
+ if (typeof opts.focusedId === "string") {
664
+ modeState.focusedId = opts.focusedId;
665
+ } else if (opts.focusedId === null) {
666
+ modeState.focusedId = null;
667
+ }
668
+ }
669
+ applyMode();
670
+ return true;
671
+ }
672
+
673
+ /** Clear the user override; reapply the agent's last mode (or default). */
674
+ function clearUserOverride() {
675
+ modeState.userOverride = false;
676
+ if (modeState.pendingAgent) {
677
+ modeState.current = modeState.pendingAgent;
678
+ }
679
+ applyMode();
680
+ }
681
+
682
+ function getMode() {
683
+ return { ...modeState };
684
+ }
685
+
686
+ function onModeChange(fn) {
687
+ modeListeners.add(fn);
688
+ return () => modeListeners.delete(fn);
689
+ }
690
+
582
691
  // ── Stage C: dwell / release / recall / pin / unpin ────────────────
583
692
  // The CRUD vocabulary (add/update/remove) treats the panel as a
584
693
  // notification list. These verbs treat it as a mind-window: cards
@@ -777,6 +886,12 @@
777
886
  pin,
778
887
  unpin,
779
888
  listRecentlyReleased,
889
+ // Stage D — modes
890
+ setMode,
891
+ getMode,
892
+ clearUserOverride,
893
+ onModeChange,
894
+ MODES,
780
895
  };
781
896
 
782
897
  // Phase 3: server-pushed cards. The gateway's `symframe` broadcast is
@@ -809,9 +924,33 @@
809
924
  pin(payload.id);
810
925
  } else if (payload.action === "unpin" && payload.id) {
811
926
  unpin(payload.id);
927
+ } else if (payload.action === "setMode" && typeof payload.mode === "string") {
928
+ setMode(payload.mode, {
929
+ focusedId: typeof payload.focusedId === "string" ? payload.focusedId : null,
930
+ });
812
931
  }
813
932
  });
814
933
 
934
+ // Stage D: apply the default mode on boot so the data-mode attribute
935
+ // always exists for CSS to target. No-op if userOverride is set
936
+ // (won't happen at boot, but defensive).
937
+ applyMode();
938
+
939
+ // Stage D: when mode changes, toggle the .symframe-card-focused class
940
+ // on the card whose id matches modeState.focusedId. CSS can't select
941
+ // "the card whose data-card-id matches the symframe's data-focus-id"
942
+ // directly, so JS maintains the class. Re-applies after card add too.
943
+ function syncFocusClass() {
944
+ for (const [id, entry] of cards.entries()) {
945
+ if (!entry.element) {
946
+ continue;
947
+ }
948
+ const shouldFocus = modeState.current === "focus" && modeState.focusedId === id;
949
+ entry.element.classList.toggle("symframe-card-focused", shouldFocus);
950
+ }
951
+ }
952
+ onModeChange(syncFocusClass);
953
+
815
954
  // Stage C: restore pinned cards from localStorage on boot. Pinned
816
955
  // cards are user-curated state and survive reload, /new, and gateway
817
956
  // reconnect. Each pin is a snapshot; the registry re-adds it as a