@fased/fased 0.1.9 → 0.1.11

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 (293) hide show
  1. package/README.md +7 -5
  2. package/dist/{active-listener-BC05EmaA.js → active-listener-CNrLAfOg.js} +1 -1
  3. package/dist/{agent-scope-DVw8mi_i.js → agent-scope-T2X-I1lP.js} +124 -124
  4. package/dist/{agents-C4jz1huI.js → agents-CMJVD0OP.js} +8 -8
  5. package/dist/{attachment-normalize-C-VpVpLd.js → attachment-normalize-CJ8osx0y.js} +1 -1
  6. package/dist/{attachment-normalize-DijgHIHy.js → attachment-normalize-Dqpm6stW.js} +1 -1
  7. package/dist/{audio-preflight-DLGJBu9O.js → audio-preflight-B2FUM6w4.js} +2 -2
  8. package/dist/{audio-preflight-oqlL2Kux.js → audio-preflight-BaItcO_S.js} +31 -31
  9. package/dist/{audio-preflight-CI7BIPwS.js → audio-preflight-CMBypKf2.js} +2 -2
  10. package/dist/{audio-preflight-U5m63Clr.js → audio-preflight-Cf6hrG6Y.js} +1 -1
  11. package/dist/{audio-preflight-C6Lb4XU1.js → audio-preflight-JNkJegO7.js} +1 -1
  12. package/dist/{audit-DwbVJt3i.js → audit-BjCVaoog.js} +1 -1
  13. package/dist/{audit-BCtx-_dH.js → audit-Dm9_Y9ek.js} +1 -1
  14. package/dist/{auth-DIBzLQ-S.js → auth-CmR3RRfx.js} +5 -5
  15. package/dist/{auth-choice-DOqXUqF_.js → auth-choice-C-sID0RY.js} +1 -1
  16. package/dist/{auth-choice-CEeiz47B.js → auth-choice-DPEogMvC.js} +1 -1
  17. package/dist/{auth-choice-prompt-yqlvFI-_.js → auth-choice-prompt-Cbcp3Ee2.js} +1 -1
  18. package/dist/{auth-choice-prompt-FJDk83bS.js → auth-choice-prompt-CzCpmXZF.js} +1 -1
  19. package/dist/build-info.json +3 -3
  20. package/dist/bundled/boot-md/handler.js +31 -31
  21. package/dist/bundled/bootstrap-extra-files/handler.js +3 -3
  22. package/dist/bundled/command-logger/handler.js +2 -2
  23. package/dist/bundled/session-memory/handler.js +31 -31
  24. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  25. package/dist/{channel-activity-D2IwnDWk.js → channel-activity-C0DEUhUn.js} +1 -1
  26. package/dist/{channel-web-DaircVlX.js → channel-web-BTEzRgur.js} +1 -1
  27. package/dist/{channel-web-CBfPvCCy.js → channel-web-DBR7OGrA.js} +1 -1
  28. package/dist/{channels-DgozxTBh.js → channels-DsRKZ6NL.js} +6 -6
  29. package/dist/{channels-BygboZOV.js → channels-FmXyntMa.js} +6 -6
  30. package/dist/{channels-cli-DNz7gmOI.js → channels-cli-D3X_07NV.js} +5 -5
  31. package/dist/{channels-cli-CY4n5HcO.js → channels-cli-DvmzEcUf.js} +5 -5
  32. package/dist/{chunk-BFGwq8HC.js → chunk-Q3WlKwcl.js} +1 -1
  33. package/dist/cli/daemon-cli.js +1 -1
  34. package/dist/{cli-63C0cETT.js → cli-BvLMhUPA.js} +2 -2
  35. package/dist/{cli-CFgX3wef.js → cli-DMsf3uMe.js} +2 -2
  36. package/dist/{command-registry-DEiX9EPg.js → command-registry-DXN6Gm6U.js} +9 -9
  37. package/dist/{commands-registry-C7qLmjBS.js → commands-registry-CwvSkARW.js} +1 -1
  38. package/dist/{completion-cli-BzQetGgr.js → completion-cli-BakZ-vJj.js} +2 -2
  39. package/dist/{completion-cli-CRwAyWQL.js → completion-cli-is3s-YXN.js} +1 -1
  40. package/dist/{config-cli-CPl1vPz0.js → config-cli-CHZ9egMQ.js} +1 -1
  41. package/dist/{config-cli-C7vsh8Fg.js → config-cli-DTCrEIaK.js} +1 -1
  42. package/dist/{config-eval-CG37crOn.js → config-eval-BMGRKCyr.js} +3 -3
  43. package/dist/{configure-BEI_pe6B.js → configure-AXjfwCbC.js} +12 -12
  44. package/dist/{configure-C8lxNKpB.js → configure-t0_VefWH.js} +12 -12
  45. package/dist/control-ui/assets/app-wpJSg6bV.js.map +1 -1
  46. package/dist/{cron-cli-C7i4-Whp.js → cron-cli-BHTg4HPO.js} +2 -2
  47. package/dist/{cron-cli-BqD_ZSEC.js → cron-cli-DIedHCwY.js} +2 -2
  48. package/dist/{daemon-cli-D0AP3mOg.js → daemon-cli-DUMUVnTJ.js} +1 -1
  49. package/dist/{daemon-cli-DFYzYoB8.js → daemon-cli-lv-0kuHT.js} +1 -1
  50. package/dist/daemon-cli.js +1 -1
  51. package/dist/{deps-D9aJvXyD.js → deps-5PYELVCT.js} +1 -1
  52. package/dist/{deps-ddWLkZAO.js → deps-7Xjk7uTn.js} +1 -1
  53. package/dist/{diagnostic-DWKSfm_q.js → diagnostic-PXNTTbiu.js} +1 -1
  54. package/dist/entry.js +1 -1
  55. package/dist/extensionAPI.js +1 -1
  56. package/dist/{fetch-DyKd0D6V.js → fetch-D6yXePhv.js} +1 -1
  57. package/dist/{fetch-guard-Ztzfiio9.js → fetch-guard-B-XqKRhJ.js} +1 -1
  58. package/dist/{filesystem-manager-9uqWGvqg.js → filesystem-manager-_ccVdKHj.js} +4 -4
  59. package/dist/{fs-safe--dkmYAev.js → fs-safe-CkRcL-Cg.js} +2 -2
  60. package/dist/{gateway-cli-D_Yd_yWi.js → gateway-cli-COUUlBN5.js} +27 -27
  61. package/dist/{gateway-cli-BJkYYsGs.js → gateway-cli-a_LcL_Yi.js} +27 -27
  62. package/dist/{github-copilot-token-DYrFvncE.js → github-copilot-token-eyrRGmGp.js} +8 -8
  63. package/dist/{health-gyJixAyN.js → health-DQlfle1u.js} +2 -2
  64. package/dist/{health-DCFLGVDU.js → health-DZ82kGpi.js} +2 -2
  65. package/dist/{heartbeat-runner-BFUvc4Y_.js → heartbeat-runner-BDe0KJF7.js} +1 -1
  66. package/dist/{heartbeat-runner-Bn9N5npL.js → heartbeat-runner-C2n7G095.js} +1 -1
  67. package/dist/{hooks-cli-BGFJQWmK.js → hooks-cli-8GN0KlvM.js} +3 -3
  68. package/dist/{hooks-cli-CGj2FDai.js → hooks-cli-yMMxy-yo.js} +3 -3
  69. package/dist/index.js +7 -7
  70. package/dist/{internal-yBWuTfFC.js → internal-CtV-S2kE.js} +3 -3
  71. package/dist/{ipv4-CjO0_Ry3.js → ipv4-D1FYQ4Mk.js} +2 -2
  72. package/dist/{ipv4-CBSPFYQB.js → ipv4-DgQOUcwH.js} +2 -2
  73. package/dist/{lifecycle-BBNnNoX_.js → lifecycle-BxdJxsFv.js} +1 -1
  74. package/dist/{lifecycle-DFvIF7r0.js → lifecycle-DUQ2P6lB.js} +1 -1
  75. package/dist/{list.auth-overview-DySKd1cu.js → list.auth-overview-C9QoS2ou.js} +1 -1
  76. package/dist/{list.auth-overview-CpmtoN2D.js → list.auth-overview-CrlYcU_A.js} +1 -1
  77. package/dist/llm-slug-generator.js +31 -31
  78. package/dist/{login-DPlzYTXi.js → login-C4KKxuM4.js} +2 -2
  79. package/dist/{login-qr-c2Drd9vA.js → login-qr-DMxUP87H.js} +3 -3
  80. package/dist/{manager-XJq9ONvb.js → manager-O_PUm8xA.js} +8 -8
  81. package/dist/{markdown-tables-7kI-7mng.js → markdown-tables-QOHpjP76.js} +1 -1
  82. package/dist/{media-D4nEnyX9.js → media-KeWZMCmf.js} +5 -5
  83. package/dist/{memory-search-Z2b0d-jA.js → memory-search-BVlFBMLd.js} +2 -2
  84. package/dist/{model-catalog-DfLJlqO8.js → model-catalog-D4rEoh5e.js} +9 -9
  85. package/dist/{models-cli-DpmciZv6.js → models-cli-C8qP0w3J.js} +5 -5
  86. package/dist/{models-cli-Bu09lWlb.js → models-cli-Cq7UxTbg.js} +4 -4
  87. package/dist/{models-BbfLyMBF.js → models-jVuLblxp.js} +4 -4
  88. package/dist/{node-cli-GbiqR_FY.js → node-cli-DScZRTL7.js} +2 -2
  89. package/dist/{node-cli-D6iG4Fev.js → node-cli-o6uY0qaP.js} +2 -2
  90. package/dist/{onboard-CViA2g_j.js → onboard-Ddf5Pf9K.js} +6 -6
  91. package/dist/{onboard-channels-BeKekf-a.js → onboard-channels-D8ENQiPK.js} +2 -2
  92. package/dist/{onboard-channels-gHaSwzTd.js → onboard-channels-Doc5AjLd.js} +2 -2
  93. package/dist/{onboard-BgU-rNfl.js → onboard-feH8VdPn.js} +6 -6
  94. package/dist/{onboard-search-COuzKj6_.js → onboard-search-DuxAp1aK.js} +5 -5
  95. package/dist/{onboard-search-DkOnHZm9.js → onboard-search-GxWu-SFZ.js} +5 -5
  96. package/dist/{onboarding-nabyJljA.js → onboarding-BR7OHJ2H.js} +100 -63
  97. package/dist/{onboarding-CowfhJUD.js → onboarding-Cxkh1WwH.js} +100 -63
  98. package/dist/{openresponses-http-BBydvP41.js → openresponses-http-BL1pUdQf.js} +2 -2
  99. package/dist/{openresponses-http-D42SXipZ.js → openresponses-http-CzuT0O2I.js} +31 -31
  100. package/dist/{openresponses-http-C66CR36w.js → openresponses-http-DCHdi7mF.js} +2 -2
  101. package/dist/{openresponses-http-CdO4hFg_.js → openresponses-http-DQ3c6n1-.js} +1 -1
  102. package/dist/{openresponses-http-One5KNo8.js → openresponses-http-MJ14c-46.js} +1 -1
  103. package/dist/{outbound-attachment-BnC5ITTB.js → outbound-attachment-DO7UVxZm.js} +2 -2
  104. package/dist/{outbound-vYt3OL7q.js → outbound-nrqaUQPn.js} +6 -6
  105. package/dist/{parent-default-help-82dOtJ5b.js → parent-default-help-BKBeFc1_.js} +1 -1
  106. package/dist/{parent-default-help-iR__S_d8.js → parent-default-help-BRMZI4YX.js} +1 -1
  107. package/dist/{path-alias-guards-BgsaoH-B.js → path-alias-guards-BVcGMct5.js} +1 -1
  108. package/dist/{paths-0bvQb_LS.js → paths-C2qpRqKS.js} +22 -22
  109. package/dist/{pi-embedded-DLEbV2ey.js → pi-embedded-Bqi9f0et.js} +5 -5
  110. package/dist/{pi-embedded-BqhMzKsb.js → pi-embedded-D2PqvAO_.js} +239 -239
  111. package/dist/{pi-model-discovery-DpUv4v_r.js → pi-model-discovery-B1-QPAbn.js} +7 -7
  112. package/dist/pi-model-discovery-runtime-C3EP4IIA.js +5 -0
  113. package/dist/{plugin-registry-DgLnDYKc.js → plugin-registry-CmfUTKxt.js} +1 -1
  114. package/dist/{plugin-registry-DgnprUWu.js → plugin-registry-VukRFBIJ.js} +1 -1
  115. package/dist/plugin-sdk/{active-listener-C5ltqLGu.js → active-listener-DoYouR42.js} +1 -1
  116. package/dist/plugin-sdk/{audio-preflight-B3yRlgpY.js → audio-preflight-CVzfTzWu.js} +29 -29
  117. package/dist/plugin-sdk/{audio-preflight-bl33qm06.js → audio-preflight-CfNG6Wtw.js} +1 -1
  118. package/dist/plugin-sdk/{channel-activity-C1o4f6CQ.js → channel-activity-DO70gTOL.js} +1 -1
  119. package/dist/plugin-sdk/channel-plugin-common.js +1 -1
  120. package/dist/plugin-sdk/{channel-web-BmhttJUt.js → channel-web-DksUuYJT.js} +14 -14
  121. package/dist/plugin-sdk/{chunk-CLhP_PY2.js → chunk-RXEaIS7Q.js} +1 -1
  122. package/dist/plugin-sdk/command-status.js +1 -1
  123. package/dist/plugin-sdk/{commands-registry-rxhYCn3h.js → commands-registry-Cc-MsC0s.js} +1 -1
  124. package/dist/plugin-sdk/{diagnostic-B18Fb_x_.js → diagnostic-B49XKTb2.js} +1 -1
  125. package/dist/plugin-sdk/{fetch-IDUrNG4h.js → fetch-5yC4s4HB.js} +1 -1
  126. package/dist/plugin-sdk/{fetch-guard-CNvu0czC.js → fetch-guard-D-xE9VSj.js} +1 -1
  127. package/dist/plugin-sdk/{filesystem-manager-BeVVhJWO.js → filesystem-manager-CJboYM0S.js} +3 -3
  128. package/dist/plugin-sdk/{fs-safe-DQ3_54XI.js → fs-safe-UcucKJic.js} +2 -2
  129. package/dist/plugin-sdk/index.js +34 -34
  130. package/dist/plugin-sdk/{internal-B5WwMXvo.js → internal-BPIA30Uo.js} +1 -1
  131. package/dist/plugin-sdk/{ir-wu6kjYKs.js → ir-DChJP1Kb.js} +4 -4
  132. package/dist/plugin-sdk/{login-D4XFYPCP.js → login-Blc_5Mfx.js} +2 -2
  133. package/dist/plugin-sdk/{login-qr-B9DNJMzT.js → login-qr-BQz8yyAp.js} +2 -2
  134. package/dist/plugin-sdk/{manager-DosJYKNm.js → manager-E2u7eTdN.js} +5 -5
  135. package/dist/plugin-sdk/{markdown-tables-BPDFC99a.js → markdown-tables-nBLwWu-q.js} +1 -1
  136. package/dist/plugin-sdk/{memory-search-BRc1w0pH.js → memory-search-BMfxsZst.js} +1 -1
  137. package/dist/plugin-sdk/{openresponses-http-BkfMH5vT.js → openresponses-http-DFcst5ak.js} +29 -29
  138. package/dist/plugin-sdk/{openresponses-http-DVsmkLyj.js → openresponses-http-G9zNQzxR.js} +1 -1
  139. package/dist/plugin-sdk/{outbound-DPWNpSQ2.js → outbound-D3S8AyO5.js} +6 -6
  140. package/dist/plugin-sdk/{outbound-attachment-sc88JFtf.js → outbound-attachment-Jnv1CNZB.js} +2 -2
  141. package/dist/plugin-sdk/{path-alias-guards-DVDPpKaI.js → path-alias-guards-B4FeK1hV.js} +1 -1
  142. package/dist/plugin-sdk/{paths-DPJkB1Zo.js → paths-DfSR5y6m.js} +2 -2
  143. package/dist/plugin-sdk/{pi-model-discovery-Bt88hotH.js → pi-model-discovery-CPfojvUt.js} +1 -1
  144. package/dist/plugin-sdk/{pi-model-discovery-runtime-C_BkP4_p.js → pi-model-discovery-runtime-BtVqhePh.js} +2 -2
  145. package/dist/plugin-sdk/{pi-model-discovery-runtime-Cv_H-NDs.js → pi-model-discovery-runtime-CddkbYKA.js} +1 -1
  146. package/dist/plugin-sdk/{pw-ai-z7u9z0Gl.js → pw-ai-Cy5GL4BO.js} +4 -4
  147. package/dist/plugin-sdk/{qmd-manager-CR7ZrE4D.js → qmd-manager-DTLLD9QF.js} +4 -4
  148. package/dist/plugin-sdk/{qmd-scope-D5pfYpTx.js → qmd-scope-0JoQj00s.js} +1 -1
  149. package/dist/plugin-sdk/{query-expansion-DMIckuk2.js → query-expansion-bWgC9vRe.js} +2 -2
  150. package/dist/plugin-sdk/{registry-Dpq_OloZ.js → registry-BhjWgT6A.js} +8 -8
  151. package/dist/plugin-sdk/{replies-ChLqx77a.js → replies-BbDYqvSQ.js} +2 -2
  152. package/dist/plugin-sdk/{reply-ceu7kXX1.js → reply-DvuHwHUJ.js} +67 -67
  153. package/dist/plugin-sdk/{reply-prefix-BMlximgi.js → reply-prefix-Zqi6lY5T.js} +1 -1
  154. package/dist/plugin-sdk/{resolve-outbound-target-CzHPZvQ9.js → resolve-outbound-target-B8kZ51TY.js} +2 -2
  155. package/dist/plugin-sdk/{resolve-route-DlUpOg3_.js → resolve-route-DR2HJZkT.js} +1 -1
  156. package/dist/plugin-sdk/{runtime-x71O5s-N.js → runtime-45bZMjLH.js} +8 -8
  157. package/dist/plugin-sdk/{send-DY9n8pqC.js → send-BWTZZs_g.js} +7 -7
  158. package/dist/plugin-sdk/{send-DBVp3j7m.js → send-BtnKV_Vt.js} +5 -5
  159. package/dist/plugin-sdk/{send-DEQVHEZ4.js → send-DES1Q84z.js} +5 -5
  160. package/dist/plugin-sdk/{send-Ba0KNbhf.js → send-WNg2lRih.js} +4 -4
  161. package/dist/plugin-sdk/{send-BhMMKWOk.js → send-mGgg18UE.js} +7 -7
  162. package/dist/plugin-sdk/{session-BsRPv-eK.js → session-CgkSjr1g.js} +1 -1
  163. package/dist/plugin-sdk/{skill-commands-BctYBY9Z.js → skill-commands-mVqbX-Fs.js} +5 -5
  164. package/dist/plugin-sdk/src/brand.d.ts +2 -2
  165. package/dist/plugin-sdk/{status-BcZw5R2P.js → status-6xupAoj-.js} +5 -5
  166. package/dist/plugin-sdk/{tables-CBeKgNEN.js → tables-BHylyCNS.js} +1 -1
  167. package/dist/plugin-sdk/{target-errors-B4PsZKb0.js → target-errors-Boue6hd3.js} +2 -2
  168. package/dist/plugin-sdk/{tool-loop-detection-XlSYI5bB.js → tool-loop-detection-BxnMJJ7B.js} +1 -1
  169. package/dist/plugin-sdk/{web-DE0yQyjF.js → web-Cfri_iT8.js} +1 -1
  170. package/dist/plugin-sdk/web-Zyzl7IW6.js +40 -0
  171. package/dist/plugin-sdk/{whatsapp-actions-CSxYzpeM.js → whatsapp-actions-DJ5x-Sd_.js} +12 -12
  172. package/dist/{plugins-cli-gUow1y1s.js → plugins-cli-Bx0H7QQU.js} +5 -5
  173. package/dist/{plugins-cli-Bw8rkcLm.js → plugins-cli-HReUJLnn.js} +5 -5
  174. package/dist/{program-D7MpzAVA.js → program-BY3EZa_c.js} +7 -7
  175. package/dist/{program-context-BnMmmcLR.js → program-context-MZAw4oo8.js} +24 -24
  176. package/dist/{prompt-select-styled-DEZExK7l.js → prompt-select-styled-BasY5XsH.js} +4 -4
  177. package/dist/{prompt-select-styled-CZx9cLwR.js → prompt-select-styled-DBq1-yGB.js} +4 -4
  178. package/dist/{pw-ai-DAn5Q5Mg.js → pw-ai-D6C45ed-.js} +5 -5
  179. package/dist/{qmd-manager-BQu_lmJ6.js → qmd-manager--NffQxNh.js} +5 -5
  180. package/dist/{qmd-scope-Bv2HOfhX.js → qmd-scope-BgLcDW_Q.js} +1 -1
  181. package/dist/{query-expansion-LNmYRPvI.js → query-expansion-BW1RomSn.js} +2 -2
  182. package/dist/{register.agent-B9A7SDz_.js → register.agent-C7oqvzMO.js} +8 -8
  183. package/dist/{register.agent-DlatKWlA.js → register.agent-DPvilSbD.js} +9 -9
  184. package/dist/{register.configure-yNuOFQX6.js → register.configure-CmawJCq8.js} +12 -12
  185. package/dist/{register.configure-Tez2eNbW.js → register.configure-Jq4rlEpZ.js} +12 -12
  186. package/dist/{register.maintenance-BzVw1_e5.js → register.maintenance-DauG5FhL.js} +9 -9
  187. package/dist/{register.maintenance-Q6YI5mk9.js → register.maintenance-Db7KY5qy.js} +8 -8
  188. package/dist/{register.message-Cda69Xe6.js → register.message-B5itvC4t.js} +3 -3
  189. package/dist/{register.message-D9LYOvON.js → register.message-Cy9ynm9n.js} +3 -3
  190. package/dist/{register.onboard-BoLP06ui.js → register.onboard-BRtEN2AH.js} +14 -14
  191. package/dist/{register.onboard-D1a2rQDh.js → register.onboard-DQVkAX4S.js} +14 -14
  192. package/dist/{register.setup-DhppZhR2.js → register.setup-BJtgWkaG.js} +14 -14
  193. package/dist/{register.setup-D_qD7Gvk.js → register.setup-WqEzZMCR.js} +14 -14
  194. package/dist/{register.status-health-sessions-Csah3svc.js → register.status-health-sessions-CRLl-Xg2.js} +6 -6
  195. package/dist/{register.status-health-sessions-C0IsjCxS.js → register.status-health-sessions-CYft6R-u.js} +6 -6
  196. package/dist/{register.subclis-C4Dh6aQK.js → register.subclis-Di9x-uQJ.js} +16 -16
  197. package/dist/{replies-D0__mn7d.js → replies-lgWzXlwM.js} +2 -2
  198. package/dist/{reply-C3PsVpeQ.js → reply-CBhrmRrH.js} +9 -9
  199. package/dist/{reply-prefix-CteprwTK.js → reply-prefix-BM90m-1s.js} +1 -1
  200. package/dist/{resolve-route-Twc4mQV7.js → resolve-route-DyTOJJs4.js} +1 -1
  201. package/dist/{run-main-XXpwNNGn.js → run-main-BORzDxLY.js} +14 -14
  202. package/dist/{runtime-helper-grants-C10OJEih.js → runtime-helper-grants-Cuybmi1P.js} +1 -1
  203. package/dist/{runtime-helper-grants-BTCk3CzW.js → runtime-helper-grants-neC-6Mp_.js} +1 -1
  204. package/dist/{sandbox-cli-2MuBwFbv.js → sandbox-cli-BAfTtJI-.js} +2 -2
  205. package/dist/{sandbox-cli-Bft002rK.js → sandbox-cli-ZEYXtCXZ.js} +2 -2
  206. package/dist/{security-cli-6CmGeQ7I.js → security-cli-CFmeNE3e.js} +3 -3
  207. package/dist/{security-cli-DpQep_pt.js → security-cli-aO7V40WP.js} +3 -3
  208. package/dist/{send-BVmNzYrR.js → send-B8seeqjd.js} +18 -18
  209. package/dist/{send-BG45zdAY.js → send-BPleg3km.js} +5 -5
  210. package/dist/{send-BAtDZt7a.js → send-CAqldeiP.js} +5 -5
  211. package/dist/{send-DD6K3WP6.js → send-D63evlEI.js} +7 -7
  212. package/dist/{send-6RE7ZD_B.js → send-M0lHXvC5.js} +4 -4
  213. package/dist/{server-OidOCKoj.js → server-CU9N0zVA.js} +2 -2
  214. package/dist/{server-D62kYBqw.js → server-DtSfJaMU.js} +2 -2
  215. package/dist/{server-cron--g6K0O5V.js → server-cron-3feKZet8.js} +2 -2
  216. package/dist/{server-cron-DYKXfeLU.js → server-cron-B8nnjZ6j.js} +2 -2
  217. package/dist/{server-node-events-Csbl1Sw5.js → server-node-events-D4wjAoTI.js} +3 -3
  218. package/dist/{server-node-events-DSvH0zky.js → server-node-events-dJRsvJ1e.js} +3 -3
  219. package/dist/{session-CHL2ptKy.js → session-Cvg_ryor.js} +5 -5
  220. package/dist/{skill-commands-_zLgADck.js → skill-commands-D_QLBtje.js} +24 -24
  221. package/dist/{status-cY8_nNEN.js → status-B6xlt9hk.js} +1 -1
  222. package/dist/{status-DDWMOp5e.js → status-CKesh_zv.js} +4 -4
  223. package/dist/{status-CEqTBWFX.js → status-DsryVrZZ.js} +4 -4
  224. package/dist/{status-e52jwOFE.js → status-WM2U3wSq.js} +1 -1
  225. package/dist/{tables-Dtfop_e5.js → tables-Cif1OeQ1.js} +1 -1
  226. package/dist/{target-errors-DrHV8jza.js → target-errors-B-8Jf9OT.js} +2 -2
  227. package/dist/{tool-loop-detection-CPr-VVyD.js → tool-loop-detection-6p0-ziTi.js} +2 -2
  228. package/dist/{tui-K3OJBQvo.js → tui-C_15rImY.js} +1 -1
  229. package/dist/{tui-cli-DEHMUby1.js → tui-cli-CJ4ktGm6.js} +3 -3
  230. package/dist/{tui-cli-CNL0cM4e.js → tui-cli-Cf4QMwPG.js} +3 -3
  231. package/dist/{tui-B5xfA4bO.js → tui-m7pkF_Gw.js} +1 -1
  232. package/dist/{update-cli-Cr7BTqe1.js → update-cli-BdyzhH15.js} +9 -9
  233. package/dist/{update-cli-BqiZ9j4m.js → update-cli-CpVUWYIh.js} +10 -10
  234. package/dist/{update-runner-DcpjVgBR.js → update-runner-Cp2KspVC.js} +1 -1
  235. package/dist/{update-runner-CycjY65W.js → update-runner-polOgbQK.js} +1 -1
  236. package/dist/{web-ChgN9rvX.js → web-BK0Q-Mcx.js} +3 -3
  237. package/dist/{web-CLOmxVxX.js → web-Bd_VJKf7.js} +1 -1
  238. package/dist/{web-DhwWGdvc.js → web-CD6MuZp1.js} +35 -35
  239. package/dist/{web-D6CU81WZ.js → web-CnMZHMVF.js} +1 -1
  240. package/dist/{web-6MnkoZyD.js → web-CnS-eVr9.js} +3 -3
  241. package/dist/{web-search-providers.runtime-u6_OyUd6.js → web-search-providers.runtime-C0_Og2wh.js} +1 -1
  242. package/dist/{web-search-providers.runtime-D-uepqZ5.js → web-search-providers.runtime-kvogieHw.js} +1 -1
  243. package/dist/{whatsapp-actions-C5qguyU5.js → whatsapp-actions-BAux9gs7.js} +12 -12
  244. package/docs/install/index.md +16 -10
  245. package/docs/install/installer.md +18 -9
  246. package/docs/install/node.md +5 -4
  247. package/docs/install/vps.md +7 -3
  248. package/extensions/acpx/package.json +1 -1
  249. package/extensions/bluebubbles/package.json +1 -1
  250. package/extensions/copilot-proxy/package.json +1 -1
  251. package/extensions/diagnostics-otel/package.json +1 -1
  252. package/extensions/discord/package.json +1 -1
  253. package/extensions/fased-federation/package.json +1 -1
  254. package/extensions/feishu/package.json +1 -1
  255. package/extensions/google-gemini-cli-auth/package.json +1 -1
  256. package/extensions/googlechat/package.json +1 -1
  257. package/extensions/imessage/package.json +1 -1
  258. package/extensions/irc/package.json +1 -1
  259. package/extensions/line/package.json +1 -1
  260. package/extensions/llm-task/package.json +1 -1
  261. package/extensions/lobster/package.json +1 -1
  262. package/extensions/matrix/CHANGELOG.md +6 -0
  263. package/extensions/matrix/package.json +1 -1
  264. package/extensions/mattermost/package.json +1 -1
  265. package/extensions/memory-core/package.json +1 -1
  266. package/extensions/memory-lancedb/package.json +1 -1
  267. package/extensions/minimax-portal-auth/package.json +1 -1
  268. package/extensions/msteams/CHANGELOG.md +6 -0
  269. package/extensions/msteams/package.json +1 -1
  270. package/extensions/nextcloud-talk/package.json +1 -1
  271. package/extensions/nostr/CHANGELOG.md +6 -0
  272. package/extensions/nostr/package.json +1 -1
  273. package/extensions/open-prose/package.json +1 -1
  274. package/extensions/sat-mining/package.json +1 -1
  275. package/extensions/signal/package.json +1 -1
  276. package/extensions/slack/package.json +1 -1
  277. package/extensions/synology-chat/package.json +1 -1
  278. package/extensions/telegram/package.json +1 -1
  279. package/extensions/tlon/package.json +1 -1
  280. package/extensions/twitch/CHANGELOG.md +6 -0
  281. package/extensions/twitch/package.json +1 -1
  282. package/extensions/voice-call/CHANGELOG.md +6 -0
  283. package/extensions/voice-call/package.json +1 -1
  284. package/extensions/whatsapp/package.json +1 -1
  285. package/extensions/zalo/CHANGELOG.md +6 -0
  286. package/extensions/zalo/package.json +1 -1
  287. package/extensions/zalouser/CHANGELOG.md +6 -0
  288. package/extensions/zalouser/package.json +1 -1
  289. package/package.json +3 -1
  290. package/scripts/run-node.mjs +294 -0
  291. package/scripts/start-managed.sh +1551 -0
  292. package/dist/pi-model-discovery-runtime-9_2G9r_K.js +0 -5
  293. package/dist/plugin-sdk/web-D_ze88LT.js +0 -40
@@ -0,0 +1,1551 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/start-managed.sh
3
+ # Starts the FasedAgent in managed public runtime mode.
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ FASED_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
6
+ INSTALL_MARKER_PATH="${FASED_CONFIG_DIR:-$HOME/.fased}/install-complete.json"
7
+
8
+ runtime_root_has_build() {
9
+ local root="$1"
10
+ [[ -n "$root" ]] || return 1
11
+ [[ -f "$root/scripts/run-node.mjs" ]] || return 1
12
+ [[ -f "$root/dist/index.js" || -f "$root/dist/index.mjs" || -f "$root/dist/entry.js" || -f "$root/dist/entry.mjs" ]]
13
+ }
14
+
15
+ resolve_managed_runtime_root() {
16
+ local root
17
+ for root in \
18
+ "${FASED_MANAGED_RUNTIME_ROOT:-}" \
19
+ "$FASED_ROOT" \
20
+ "$HOME/.fased/install-cache/npm-global/lib/node_modules/@fased/fased"; do
21
+ if runtime_root_has_build "$root"; then
22
+ printf '%s\n' "$root"
23
+ return 0
24
+ fi
25
+ done
26
+ printf '%s\n' "$FASED_ROOT"
27
+ }
28
+
29
+ FASED_RUNTIME_ROOT="$(resolve_managed_runtime_root)"
30
+ RUN_NODE_SCRIPT="$FASED_RUNTIME_ROOT/scripts/run-node.mjs"
31
+
32
+ node_runtime_ok_for() {
33
+ local node_bin="$1"
34
+ [[ -n "$node_bin" && -x "$node_bin" ]] || return 1
35
+ "$node_bin" -e 'const [major, minor] = process.versions.node.split(".").map(Number); if (major < 22 || (major === 22 && minor < 14)) process.exit(2); try { require("node:sqlite"); } catch { process.exit(3); }' >/dev/null 2>&1
36
+ }
37
+
38
+ resolve_node_bin() {
39
+ if node_runtime_ok_for "${FASED_NODE_BIN:-}"; then
40
+ printf '%s\n' "${FASED_NODE_BIN}"
41
+ return 0
42
+ fi
43
+
44
+ local candidate
45
+ for candidate in \
46
+ "$HOME"/.nvm/versions/node/*/bin/node \
47
+ "$HOME"/.fnm/node-versions/*/installation/bin/node \
48
+ "$HOME"/.volta/bin/node \
49
+ "$HOME"/.asdf/shims/node \
50
+ "$HOME"/.local/share/mise/shims/node \
51
+ /usr/bin/node \
52
+ /usr/local/bin/node \
53
+ /opt/homebrew/bin/node; do
54
+ [[ -e "$candidate" ]] || continue
55
+ if node_runtime_ok_for "$candidate"; then
56
+ printf '%s\n' "$candidate"
57
+ return 0
58
+ fi
59
+ done
60
+
61
+ if command -v node >/dev/null 2>&1 && node_runtime_ok_for "$(command -v node)"; then
62
+ command -v node
63
+ return 0
64
+ fi
65
+ return 1
66
+ }
67
+
68
+ if [[ "${FASED_MANAGED_INTERNAL:-0}" != "1" ]]; then
69
+ if [[ ! -f "$INSTALL_MARKER_PATH" ]]; then
70
+ echo "==> Canonical onboarding not detected ($INSTALL_MARKER_PATH missing)."
71
+ echo "==> Recommended first run: $FASED_ROOT/install.sh"
72
+ if [[ "${FASED_AUTO_INSTALL_ON_START:-0}" == "1" ]]; then
73
+ echo "==> Auto-bootstrap enabled (FASED_AUTO_INSTALL_ON_START=1): running installer in no-start mode..."
74
+ "$FASED_ROOT/install.sh" --no-start
75
+ fi
76
+ fi
77
+ echo "==> Delegating to managed orchestrator (node $RUN_NODE_SCRIPT managed up)..."
78
+ NODE_BIN="$(resolve_node_bin || true)"
79
+ if [[ -z "$NODE_BIN" ]]; then
80
+ echo "==> ERROR: compatible node binary not found. Install Node 24 or Node >=22.14.0 with node:sqlite."
81
+ exit 1
82
+ fi
83
+ export PATH="$(dirname "$NODE_BIN"):$PATH"
84
+ FASED_SKIP_BUILD="${FASED_SKIP_BUILD:-1}" exec "$NODE_BIN" "$RUN_NODE_SCRIPT" managed up "$@"
85
+ fi
86
+
87
+ set -euo pipefail
88
+
89
+ NODE_BIN="$(resolve_node_bin || true)"
90
+ if [[ -z "$NODE_BIN" ]]; then
91
+ echo "[managed] ERROR: compatible node binary not found. Install Node 24 or Node >=22.14.0 with node:sqlite, then reinstall or restart the gateway service."
92
+ exit 1
93
+ fi
94
+
95
+ # Config
96
+ export PATH="$(dirname "$NODE_BIN"):$HOME/.zrok/bin:$PATH"
97
+ export FASED_GATEWAY_MODE=managed
98
+ export FASED_GATEWAY_FAST_START="${FASED_GATEWAY_FAST_START:-1}"
99
+ export FASED_GATEWAY_STARTUP_TRACE="${FASED_GATEWAY_STARTUP_TRACE:-1}"
100
+ export FASED_DISABLE_CONTROL_UI_AUTOBUILD="${FASED_DISABLE_CONTROL_UI_AUTOBUILD:-1}"
101
+ export FASED_FEDERATION_URL=https://ff1.fased.app
102
+ export FASED_GATEWAY_PORT="${FASED_GATEWAY_PORT:-18789}"
103
+ export FASED_CONFIG_DIR="${FASED_CONFIG_DIR:-$HOME/.fased}"
104
+ FASED_GATEWAY_MAX_OLD_SPACE_MB="${FASED_GATEWAY_MAX_OLD_SPACE_MB:-1024}"
105
+ if [[ "${NODE_OPTIONS:-}" != *"--max-old-space-size="* ]]; then
106
+ export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--max-old-space-size=${FASED_GATEWAY_MAX_OLD_SPACE_MB}"
107
+ fi
108
+ TOKEN_PATH="$FASED_CONFIG_DIR/federation/access-token.json"
109
+ GW_TOKEN_PATH="$FASED_CONFIG_DIR/gateway-secret"
110
+ INITIAL_TOKEN_SIG=""
111
+ LOG_DIR="${FASED_LOG_DIR:-$FASED_CONFIG_DIR/logs}"
112
+ GATEWAY_BOOT_LOG="$LOG_DIR/start-managed-gateway.log"
113
+ ZROK_RUNTIME_LOG="$LOG_DIR/start-managed-zrok.log"
114
+ WALLET_SETUP_LOG="$LOG_DIR/start-managed-wallet.log"
115
+ VERBOSE_STARTUP="${FASED_VERBOSE_STARTUP:-0}"
116
+ # First boots on small VPS can take several minutes (control-ui asset prep, cold fs/cache, etc).
117
+ # Keep timeout generous to avoid restart loops that kill startup progress before listener comes up.
118
+ GATEWAY_READY_TIMEOUT="${FASED_GATEWAY_READY_TIMEOUT:-600}"
119
+ WALLET_BASELINE_MODE="${FASED_WALLET_BASELINE_MODE:-enforce}" # enforce|skip
120
+ WALLET_BASELINE_TIMEOUT_SECONDS="${FASED_WALLET_BASELINE_TIMEOUT_SECONDS:-25}"
121
+ SIGNERD_READY_TIMEOUT_SECONDS="${FASED_SIGNERD_READY_TIMEOUT_SECONDS:-10}"
122
+ ZROK_MONITOR_PID_FILE="$FASED_CONFIG_DIR/.zrok-monitor.pid"
123
+ ZROK_MONITOR_PID=""
124
+ ZROK_INITIAL_START_RETRIES="${FASED_ZROK_INITIAL_START_RETRIES:-5}"
125
+ ZROK_INITIAL_START_RETRY_DELAY_SECONDS="${FASED_ZROK_INITIAL_START_RETRY_DELAY_SECONDS:-2}"
126
+ CLOCK_SYNC_SKEW_THRESHOLD_SECONDS="${FASED_CLOCK_SYNC_SKEW_THRESHOLD_SECONDS:-2}"
127
+
128
+ resolve_gateway_cli_entry() {
129
+ local candidates=(
130
+ "$FASED_RUNTIME_ROOT/dist/index.js"
131
+ "$FASED_RUNTIME_ROOT/dist/index.mjs"
132
+ "$FASED_RUNTIME_ROOT/dist/entry.js"
133
+ "$FASED_RUNTIME_ROOT/dist/entry.mjs"
134
+ )
135
+ local candidate
136
+ for candidate in "${candidates[@]}"; do
137
+ if [[ -f "$candidate" ]]; then
138
+ printf "%s" "$candidate"
139
+ return 0
140
+ fi
141
+ done
142
+ return 1
143
+ }
144
+
145
+ mask_secret() {
146
+ local raw="$1"
147
+ local n=${#raw}
148
+ if [[ $n -le 10 ]]; then
149
+ printf "%s" "$raw"
150
+ return
151
+ fi
152
+ printf "%s...%s" "${raw:0:6}" "${raw: -4}"
153
+ }
154
+
155
+ is_gateway_listener_ready() {
156
+ (echo >"/dev/tcp/127.0.0.1/${FASED_GATEWAY_PORT}") >/dev/null 2>&1
157
+ }
158
+
159
+ abs_int() {
160
+ local value="${1:-0}"
161
+ value="${value#-}"
162
+ if [[ -z "$value" ]]; then
163
+ value="0"
164
+ fi
165
+ printf '%s\n' "$value"
166
+ }
167
+
168
+ can_run_privileged_time_sync_command() {
169
+ if [[ "$(id -u)" -eq 0 ]]; then
170
+ return 0
171
+ fi
172
+ command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1
173
+ }
174
+
175
+ run_privileged_time_sync_command() {
176
+ if [[ "$(id -u)" -eq 0 ]]; then
177
+ "$@"
178
+ return $?
179
+ fi
180
+ sudo -n "$@"
181
+ }
182
+
183
+ fetch_remote_epoch_from_http_date() {
184
+ local url="$1"
185
+ local date_header=""
186
+ local remote_epoch=""
187
+ date_header="$(
188
+ curl -fsSI --max-time 10 "$url" 2>/dev/null \
189
+ | tr -d '\r' \
190
+ | awk 'BEGIN{IGNORECASE=1} /^Date:/ { sub(/^Date:[[:space:]]*/, "", $0); print; exit }'
191
+ )"
192
+ if [[ -z "$date_header" ]]; then
193
+ return 1
194
+ fi
195
+ remote_epoch="$(date -u -d "$date_header" +%s 2>/dev/null || true)"
196
+ if [[ -z "$remote_epoch" ]]; then
197
+ return 1
198
+ fi
199
+ printf '%s\n' "$remote_epoch"
200
+ }
201
+
202
+ measure_managed_clock_skew_seconds() {
203
+ local probe_urls=(
204
+ "${ZROK_API_ENDPOINT:-https://zrok.fased.app}"
205
+ "${FASED_FEDERATION_URL:-https://ff1.fased.app}"
206
+ )
207
+ local url=""
208
+ local remote_epoch=""
209
+ local local_epoch=""
210
+ for url in "${probe_urls[@]}"; do
211
+ remote_epoch="$(fetch_remote_epoch_from_http_date "$url" || true)"
212
+ if [[ -z "$remote_epoch" ]]; then
213
+ continue
214
+ fi
215
+ local_epoch="$(date -u +%s)"
216
+ printf '%s\n' "$((remote_epoch - local_epoch))"
217
+ return 0
218
+ done
219
+ return 1
220
+ }
221
+
222
+ zrok_log_indicates_clock_skew() {
223
+ tail -n 80 "$ZROK_RUNTIME_LOG" 2>/dev/null | grep -Fqi "issuedAt of token is in the future"
224
+ }
225
+
226
+ attempt_managed_clock_sync_repair() {
227
+ if ! can_run_privileged_time_sync_command; then
228
+ return 1
229
+ fi
230
+ echo "[tunnel] Attempting automatic host clock sync repair..."
231
+ run_privileged_time_sync_command timedatectl set-ntp true >/dev/null 2>&1 || true
232
+ run_privileged_time_sync_command systemctl restart systemd-timesyncd >/dev/null 2>&1 || \
233
+ run_privileged_time_sync_command systemctl restart chronyd >/dev/null 2>&1 || true
234
+ if command -v chronyc >/dev/null 2>&1; then
235
+ run_privileged_time_sync_command chronyc -a makestep >/dev/null 2>&1 || true
236
+ fi
237
+ sleep 2
238
+ }
239
+
240
+ ensure_managed_clock_sync() {
241
+ local threshold="${CLOCK_SYNC_SKEW_THRESHOLD_SECONDS:-2}"
242
+ local skew_seconds=""
243
+ local remaining_skew=""
244
+
245
+ skew_seconds="$(measure_managed_clock_skew_seconds || true)"
246
+ if [[ -z "$skew_seconds" ]]; then
247
+ return 0
248
+ fi
249
+ if (( $(abs_int "$skew_seconds") < threshold )); then
250
+ return 0
251
+ fi
252
+
253
+ echo "[tunnel] WARNING: Host clock skew detected (${skew_seconds}s versus public control plane)."
254
+ if ! attempt_managed_clock_sync_repair; then
255
+ echo "[tunnel] WARNING: Automatic host clock repair requires root or passwordless sudo."
256
+ return 1
257
+ fi
258
+
259
+ remaining_skew="$(measure_managed_clock_skew_seconds || true)"
260
+ if [[ -n "$remaining_skew" ]] && (( $(abs_int "$remaining_skew") < threshold )); then
261
+ echo "[tunnel] Host clock sync repaired (remaining skew ${remaining_skew}s)."
262
+ return 0
263
+ fi
264
+
265
+ echo "[tunnel] WARNING: Host clock repair did not fully converge (remaining skew ${remaining_skew:-unknown}s)."
266
+ return 1
267
+ }
268
+
269
+ start_initial_zrok_share() {
270
+ local slug="$1"
271
+ local attempts="${ZROK_INITIAL_START_RETRIES:-5}"
272
+ local retry_delay="${ZROK_INITIAL_START_RETRY_DELAY_SECONDS:-2}"
273
+ local attempt=1
274
+
275
+ while (( attempt <= attempts )); do
276
+ echo "[tunnel] Starting tunnel for ${slug} (attempt ${attempt}/${attempts})..."
277
+ "$ZROK_BIN" share reserved "$RES_TOKEN" --headless >>"$ZROK_RUNTIME_LOG" 2>&1 &
278
+ ZROK_PID=$!
279
+ echo "$ZROK_PID" > "$FASED_CONFIG_DIR/.zrok-pid"
280
+ sleep 5
281
+
282
+ if ! kill -0 "$AGENT_PID" 2>/dev/null; then
283
+ if is_gateway_listener_ready; then
284
+ echo "[gateway] WARNING: Gateway launcher exited, but listener is healthy on 127.0.0.1:${FASED_GATEWAY_PORT}; continuing."
285
+ else
286
+ echo "[gateway] ERROR: Gateway process exited during tunnel startup."
287
+ if [[ "$VERBOSE_STARTUP" != "1" ]]; then
288
+ echo "[debug] Last gateway startup logs:"
289
+ tail -n 80 "$GATEWAY_BOOT_LOG" || true
290
+ fi
291
+ return 1
292
+ fi
293
+ fi
294
+
295
+ if ! is_gateway_listener_ready; then
296
+ echo "[gateway] ERROR: Gateway listener on 127.0.0.1:${FASED_GATEWAY_PORT} is not reachable."
297
+ if [[ "$VERBOSE_STARTUP" != "1" ]]; then
298
+ echo "[debug] Last gateway startup logs:"
299
+ tail -n 80 "$GATEWAY_BOOT_LOG" || true
300
+ fi
301
+ return 1
302
+ fi
303
+
304
+ if kill -0 "$ZROK_PID" 2>/dev/null; then
305
+ echo "[tunnel] ✓ Tunnel active."
306
+ return 0
307
+ fi
308
+
309
+ echo "[tunnel] WARNING: zrok share died during startup."
310
+ if [[ "$VERBOSE_STARTUP" != "1" ]]; then
311
+ echo "[debug] Last zrok startup logs:"
312
+ tail -n 40 "$ZROK_RUNTIME_LOG" || true
313
+ fi
314
+ if zrok_log_indicates_clock_skew; then
315
+ echo "[tunnel] Detected zrok auth failure caused by host clock skew."
316
+ ensure_managed_clock_sync || true
317
+ fi
318
+
319
+ if (( attempt == attempts )); then
320
+ echo "[tunnel] ERROR: zrok share failed to stay up after ${attempts} attempts."
321
+ return 1
322
+ fi
323
+
324
+ echo "[tunnel] Retrying zrok startup in ${retry_delay}s..."
325
+ sleep "$retry_delay"
326
+ if [[ "$retry_delay" =~ ^[0-9]+$ ]] && (( retry_delay < 8 )); then
327
+ retry_delay=$((retry_delay * 2))
328
+ fi
329
+ attempt=$((attempt + 1))
330
+ done
331
+
332
+ return 1
333
+ }
334
+
335
+ force_stop_local_gateway() {
336
+ if ! is_gateway_listener_ready; then
337
+ return 0
338
+ fi
339
+ if command -v fuser >/dev/null 2>&1; then
340
+ fuser -k "${FASED_GATEWAY_PORT}/tcp" >/dev/null 2>&1 || true
341
+ fi
342
+ if command -v lsof >/dev/null 2>&1; then
343
+ mapfile -t PORT_PIDS < <(lsof -t -iTCP:"${FASED_GATEWAY_PORT}" -sTCP:LISTEN 2>/dev/null || true)
344
+ if [[ ${#PORT_PIDS[@]} -gt 0 ]]; then
345
+ kill "${PORT_PIDS[@]}" >/dev/null 2>&1 || true
346
+ fi
347
+ fi
348
+ pkill -f "scripts/run-node.mjs gateway" >/dev/null 2>&1 || true
349
+ pkill -f "fased-gateway" >/dev/null 2>&1 || true
350
+ }
351
+
352
+ wait_for_gateway_listener() {
353
+ local retries="$1"
354
+ local count=0
355
+ echo "==> Waiting for local gateway listener on 127.0.0.1:${FASED_GATEWAY_PORT} (max ${retries}s)..."
356
+ while true; do
357
+ if is_gateway_listener_ready; then
358
+ return 0
359
+ fi
360
+ if ! kill -0 "$AGENT_PID" 2>/dev/null; then
361
+ echo "[gateway] ERROR: Gateway process exited before listener became ready."
362
+ if [[ "$VERBOSE_STARTUP" != "1" ]]; then
363
+ echo "[debug] Last gateway startup logs:"
364
+ tail -n 80 "$GATEWAY_BOOT_LOG" || true
365
+ fi
366
+ return 1
367
+ fi
368
+ sleep 1
369
+ count=$((count + 1))
370
+ if [[ $count -ge $retries ]]; then
371
+ echo "[gateway] ERROR: Timed out waiting for local listener on port ${FASED_GATEWAY_PORT}."
372
+ if [[ "$VERBOSE_STARTUP" != "1" ]]; then
373
+ echo "[debug] Last gateway startup logs:"
374
+ tail -n 80 "$GATEWAY_BOOT_LOG" || true
375
+ fi
376
+ return 1
377
+ fi
378
+ done
379
+ }
380
+
381
+ start_gateway_if_needed() {
382
+ if [[ -n "${AGENT_PID:-}" ]] && kill -0 "$AGENT_PID" 2>/dev/null; then
383
+ if is_gateway_listener_ready; then
384
+ return 0
385
+ fi
386
+ fi
387
+
388
+ echo "==> Starting FasedAgent Gateway..."
389
+ # Capture pre-existing token fingerprint so we can detect a real refresh.
390
+ if [[ -f "$TOKEN_PATH" ]]; then
391
+ INITIAL_TOKEN_SIG=$(sha256sum "$TOKEN_PATH" | awk '{print $1}')
392
+ fi
393
+ GATEWAY_ENTRY="$(resolve_gateway_cli_entry || true)"
394
+ if [[ -z "$GATEWAY_ENTRY" ]]; then
395
+ echo "[gateway] ERROR: Unable to resolve built gateway entry under $FASED_ROOT/dist"
396
+ exit 1
397
+ fi
398
+ if [[ "$VERBOSE_STARTUP" == "1" ]]; then
399
+ FASED_SKIP_BUILD=1 "$NODE_BIN" "$GATEWAY_ENTRY" gateway --allow-unconfigured --force --bind loopback --port "$FASED_GATEWAY_PORT" &
400
+ else
401
+ : > "$GATEWAY_BOOT_LOG"
402
+ FASED_SKIP_BUILD=1 "$NODE_BIN" "$GATEWAY_ENTRY" gateway --allow-unconfigured --force --bind loopback --port "$FASED_GATEWAY_PORT" >>"$GATEWAY_BOOT_LOG" 2>&1 &
403
+ fi
404
+ AGENT_PID=$!
405
+
406
+ if ! wait_for_gateway_listener "$GATEWAY_READY_TIMEOUT"; then
407
+ exit 1
408
+ fi
409
+ }
410
+
411
+ # Ensure Gateway Token exists
412
+ mkdir -p "$FASED_CONFIG_DIR"
413
+ mkdir -p "$LOG_DIR"
414
+ : > "$ZROK_RUNTIME_LOG"
415
+ : > "$WALLET_SETUP_LOG"
416
+ SERVICE_GATEWAY_TOKEN="${FASED_GATEWAY_TOKEN:-}"
417
+ if [ ! -f "$GW_TOKEN_PATH" ]; then
418
+ if [[ -n "$SERVICE_GATEWAY_TOKEN" ]]; then
419
+ printf '%s\n' "$SERVICE_GATEWAY_TOKEN" > "$GW_TOKEN_PATH"
420
+ else
421
+ openssl rand -hex 32 > "$GW_TOKEN_PATH"
422
+ fi
423
+ fi
424
+ export FASED_GATEWAY_TOKEN="$(tr -d '\n' < "$GW_TOKEN_PATH")"
425
+
426
+ # 0. Clean up stale state
427
+ # rm -f "$TOKEN_PATH" # (Disabled: Preserve identity for stable Zrok tunnels)
428
+ # Note: --force on gateway start will handle the port conflict,
429
+ # but we still kill zrok to ensure no tunnel conflicts.
430
+ if [[ "${FASED_MANAGED_INTERNAL:-0}" != "1" ]]; then
431
+ FASED_SKIP_BUILD=1 "$NODE_BIN" "$RUN_NODE_SCRIPT" gateway stop >/dev/null 2>&1 || true
432
+ fi
433
+ force_stop_local_gateway
434
+
435
+ # Start the dashboard backend before slower hosted setup. Signer, wallet,
436
+ # federation, and tunnel work must not prevent the owner from opening the UI.
437
+ start_gateway_if_needed
438
+
439
+ # 0a. Start local key signer daemon (fased-signerd) if available
440
+ SIGNERD_BIN="${FASED_CONFIG_DIR}/bin/fased-signerd"
441
+ SIGNERD_SOCKET="${FASED_CONFIG_DIR}/wallet/local-signer.sock"
442
+ SIGNERD_BACKEND_SOCKET="$SIGNERD_SOCKET"
443
+ SIGNERD_MATERIAL_DIR="${FASED_CONFIG_DIR}/wallet"
444
+ SIGNERD_PASSPHRASE_FILE="$SIGNERD_MATERIAL_DIR/passphrase"
445
+ SIGNERD_EVM_KEYSTORE="$SIGNERD_MATERIAL_DIR/keystore-evm.v1.enc"
446
+ SIGNERD_SOL_KEYSTORE="$SIGNERD_MATERIAL_DIR/keystore-solana.v1.enc"
447
+ SIGNERD_LOG="${LOG_DIR}/fased-signerd.log"
448
+ SIGNERD_BROKER_LOG="${LOG_DIR}/local-signer-broker.log"
449
+ SIGNER_ISOLATION_HELPER="/usr/local/sbin/fased-signer-isolation"
450
+ CONFIG_JSON="${FASED_CONFIG_DIR}/fased.json"
451
+ SIGNERD_ENV_FILE="${FASED_CONFIG_DIR}/wallet/signer.env"
452
+ WALLET_REGISTRY_JSON="${FASED_CONFIG_DIR}/wallet/provider-registry.v1.json"
453
+ SIGNERD_STARTUP_MODE="disabled"
454
+ SIGNERD_ERROR=""
455
+
456
+ mark_signerd_degraded() {
457
+ SIGNERD_STARTUP_MODE="degraded"
458
+ SIGNERD_ERROR="$1"
459
+ echo "[signerd] WARNING: $1"
460
+ echo "[signerd] Dashboard stays online; wallet actions remain degraded until signer is fixed."
461
+ }
462
+
463
+ load_wallet_signer_env_from_config() {
464
+ if [[ ! -f "$CONFIG_JSON" ]]; then
465
+ return 0
466
+ fi
467
+ if ! command -v jq >/dev/null 2>&1; then
468
+ return 0
469
+ fi
470
+ while IFS= read -r line; do
471
+ [[ "$line" == *=* ]] || continue
472
+ local key="${line%%=*}"
473
+ local value="${line#*=}"
474
+ if [[ "$key" =~ ^FASED_WALLET_(EVM|SOLANA)_(RPC_URL|KEYSTORE_PATH)(__[A-Za-z0-9_-]+)?$ ]] \
475
+ || [[ "$key" == "FASED_WALLET_CHAINS" ]] \
476
+ || [[ "$key" == "FASED_WALLET_PASSPHRASE_FILE" ]] \
477
+ || [[ "$key" == "FASED_WALLET_LOCAL_SIGNER_SOCKET" ]] \
478
+ || [[ "$key" == "FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET" ]] \
479
+ || [[ "$key" == "FASED_WALLET_SIGNER_STATE_DIR" ]] \
480
+ || [[ "$key" == "FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER" ]] \
481
+ || [[ "$key" == "FASED_WALLET_LOCAL_SIGNER_BIN" ]] \
482
+ || [[ "$key" =~ ^FASED_WALLET_LOCAL_SIGNER_(ROLE|DIRECT_SIGNING|CAPS_ENABLED|SOLANA_MAX_PER_TX|SOLANA_MAX_DAILY|SOLANA_ALLOW_PROGRAMS)(__[A-Za-z0-9_-]+)?$ ]]; then
483
+ export "$key=$value"
484
+ fi
485
+ done < <(
486
+ jq -r '
487
+ .env.vars // {}
488
+ | to_entries[]
489
+ | select(
490
+ .key
491
+ | test("^FASED_WALLET_(EVM|SOLANA)_(RPC_URL|KEYSTORE_PATH)(__[A-Za-z0-9_-]+)?$")
492
+ or . == "FASED_WALLET_CHAINS"
493
+ or . == "FASED_WALLET_PASSPHRASE_FILE"
494
+ or . == "FASED_WALLET_LOCAL_SIGNER_SOCKET"
495
+ or . == "FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET"
496
+ or . == "FASED_WALLET_SIGNER_STATE_DIR"
497
+ or . == "FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER"
498
+ or . == "FASED_WALLET_LOCAL_SIGNER_BIN"
499
+ or test("^FASED_WALLET_LOCAL_SIGNER_(ROLE|DIRECT_SIGNING|CAPS_ENABLED|SOLANA_MAX_PER_TX|SOLANA_MAX_DAILY|SOLANA_ALLOW_PROGRAMS)(__[A-Za-z0-9_-]+)?$")
500
+ )
501
+ | "\(.key)=\(.value|tostring)"
502
+ ' "$CONFIG_JSON" 2>/dev/null || true
503
+ )
504
+ }
505
+
506
+ load_wallet_signer_env_file() {
507
+ if [[ ! -f "$SIGNERD_ENV_FILE" ]]; then
508
+ return 0
509
+ fi
510
+
511
+ set -a
512
+ # signer.env is generated by onboarding.wallet.ts; only import export lines, not the launch command.
513
+ source <(grep -E '^export FASED_WALLET_' "$SIGNERD_ENV_FILE" || true)
514
+ set +a
515
+ }
516
+
517
+ has_scoped_wallet_env_value() {
518
+ local prefix="$1"
519
+ local name=""
520
+ while IFS= read -r name; do
521
+ [[ -n "$name" ]] || continue
522
+ if [[ -n "${!name:-}" ]]; then
523
+ return 0
524
+ fi
525
+ done < <(compgen -A variable "${prefix}__" || true)
526
+ return 1
527
+ }
528
+
529
+ normalize_wallet_env_suffix() {
530
+ local raw="${1:-}"
531
+ printf '%s' "$raw" \
532
+ | tr '[:upper:]' '[:lower:]' \
533
+ | sed -E 's/[^a-z0-9]+/_/g; s/^_+//; s/_+$//'
534
+ }
535
+
536
+ normalize_wallet_filename_id() {
537
+ local raw="${1:-}"
538
+ printf '%s' "$raw" \
539
+ | tr '[:upper:]' '[:lower:]' \
540
+ | sed -E 's/[^a-z0-9_-]+/-/g; s/^-+//; s/-+$//'
541
+ }
542
+
543
+ hydrate_scoped_wallet_keystore_env_from_registry() {
544
+ local material_dir="$1"
545
+ if [[ ! -f "$WALLET_REGISTRY_JSON" ]] || ! command -v jq >/dev/null 2>&1; then
546
+ return 0
547
+ fi
548
+
549
+ while IFS=$'\t' read -r wallet_id provider_id; do
550
+ [[ -n "$wallet_id" ]] || continue
551
+ if [[ "$provider_id" != "local-socket-signer" && "$provider_id" != "embedded-keystore" ]]; then
552
+ continue
553
+ fi
554
+ local env_suffix
555
+ local file_suffix
556
+ env_suffix="$(normalize_wallet_env_suffix "$wallet_id")"
557
+ file_suffix="$(normalize_wallet_filename_id "$wallet_id")"
558
+ [[ -n "$env_suffix" && -n "$file_suffix" ]] || continue
559
+
560
+ local sol_key="FASED_WALLET_SOLANA_KEYSTORE_PATH__${env_suffix^^}"
561
+ if [[ -z "${!sol_key:-}" ]]; then
562
+ local sol_candidate="${material_dir}/keystore-solana-${file_suffix}.v1.enc"
563
+ if [[ -f "$sol_candidate" ]]; then
564
+ export "$sol_key=$sol_candidate"
565
+ fi
566
+ fi
567
+
568
+ local evm_key="FASED_WALLET_EVM_KEYSTORE_PATH__${env_suffix^^}"
569
+ if [[ -z "${!evm_key:-}" ]]; then
570
+ local evm_candidate="${material_dir}/keystore-evm-${file_suffix}.v1.enc"
571
+ if [[ -f "$evm_candidate" ]]; then
572
+ export "$evm_key=$evm_candidate"
573
+ fi
574
+ fi
575
+ done < <(
576
+ jq -r '
577
+ (.wallets // [])
578
+ | .[]
579
+ | select(.id != null and .providerId != null)
580
+ | "\(.id|tostring)\t\(.providerId|tostring)"
581
+ ' "$WALLET_REGISTRY_JSON" 2>/dev/null || true
582
+ )
583
+ }
584
+
585
+ resolve_signerd_keystore_export() {
586
+ local explicit_value="$1"
587
+ local scoped_prefix="$2"
588
+ local default_path="$3"
589
+
590
+ if has_scoped_wallet_env_value "$scoped_prefix"; then
591
+ printf '\n'
592
+ return 0
593
+ fi
594
+
595
+ if [[ -n "$explicit_value" && "$explicit_value" != "$default_path" ]]; then
596
+ printf '%s\n' "$explicit_value"
597
+ return 0
598
+ fi
599
+
600
+ if [[ -n "$explicit_value" ]]; then
601
+ printf '%s\n' "$explicit_value"
602
+ return 0
603
+ fi
604
+
605
+ printf '%s\n' "$default_path"
606
+ }
607
+
608
+ resolve_wallet_chains_from_config() {
609
+ if [[ ! -f "$CONFIG_JSON" ]] || ! command -v jq >/dev/null 2>&1; then
610
+ printf '%s\n' "evm,solana"
611
+ return 0
612
+ fi
613
+ jq -r '
614
+ [
615
+ ((.env.vars.FASED_WALLET_CHAINS // "") | split(",")[]?),
616
+ (.wallet.runtime.chains // [] | .[]?)
617
+ ]
618
+ | map((.|tostring|ascii_downcase|gsub("^\\s+|\\s+$"; "")))
619
+ | map(select(. == "evm" or . == "solana"))
620
+ | unique
621
+ | if length > 0 then join(",") else "evm,solana" end
622
+ ' "$CONFIG_JSON" 2>/dev/null || printf '%s\n' "evm,solana"
623
+ }
624
+
625
+ resolve_local_signer_sidecar_path() {
626
+ local socket_path="$1"
627
+ local kind="$2"
628
+ local socket_dir
629
+ local socket_name
630
+ local sidecar_base
631
+ socket_dir="$(dirname "$socket_path")"
632
+ socket_name="$(basename "$socket_path")"
633
+ sidecar_base="${socket_name%.sock}"
634
+ if [[ "$sidecar_base" == "$socket_name" ]]; then
635
+ sidecar_base="$socket_name"
636
+ fi
637
+ if [[ "$kind" == "pid" ]]; then
638
+ printf '%s\n' "${socket_dir}/${sidecar_base}.pid"
639
+ else
640
+ printf '%s\n' "${socket_dir}/${sidecar_base}.audit.jsonl"
641
+ fi
642
+ }
643
+
644
+ registry_has_local_signer_wallet() {
645
+ if [[ ! -f "$WALLET_REGISTRY_JSON" ]] || ! command -v jq >/dev/null 2>&1; then
646
+ return 1
647
+ fi
648
+ jq -e '
649
+ (.wallets // [])
650
+ | any(.providerId == "local-socket-signer")
651
+ ' "$WALLET_REGISTRY_JSON" >/dev/null 2>&1
652
+ }
653
+
654
+ has_local_signer_keystore_material() {
655
+ if [[ -n "${FASED_WALLET_SOLANA_KEYSTORE_PATH:-}" ]] || has_scoped_wallet_env_value "FASED_WALLET_SOLANA_KEYSTORE_PATH"; then
656
+ return 0
657
+ fi
658
+ local candidate
659
+ for candidate in "$SIGNERD_MATERIAL_DIR"/keystore-solana*.v1.enc "$SIGNERD_MATERIAL_DIR"/keystore-evm*.v1.enc; do
660
+ [[ -f "$candidate" ]] && return 0
661
+ done
662
+ return 1
663
+ }
664
+
665
+ should_start_signerd() {
666
+ registry_has_local_signer_wallet || has_local_signer_keystore_material
667
+ }
668
+
669
+ collect_existing_signerd_pids() {
670
+ local pid_file app_pid_file
671
+ pid_file="$(resolve_local_signer_sidecar_path "$SIGNERD_BACKEND_SOCKET" "pid")"
672
+ app_pid_file="$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "pid")"
673
+ {
674
+ if [[ -f "$pid_file" ]]; then
675
+ cat "$pid_file" 2>/dev/null || true
676
+ fi
677
+ if [[ "$app_pid_file" != "$pid_file" && -f "$app_pid_file" ]]; then
678
+ cat "$app_pid_file" 2>/dev/null || true
679
+ fi
680
+ pgrep -f "$SIGNERD_BIN" 2>/dev/null || true
681
+ pgrep -f "wallet signer broker" 2>/dev/null || true
682
+ } | awk '/^[0-9]+$/ { if (!seen[$1]++) print $1 }'
683
+ }
684
+
685
+ count_existing_signerd_pids() {
686
+ local count=0
687
+ while IFS= read -r _pid; do
688
+ count=$((count + 1))
689
+ done < <(collect_existing_signerd_pids)
690
+ printf '%s\n' "$count"
691
+ }
692
+
693
+ dump_existing_signerd_processes() {
694
+ pgrep -af "$SIGNERD_BIN" 2>/dev/null || true
695
+ }
696
+
697
+ stop_existing_signerd() {
698
+ local pid_file app_pid_file
699
+ pid_file="$(resolve_local_signer_sidecar_path "$SIGNERD_BACKEND_SOCKET" "pid")"
700
+ app_pid_file="$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "pid")"
701
+ if signer_isolation_helper_available; then
702
+ run_signer_isolation_helper stop "$SIGNERD_BACKEND_SOCKET" "$pid_file" >/dev/null 2>&1 || true
703
+ if [[ "$SIGNERD_SOCKET" != "$SIGNERD_BACKEND_SOCKET" ]]; then
704
+ run_signer_isolation_helper stop "$SIGNERD_SOCKET" "$app_pid_file" >/dev/null 2>&1 || true
705
+ fi
706
+ return 0
707
+ fi
708
+ local pid=""
709
+ while IFS= read -r pid; do
710
+ [[ "$pid" =~ ^[0-9]+$ ]] || continue
711
+ kill "$pid" >/dev/null 2>&1 || true
712
+ done < <(collect_existing_signerd_pids)
713
+ sleep 0.5
714
+ while IFS= read -r pid; do
715
+ [[ "$pid" =~ ^[0-9]+$ ]] || continue
716
+ kill -9 "$pid" >/dev/null 2>&1 || true
717
+ done < <(collect_existing_signerd_pids)
718
+ rm -f "$SIGNERD_SOCKET" "$SIGNERD_BACKEND_SOCKET" "$pid_file" "$app_pid_file"
719
+ }
720
+
721
+ wait_for_signerd_ready() {
722
+ local retries="$1"
723
+ local count=0
724
+ while true; do
725
+ local active_count
726
+ active_count="$(count_existing_signerd_pids)"
727
+ if [[ "$active_count" == "1" && -S "$SIGNERD_BACKEND_SOCKET" ]]; then
728
+ return 0
729
+ fi
730
+ if [[ "$active_count" -gt 1 ]]; then
731
+ echo "[signerd] ERROR: multiple fased-signerd processes detected; refusing to start gateway."
732
+ dump_existing_signerd_processes
733
+ return 1
734
+ fi
735
+ sleep 1
736
+ count=$((count + 1))
737
+ if [[ $count -ge $retries ]]; then
738
+ echo "[signerd] ERROR: fased-signerd did not become healthy within ${retries}s."
739
+ dump_existing_signerd_processes
740
+ tail -n 40 "$SIGNERD_LOG" 2>/dev/null || true
741
+ return 1
742
+ fi
743
+ done
744
+ }
745
+
746
+ wait_for_signer_broker_ready() {
747
+ local retries="$1"
748
+ local count=0
749
+ while true; do
750
+ if [[ -S "$SIGNERD_SOCKET" ]]; then
751
+ return 0
752
+ fi
753
+ sleep 1
754
+ count=$((count + 1))
755
+ if [[ $count -ge $retries ]]; then
756
+ echo "[signerd] ERROR: signer broker did not create app socket within ${retries}s."
757
+ tail -n 40 "$SIGNERD_BROKER_LOG" 2>/dev/null || true
758
+ return 1
759
+ fi
760
+ done
761
+ }
762
+
763
+ collect_wallet_env_args() {
764
+ local key value
765
+ while IFS='=' read -r key value; do
766
+ [[ "$key" == FASED_WALLET_* ]] || continue
767
+ printf '%s=%s\0' "$key" "$value"
768
+ done < <(env)
769
+ }
770
+
771
+ current_app_user() {
772
+ id -un 2>/dev/null || printf '%s\n' "${USER:-app}"
773
+ }
774
+
775
+ signer_isolation_helper_available() {
776
+ [[ -n "${FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER:-}" ]] || return 1
777
+ [[ -x "$SIGNER_ISOLATION_HELPER" ]] || return 1
778
+ command -v sudo >/dev/null 2>&1 || return 1
779
+ }
780
+
781
+ run_signer_isolation_helper() {
782
+ local app_user
783
+ app_user="$(current_app_user)"
784
+ sudo -n -E "$SIGNER_ISOLATION_HELPER" "$app_user" "$FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER" "$@"
785
+ }
786
+
787
+ start_signerd_process() {
788
+ local pid_file audit_log
789
+ pid_file="$(resolve_local_signer_sidecar_path "$SIGNERD_BACKEND_SOCKET" "pid")"
790
+ audit_log="$(resolve_local_signer_sidecar_path "$SIGNERD_BACKEND_SOCKET" "audit")"
791
+ if [[ -n "${FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER:-}" ]]; then
792
+ if signer_isolation_helper_available; then
793
+ run_signer_isolation_helper start-signerd \
794
+ "$SIGNERD_BIN" \
795
+ "$SIGNERD_BACKEND_SOCKET" \
796
+ "$pid_file" \
797
+ "$audit_log" \
798
+ >>"$SIGNERD_LOG" 2>&1 &
799
+ return 0
800
+ fi
801
+ if ! command -v sudo >/dev/null 2>&1; then
802
+ return 1
803
+ fi
804
+ local env_args=()
805
+ while IFS= read -r -d '' env_arg; do
806
+ env_args+=("$env_arg")
807
+ done < <(collect_wallet_env_args)
808
+ sudo -n -u "$FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER" -H env "${env_args[@]}" \
809
+ "$SIGNERD_BIN" \
810
+ -socket "$SIGNERD_BACKEND_SOCKET" \
811
+ -pid-file "$pid_file" \
812
+ -audit-log "$audit_log" \
813
+ >>"$SIGNERD_LOG" 2>&1 &
814
+ return 0
815
+ fi
816
+ "$SIGNERD_BIN" \
817
+ -socket "$SIGNERD_BACKEND_SOCKET" \
818
+ -pid-file "$pid_file" \
819
+ -audit-log "$audit_log" \
820
+ >>"$SIGNERD_LOG" 2>&1 &
821
+ }
822
+
823
+ start_signer_broker_process() {
824
+ if [[ "$SIGNERD_SOCKET" == "$SIGNERD_BACKEND_SOCKET" ]]; then
825
+ return 0
826
+ fi
827
+ local gateway_entry
828
+ gateway_entry="$(resolve_gateway_cli_entry || true)"
829
+ if [[ -z "$gateway_entry" ]]; then
830
+ echo "[signerd] ERROR: signer broker CLI unavailable under $FASED_ROOT/dist"
831
+ return 1
832
+ fi
833
+ if [[ -n "${FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER:-}" ]]; then
834
+ if signer_isolation_helper_available; then
835
+ run_signer_isolation_helper start-broker \
836
+ "$NODE_BIN" \
837
+ "$gateway_entry" \
838
+ "$SIGNERD_SOCKET" \
839
+ "$SIGNERD_BACKEND_SOCKET" \
840
+ "$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "pid")" \
841
+ "$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "audit")" \
842
+ >>"$SIGNERD_BROKER_LOG" 2>&1 &
843
+ return 0
844
+ fi
845
+ if ! command -v sudo >/dev/null 2>&1; then
846
+ return 1
847
+ fi
848
+ local env_args=()
849
+ while IFS= read -r -d '' env_arg; do
850
+ env_args+=("$env_arg")
851
+ done < <(collect_wallet_env_args)
852
+ sudo -n -u "$FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER" -H env "${env_args[@]}" \
853
+ "$NODE_BIN" "$gateway_entry" wallet signer broker \
854
+ --socket "$SIGNERD_SOCKET" \
855
+ --backend-socket "$SIGNERD_BACKEND_SOCKET" \
856
+ --pid-file "$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "pid")" \
857
+ --audit-log "$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "audit")" \
858
+ >>"$SIGNERD_BROKER_LOG" 2>&1 &
859
+ return 0
860
+ fi
861
+ "$NODE_BIN" "$gateway_entry" wallet signer broker \
862
+ --socket "$SIGNERD_SOCKET" \
863
+ --backend-socket "$SIGNERD_BACKEND_SOCKET" \
864
+ --pid-file "$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "pid")" \
865
+ --audit-log "$(resolve_local_signer_sidecar_path "$SIGNERD_SOCKET" "audit")" \
866
+ >>"$SIGNERD_BROKER_LOG" 2>&1 &
867
+ }
868
+
869
+ load_wallet_signer_env_from_config
870
+ load_wallet_signer_env_file
871
+ SIGNERD_BIN="${FASED_WALLET_LOCAL_SIGNER_BIN:-$SIGNERD_BIN}"
872
+ SIGNERD_MATERIAL_DIR="${FASED_WALLET_SIGNER_STATE_DIR:-$SIGNERD_MATERIAL_DIR}"
873
+ if [[ -n "${FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER:-}" ]]; then
874
+ SIGNERD_BROKER_LOG="$(dirname "${FASED_WALLET_LOCAL_SIGNER_SOCKET:-$SIGNERD_SOCKET}")/local-signer-broker.log"
875
+ fi
876
+ hydrate_scoped_wallet_keystore_env_from_registry "$SIGNERD_MATERIAL_DIR"
877
+ if should_start_signerd; then
878
+ SIGNERD_STARTUP_MODE="healthy"
879
+ SIGNERD_SOCKET="${FASED_WALLET_LOCAL_SIGNER_SOCKET:-$SIGNERD_SOCKET}"
880
+ SIGNERD_BACKEND_SOCKET="${FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET:-$SIGNERD_SOCKET}"
881
+ SIGNERD_EVM_KEYSTORE="$(resolve_signerd_keystore_export "${FASED_WALLET_EVM_KEYSTORE_PATH:-}" "FASED_WALLET_EVM_KEYSTORE_PATH" "$SIGNERD_MATERIAL_DIR/keystore-evm.v1.enc")"
882
+ SIGNERD_SOL_KEYSTORE="$(resolve_signerd_keystore_export "${FASED_WALLET_SOLANA_KEYSTORE_PATH:-}" "FASED_WALLET_SOLANA_KEYSTORE_PATH" "$SIGNERD_MATERIAL_DIR/keystore-solana.v1.enc")"
883
+ SIGNERD_PASSPHRASE="${FASED_WALLET_PASSPHRASE:-}"
884
+ SIGNERD_PASSPHRASE_FILE="${FASED_WALLET_PASSPHRASE_FILE:-}"
885
+ SIGNERD_CHAINS="${FASED_WALLET_CHAINS:-$(resolve_wallet_chains_from_config)}"
886
+ export FASED_WALLET_LOCAL_SIGNER_SOCKET="$SIGNERD_SOCKET"
887
+ export FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET="$SIGNERD_BACKEND_SOCKET"
888
+ export FASED_WALLET_SIGNER_STATE_DIR="$SIGNERD_MATERIAL_DIR"
889
+ export FASED_WALLET_CHAINS="${SIGNERD_CHAINS:-evm,solana}"
890
+ if [[ -n "$SIGNERD_EVM_KEYSTORE" ]]; then
891
+ export FASED_WALLET_EVM_KEYSTORE_PATH="$SIGNERD_EVM_KEYSTORE"
892
+ else
893
+ unset FASED_WALLET_EVM_KEYSTORE_PATH
894
+ fi
895
+ if [[ -n "$SIGNERD_SOL_KEYSTORE" ]]; then
896
+ export FASED_WALLET_SOLANA_KEYSTORE_PATH="$SIGNERD_SOL_KEYSTORE"
897
+ else
898
+ unset FASED_WALLET_SOLANA_KEYSTORE_PATH
899
+ fi
900
+ if [[ -n "$SIGNERD_PASSPHRASE" ]]; then
901
+ export FASED_WALLET_PASSPHRASE="$SIGNERD_PASSPHRASE"
902
+ unset FASED_WALLET_PASSPHRASE_FILE
903
+ else
904
+ SIGNERD_PASSPHRASE_FILE="${SIGNERD_PASSPHRASE_FILE:-$SIGNERD_MATERIAL_DIR/passphrase}"
905
+ export FASED_WALLET_PASSPHRASE_FILE="$SIGNERD_PASSPHRASE_FILE"
906
+ unset FASED_WALLET_PASSPHRASE
907
+ fi
908
+ if [[ -f "$SIGNERD_BIN" ]]; then
909
+ if [[ -S "$SIGNERD_SOCKET" ]] || [[ -S "$SIGNERD_BACKEND_SOCKET" ]] || [[ -f "$(resolve_local_signer_sidecar_path "$SIGNERD_BACKEND_SOCKET" "pid")" ]] || [[ "$(count_existing_signerd_pids)" -gt 0 ]]; then
910
+ echo "==> Restarting fased-signerd to apply current wallet chain/runtime config..."
911
+ stop_existing_signerd
912
+ fi
913
+ echo "==> Starting fased-signerd (Go key signer)..."
914
+ mkdir -p "$LOG_DIR"
915
+ mkdir -p "$(dirname "$SIGNERD_LOG")" "$(dirname "$SIGNERD_BROKER_LOG")" 2>/dev/null || true
916
+ if start_signerd_process; then
917
+ SIGNERD_PID=$!
918
+ else
919
+ mark_signerd_degraded "failed to start isolated fased-signerd as ${FASED_WALLET_LOCAL_SIGNER_RUN_AS_USER:-current user}. Check sudoers and $SIGNERD_LOG"
920
+ SIGNERD_PID=""
921
+ fi
922
+ if [[ -n "$SIGNERD_PID" ]] && wait_for_signerd_ready "$SIGNERD_READY_TIMEOUT_SECONDS"; then
923
+ if ! start_signer_broker_process; then
924
+ mark_signerd_degraded "fased-signerd started but signer broker failed. Check $SIGNERD_BROKER_LOG"
925
+ elif wait_for_signer_broker_ready "$SIGNERD_READY_TIMEOUT_SECONDS"; then
926
+ echo "==> fased-signerd started (PID=$SIGNERD_PID, socket: $SIGNERD_BACKEND_SOCKET)"
927
+ else
928
+ mark_signerd_degraded "fased-signerd broker did not become ready. Check $SIGNERD_BROKER_LOG"
929
+ fi
930
+ else
931
+ mark_signerd_degraded "fased-signerd did not create socket. Check $SIGNERD_LOG"
932
+ fi
933
+ else
934
+ mark_signerd_degraded "fased-signerd is not installed; dashboard stays online. Configure wallet signer from the dashboard or run fased wallet signer setup."
935
+ fi
936
+ else
937
+ unset FASED_WALLET_LOCAL_SIGNER_SOCKET
938
+ unset FASED_WALLET_LOCAL_SIGNER_BACKEND_SOCKET
939
+ unset FASED_WALLET_SIGNER_STATE_DIR
940
+ unset FASED_WALLET_CHAINS
941
+ unset FASED_WALLET_SOLANA_KEYSTORE_PATH
942
+ unset FASED_WALLET_EVM_KEYSTORE_PATH
943
+ unset FASED_WALLET_PASSPHRASE
944
+ unset FASED_WALLET_PASSPHRASE_FILE
945
+ echo "==> Wallet signer not configured; skipping fased-signerd startup."
946
+ fi
947
+ if [[ -f "$ZROK_MONITOR_PID_FILE" ]]; then
948
+ OLD_MONITOR_PID=$(cat "$ZROK_MONITOR_PID_FILE" 2>/dev/null || true)
949
+ if [[ -n "$OLD_MONITOR_PID" ]] && kill -0 "$OLD_MONITOR_PID" 2>/dev/null; then
950
+ kill "$OLD_MONITOR_PID" 2>/dev/null || true
951
+ fi
952
+ rm -f "$ZROK_MONITOR_PID_FILE" || true
953
+ fi
954
+ pkill -f "zrok share" || true
955
+ sleep 1
956
+
957
+ # === Tunnel Helper Functions ===
958
+
959
+ start_health_monitor() {
960
+ local SLUG="$1"
961
+ local RES_TOKEN="$2"
962
+ local retry_delay=30
963
+ local max_retry_delay=300
964
+ while true; do
965
+ sleep "$retry_delay"
966
+ local pid=""
967
+ if [[ -f "$FASED_CONFIG_DIR/.zrok-pid" ]]; then
968
+ pid="$(cat "$FASED_CONFIG_DIR/.zrok-pid" 2>/dev/null || true)"
969
+ fi
970
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
971
+ retry_delay=30
972
+ continue
973
+ fi
974
+
975
+ echo "[tunnel] zrok share is not active, attempting restart..."
976
+ if zrok_log_indicates_clock_skew; then
977
+ echo "[tunnel] Detected zrok auth failure caused by host clock skew."
978
+ ensure_managed_clock_sync || true
979
+ fi
980
+ if [[ -n "$RES_TOKEN" ]]; then
981
+ "$ZROK_BIN" share reserved "$RES_TOKEN" --headless >>"$ZROK_RUNTIME_LOG" 2>&1 &
982
+ else
983
+ "$ZROK_BIN" share public "http://127.0.0.1:${FASED_GATEWAY_PORT}" --unique-name "$SLUG" >>"$ZROK_RUNTIME_LOG" 2>&1 &
984
+ fi
985
+ pid=$!
986
+ echo "$pid" > "$FASED_CONFIG_DIR/.zrok-pid"
987
+ sleep 5
988
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
989
+ echo "[tunnel] ✓ Tunnel active."
990
+ retry_delay=30
991
+ continue
992
+ fi
993
+
994
+ echo "[tunnel] WARNING: zrok share restart failed."
995
+ if zrok_log_indicates_clock_skew; then
996
+ echo "[tunnel] Detected zrok auth failure caused by host clock skew."
997
+ ensure_managed_clock_sync || true
998
+ fi
999
+ if (( retry_delay < max_retry_delay )); then
1000
+ retry_delay=$((retry_delay * 2))
1001
+ if (( retry_delay > max_retry_delay )); then
1002
+ retry_delay=$max_retry_delay
1003
+ fi
1004
+ fi
1005
+ done
1006
+ }
1007
+
1008
+ # === Execution Flow ===
1009
+
1010
+ MANAGED_TUNNEL_DISABLED=0
1011
+ TUNNEL_ERROR=""
1012
+ disable_managed_tunnel() {
1013
+ MANAGED_TUNNEL_DISABLED=1
1014
+ TUNNEL_STARTED=0
1015
+ FINAL_URL="N/A"
1016
+ TUNNEL_ERROR="$1"
1017
+ echo "[tunnel] WARNING: $1"
1018
+ echo "[tunnel] Dashboard gateway stays online; Fased Network tunnel remains degraded until repaired."
1019
+ }
1020
+
1021
+ # 1. Wait for the agent to enroll/refresh access token.
1022
+ # We do not treat a stale pre-existing token as ready if it has no zrok metadata.
1023
+ echo "==> Waiting for agent enrollment/token refresh (max 60s)..."
1024
+ MAX_RETRIES=60
1025
+ COUNT=0
1026
+ while true; do
1027
+ if [[ -f "$TOKEN_PATH" ]]; then
1028
+ CURRENT_ZROK=$(jq -r '.zrokToken // empty' "$TOKEN_PATH" 2>/dev/null || true)
1029
+ if [[ -n "$CURRENT_ZROK" ]]; then
1030
+ break
1031
+ fi
1032
+
1033
+ CURRENT_SIG=$(sha256sum "$TOKEN_PATH" | awk '{print $1}')
1034
+ # If token changed after startup, proceed even if zrok is still missing.
1035
+ # Recovery logic below will decide strict-mode outcome.
1036
+ if [[ -n "$INITIAL_TOKEN_SIG" && "$CURRENT_SIG" != "$INITIAL_TOKEN_SIG" ]]; then
1037
+ break
1038
+ fi
1039
+
1040
+ # No initial token: once we have a token file, give it a short chance to gain zrok fields.
1041
+ if [[ -z "$INITIAL_TOKEN_SIG" && $COUNT -ge 3 ]]; then
1042
+ break
1043
+ fi
1044
+ fi
1045
+ sleep 1
1046
+ COUNT=$((COUNT + 1))
1047
+ if [[ $COUNT -ge $MAX_RETRIES ]]; then
1048
+ if [[ -f "$TOKEN_PATH" ]]; then
1049
+ echo "[tunnel] WARNING: Token refresh did not complete in time; continuing with current token."
1050
+ break
1051
+ fi
1052
+ disable_managed_tunnel "Agent enrollment/token refresh did not complete in time and no token is available."
1053
+ if [[ "$VERBOSE_STARTUP" != "1" ]]; then
1054
+ echo "[debug] Last gateway startup logs:"
1055
+ tail -n 40 "$GATEWAY_BOOT_LOG" || true
1056
+ fi
1057
+ break
1058
+ fi
1059
+ done
1060
+
1061
+ # === Tunneling (zrok) ===
1062
+
1063
+ FINAL_URL="N/A"
1064
+ TUNNEL_STARTED=0
1065
+ SLUG="N/A"
1066
+ RES_TOKEN=""
1067
+
1068
+ ZROK_VDIR="$HOME/.zrok"
1069
+ ZROK_BIN="$ZROK_VDIR/bin/zrok"
1070
+
1071
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" ]]; then
1072
+
1073
+ # Check if zrok is installed; if not, install it
1074
+ if [[ ! -f "$ZROK_BIN" ]]; then
1075
+ echo "[tunnel] Installing zrok CLI..."
1076
+ echo "[tunnel] Destination: $ZROK_VDIR/bin"
1077
+ mkdir -p "$ZROK_VDIR/bin"
1078
+
1079
+ # Temporarily disable pipefail to capture install errors
1080
+ set +e
1081
+ curl -sLf https://get.openziti.io/zrok/install/get.bash | bash -s -- --install-dir "$ZROK_VDIR/bin" > /tmp/zrok-install.log 2>&1
1082
+ INSTALL_EXIT=$?
1083
+ set -e
1084
+
1085
+ if [[ $INSTALL_EXIT -ne 0 ]]; then
1086
+ echo "[tunnel] ERROR: zrok installation failed (Exit: $INSTALL_EXIT). Log:"
1087
+ cat /tmp/zrok-install.log
1088
+ echo "[tunnel] Attempting manual download as fallback (v1.1.11)..."
1089
+
1090
+ TMP_ZROK_DIR=$(mktemp -d)
1091
+ ZROK_URL="https://github.com/openziti/zrok/releases/download/v1.1.11/zrok_1.1.11_linux_amd64.tar.gz"
1092
+
1093
+ if curl -sLf "$ZROK_URL" -o "$TMP_ZROK_DIR/zrok.tar.gz"; then
1094
+ tar -xzf "$TMP_ZROK_DIR/zrok.tar.gz" -C "$TMP_ZROK_DIR"
1095
+
1096
+ # Find zrok binary safely within the temp dir
1097
+ ZROK_FOUND=$(find "$TMP_ZROK_DIR" -name zrok -type f | head -n 1)
1098
+
1099
+ if [[ -n "$ZROK_FOUND" ]]; then
1100
+ mv "$ZROK_FOUND" "$ZROK_VDIR/bin/"
1101
+ chmod +x "$ZROK_VDIR/bin/zrok"
1102
+ echo "[tunnel] Manual download success."
1103
+ else
1104
+ echo "[tunnel] Extracted but 'zrok' binary not found."
1105
+ ls -R "$TMP_ZROK_DIR"
1106
+ rm -rf "$TMP_ZROK_DIR"
1107
+ disable_managed_tunnel "zrok manual download extracted without a zrok binary."
1108
+ fi
1109
+ else
1110
+ echo "[tunnel] Download failed (curl)."
1111
+ rm -rf "$TMP_ZROK_DIR"
1112
+ disable_managed_tunnel "zrok manual download failed."
1113
+ fi
1114
+ rm -rf "$TMP_ZROK_DIR" /tmp/zrok-install.log
1115
+ fi
1116
+ fi
1117
+
1118
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" && ! -f "$ZROK_BIN" ]]; then
1119
+ disable_managed_tunnel "zrok binary not found at $ZROK_BIN after install."
1120
+ fi
1121
+
1122
+ fi
1123
+
1124
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" ]]; then
1125
+
1126
+ chmod +x "$ZROK_BIN"
1127
+ echo "[tunnel] zrok version: $($ZROK_BIN version)"
1128
+
1129
+ echo "==> Consuming server-issued zrok credentials..."
1130
+
1131
+ # Read zrok token from the enrolled agent's access token
1132
+ if [[ ! -f "$TOKEN_PATH" ]]; then
1133
+ disable_managed_tunnel "Agent enrollment token is unavailable. Tunnel cannot start yet."
1134
+ fi
1135
+
1136
+ fi
1137
+
1138
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" ]]; then
1139
+
1140
+ ZROK_TOKEN=$(jq -r .zrokToken "$TOKEN_PATH")
1141
+ SERVER_SLUG=$(jq -r '.agentSlug // empty' "$TOKEN_PATH")
1142
+ AGENT_HANDLE=$(jq -r '.handle // empty' "$TOKEN_PATH")
1143
+
1144
+ if [[ -n "$SERVER_SLUG" ]]; then
1145
+ SLUG="$SERVER_SLUG"
1146
+ elif [[ -n "$AGENT_HANDLE" ]]; then
1147
+ SLUG=$(echo "$AGENT_HANDLE" | sed 's/[^a-zA-Z0-9]/-/g' | sed 's/^-//' | sed 's/-$//' | tr '[:upper:]' '[:lower:]')
1148
+ else
1149
+ SLUG=""
1150
+ fi
1151
+
1152
+ if [[ -z "$SLUG" ]]; then
1153
+ disable_managed_tunnel "Unable to resolve tunnel slug from enrollment token."
1154
+ fi
1155
+
1156
+ fi
1157
+
1158
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" ]]; then
1159
+
1160
+ echo "[tunnel] Target Slug: $SLUG"
1161
+
1162
+ # Reservation persistence
1163
+ RES_FILE="$FASED_CONFIG_DIR/${SLUG}.zrok-reservation"
1164
+ RES_TOKEN=""
1165
+ if [[ -f "$RES_FILE" ]]; then
1166
+ RES_TOKEN=$(cat "$RES_FILE")
1167
+ echo "[tunnel] Found cached reservation: $RES_TOKEN"
1168
+ fi
1169
+
1170
+ # Recovery when exact slug file is missing: use the only reservation token in config dir.
1171
+ if [[ -z "$RES_TOKEN" ]]; then
1172
+ mapfile -t RES_FILES < <(ls "$FASED_CONFIG_DIR"/*.zrok-reservation 2>/dev/null || true)
1173
+ if [[ ${#RES_FILES[@]} -eq 1 ]]; then
1174
+ RES_FILE="${RES_FILES[0]}"
1175
+ RES_TOKEN=$(cat "$RES_FILE")
1176
+ RECOVERED_BASENAME=$(basename "$RES_FILE")
1177
+ RECOVERED_SLUG="${RECOVERED_BASENAME%.zrok-reservation}"
1178
+ if [[ -n "$RECOVERED_SLUG" ]]; then
1179
+ SLUG="$RECOVERED_SLUG"
1180
+ echo "[tunnel] Recovered slug from cached reservation filename: $SLUG"
1181
+ fi
1182
+ echo "[tunnel] Recovered cached reservation: $RES_TOKEN"
1183
+ fi
1184
+ fi
1185
+
1186
+ TUNNEL_STARTED=0
1187
+
1188
+ if [[ "$ZROK_TOKEN" == "null" || -z "$ZROK_TOKEN" ]]; then
1189
+ echo "[tunnel] WARNING: No zrok credentials found in enrollment result."
1190
+ echo "[tunnel] Attempting strict managed recovery using cached reservation + existing zrok identity..."
1191
+ export ZROK_API_ENDPOINT="${ZROK_API_ENDPOINT:-https://zrok.fased.app}"
1192
+ "$ZROK_BIN" config set apiEndpoint "$ZROK_API_ENDPOINT" 2>/dev/null || true
1193
+
1194
+ HAS_ZROK_IDENTITY=0
1195
+ if "$ZROK_BIN" overview --json >/dev/null 2>&1; then
1196
+ HAS_ZROK_IDENTITY=1
1197
+ fi
1198
+
1199
+ if [[ -n "$RES_TOKEN" && "$HAS_ZROK_IDENTITY" -eq 1 ]]; then
1200
+ echo "[tunnel] Recovery prerequisites satisfied (cached reservation found)."
1201
+ FINAL_URL="https://${SLUG}.agents.fased.app"
1202
+ TUNNEL_STARTED=1
1203
+ else
1204
+ echo "[tunnel] ERROR: Managed mode is strict and cannot continue without tunnel credentials."
1205
+ echo "[tunnel] Required for recovery: existing zrok identity + cached *.zrok-reservation token."
1206
+ echo "[tunnel] Remediation:"
1207
+ echo "[tunnel] 1) Check server logs: journalctl -u fased -n 100 --no-pager | grep 'zrok:'"
1208
+ echo "[tunnel] 2) Ensure enroll returns zrokToken/agentSlug"
1209
+ echo "[tunnel] 3) Re-enroll only after server provisioning is healthy"
1210
+ disable_managed_tunnel "No zrok credentials or cached reservation are available yet."
1211
+ fi
1212
+ fi
1213
+
1214
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" && "$ZROK_TOKEN" != "null" && -n "$ZROK_TOKEN" ]]; then
1215
+ echo "[tunnel] Enabling zrok environment..."
1216
+ export ZROK_API_ENDPOINT="${ZROK_API_ENDPOINT:-https://zrok.fased.app}"
1217
+ ensure_managed_clock_sync || true
1218
+ "$ZROK_BIN" config set apiEndpoint "$ZROK_API_ENDPOINT" 2>/dev/null
1219
+
1220
+ # zrok enable (idempotent-ish, or just try)
1221
+ # Remove --force as it's not supported in v1.1+
1222
+ # We ignore error if already enabled, but capture output to be safe
1223
+ "$ZROK_BIN" enable "$ZROK_TOKEN" --description "fased-agent" 2>/dev/null || true
1224
+
1225
+ # If no token, check if we already reserved it (recovery from lost file)
1226
+ if [[ -z "$RES_TOKEN" ]]; then
1227
+ echo "[tunnel] Checking for existing reservation..."
1228
+ OVERVIEW_JSON=$("$ZROK_BIN" overview --json 2>/dev/null || echo "{}")
1229
+ EXISTING_TOKEN=$(echo "$OVERVIEW_JSON" | jq -r --arg SLUG "$SLUG" '(.shares // [])[] | select(.frontendEndpoints[] | contains($SLUG)) | .token' 2>/dev/null | head -n 1 || true)
1230
+
1231
+ if [[ -n "$EXISTING_TOKEN" ]]; then
1232
+ echo "[tunnel] Recovered existing token: $EXISTING_TOKEN"
1233
+ RES_TOKEN="$EXISTING_TOKEN"
1234
+ echo "$RES_TOKEN" > "$RES_FILE"
1235
+ fi
1236
+ fi
1237
+
1238
+ # If still no token, try to reserve
1239
+ if [[ -z "$RES_TOKEN" ]]; then
1240
+ echo "[tunnel] Reserving public share..."
1241
+ # Use --json-output to parse output reliably; separate stderr
1242
+ PARAMS="--unique-name $SLUG --backend-mode proxy --json-output"
1243
+
1244
+ # Capture logic: stdout to variable, stderr to temp file
1245
+ ZROK_LOG="/tmp/zrok-reserve.log"
1246
+ rm -f "$ZROK_LOG"
1247
+
1248
+ if OUT=$("$ZROK_BIN" reserve public "http://127.0.0.1:${FASED_GATEWAY_PORT}" $PARAMS 2> "$ZROK_LOG"); then
1249
+ RES_TOKEN=$(echo "$OUT" | jq -r '.token // empty')
1250
+ else
1251
+ echo "[tunnel] Reserve failed. Log:"
1252
+ cat "$ZROK_LOG"
1253
+ fi
1254
+
1255
+ if [[ -z "$RES_TOKEN" ]]; then
1256
+ echo "[tunnel] Reservation failed or already reserved. Output: $OUT"
1257
+ disable_managed_tunnel "Could not establish reserved tunnel for '$SLUG'."
1258
+ else
1259
+ echo "$RES_TOKEN" > "$RES_FILE"
1260
+ echo "[tunnel] Reserved: $RES_TOKEN"
1261
+ fi
1262
+ fi
1263
+
1264
+ fi
1265
+
1266
+ if [[ "$MANAGED_TUNNEL_DISABLED" != "1" ]]; then
1267
+ FINAL_URL="https://${SLUG}.agents.fased.app"
1268
+ TUNNEL_STARTED=1
1269
+ fi
1270
+
1271
+ if [[ "$TUNNEL_STARTED" -eq 1 ]]; then
1272
+ if ! start_initial_zrok_share "$SLUG"; then
1273
+ echo "[tunnel] WARNING: Continuing in degraded mode without a public tunnel."
1274
+ echo "[tunnel] WARNING: The local gateway remains up and background tunnel retries will continue."
1275
+ rm -f "$FASED_CONFIG_DIR/.zrok-pid" 2>/dev/null || true
1276
+ fi
1277
+ if [[ -f "$ZROK_MONITOR_PID_FILE" ]]; then
1278
+ OLD_MONITOR_PID=$(cat "$ZROK_MONITOR_PID_FILE" 2>/dev/null || true)
1279
+ if [[ -n "$OLD_MONITOR_PID" ]] && kill -0 "$OLD_MONITOR_PID" 2>/dev/null; then
1280
+ kill "$OLD_MONITOR_PID" 2>/dev/null || true
1281
+ fi
1282
+ fi
1283
+ start_health_monitor "$SLUG" "$RES_TOKEN" &
1284
+ ZROK_MONITOR_PID=$!
1285
+ echo "$ZROK_MONITOR_PID" > "$ZROK_MONITOR_PID_FILE"
1286
+ fi
1287
+
1288
+ fi
1289
+
1290
+ WALLET_JSON=""
1291
+ WALLET_HEALTHY="false"
1292
+ WALLET_PID="N/A"
1293
+ WALLET_MODE="n/a"
1294
+ WALLET_SERVICE="n/a"
1295
+ WALLET_CHAINS="n/a"
1296
+ WALLET_DIRECT_SIGNING="n/a"
1297
+ WALLET_TOOL_SCOPE="n/a"
1298
+ WALLET_KEYS_PATH="n/a"
1299
+ WALLET_STARTUP_MODE="healthy"
1300
+ WALLET_AUTH_STATE="unknown"
1301
+ WALLET_AUTH_MODE="unknown"
1302
+ WALLET_AUTH_SOURCE="unknown"
1303
+ WALLET_ERROR=""
1304
+
1305
+ echo "==> Enforcing wallet baseline (wallet service)..."
1306
+ if [[ "${WALLET_BASELINE_MODE,,}" == "skip" ]]; then
1307
+ WALLET_STARTUP_MODE="degraded"
1308
+ WALLET_ERROR="Wallet baseline skipped (FASED_WALLET_BASELINE_MODE=skip)."
1309
+ echo "[wallet] WARNING: Wallet baseline skipped; continuing startup immediately."
1310
+ else
1311
+ # Keep stderr visible to avoid hidden sudo/docker prompts while also logging.
1312
+ WALLET_SETUP_CMD=(
1313
+ env
1314
+ FASED_SKIP_BUILD=1
1315
+ FASED_WALLET_SETUP_ALLOW_DEGRADED=1
1316
+ "$NODE_BIN"
1317
+ "$RUN_NODE_SCRIPT"
1318
+ wallet
1319
+ setup
1320
+ --json
1321
+ )
1322
+ USE_TIMEOUT=0
1323
+ if command -v timeout >/dev/null 2>&1; then
1324
+ if [[ "$WALLET_BASELINE_TIMEOUT_SECONDS" =~ ^[0-9]+$ ]] && [[ "$WALLET_BASELINE_TIMEOUT_SECONDS" -gt 0 ]]; then
1325
+ USE_TIMEOUT=1
1326
+ fi
1327
+ fi
1328
+ if [[ "$USE_TIMEOUT" == "1" ]]; then
1329
+ echo "[wallet] Baseline timeout: ${WALLET_BASELINE_TIMEOUT_SECONDS}s (override: FASED_WALLET_BASELINE_TIMEOUT_SECONDS, disable timeout with 0)."
1330
+ if WALLET_OUTPUT=$(timeout --signal=TERM "${WALLET_BASELINE_TIMEOUT_SECONDS}" "${WALLET_SETUP_CMD[@]}" 2> >(tee "$WALLET_SETUP_LOG" >&2)); then
1331
+ WALLET_SETUP_RC=0
1332
+ else
1333
+ WALLET_SETUP_RC=$?
1334
+ fi
1335
+ else
1336
+ if WALLET_OUTPUT=$("${WALLET_SETUP_CMD[@]}" 2> >(tee "$WALLET_SETUP_LOG" >&2)); then
1337
+ WALLET_SETUP_RC=0
1338
+ else
1339
+ WALLET_SETUP_RC=$?
1340
+ fi
1341
+ fi
1342
+
1343
+ if [[ "${WALLET_SETUP_RC:-0}" == "0" ]]; then
1344
+ WALLET_JSON=$(printf '%s\n' "$WALLET_OUTPUT" | sed -n '/^{/,$p')
1345
+ if [[ -z "$WALLET_JSON" ]]; then
1346
+ WALLET_STARTUP_MODE="degraded"
1347
+ WALLET_ERROR="Wallet setup returned no JSON payload."
1348
+ else
1349
+ WALLET_HEALTHY=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.service.healthy // false' 2>/dev/null || echo "false")
1350
+ WALLET_PID=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.service.pid // "N/A"' 2>/dev/null || echo "N/A")
1351
+ WALLET_MODE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.mode // "n/a"' 2>/dev/null || echo "n/a")
1352
+ WALLET_SERVICE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.service.host + ":" + ((.status.service.port // 0)|tostring)' 2>/dev/null || echo "n/a")
1353
+ WALLET_CHAINS=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.chains // [] | join(",")' 2>/dev/null || echo "n/a")
1354
+ WALLET_DIRECT_SIGNING=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.policy.directSigning // "n/a"' 2>/dev/null || echo "n/a")
1355
+ WALLET_TOOL_SCOPE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.policy.toolAccessMode // "n/a"' 2>/dev/null || echo "n/a")
1356
+ WALLET_KEYS_PATH=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.paths.keysPath // "n/a"' 2>/dev/null || echo "n/a")
1357
+ WALLET_STARTUP_MODE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.startupState // "healthy"' 2>/dev/null || echo "healthy")
1358
+ WALLET_AUTH_STATE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.authState // "unknown"' 2>/dev/null || echo "unknown")
1359
+ WALLET_AUTH_MODE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.authMode // "unknown"' 2>/dev/null || echo "unknown")
1360
+ WALLET_AUTH_SOURCE=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.authSource // "unknown"' 2>/dev/null || echo "unknown")
1361
+ WALLET_ERROR=$(printf '%s\n' "$WALLET_JSON" | jq -r '.status.error // empty' 2>/dev/null || true)
1362
+ if [[ "$WALLET_HEALTHY" != "true" && "$WALLET_STARTUP_MODE" == "healthy" ]]; then
1363
+ WALLET_STARTUP_MODE="degraded"
1364
+ fi
1365
+ fi
1366
+ else
1367
+ WALLET_STARTUP_MODE="degraded"
1368
+ if [[ "${WALLET_SETUP_RC:-1}" == "124" || "${WALLET_SETUP_RC:-1}" == "143" ]]; then
1369
+ WALLET_ERROR="Wallet baseline timed out after ${WALLET_BASELINE_TIMEOUT_SECONDS}s (startup continued)."
1370
+ echo "[wallet] WARNING: Wallet baseline timed out; continuing startup in degraded mode."
1371
+ else
1372
+ WALLET_ERROR=$(tail -n 1 "$WALLET_SETUP_LOG" 2>/dev/null || echo "wallet setup failed")
1373
+ echo "[wallet] WARNING: Wallet baseline failed; continuing startup in degraded mode."
1374
+ fi
1375
+ echo "[wallet] See: $WALLET_SETUP_LOG"
1376
+ fi
1377
+
1378
+ if [[ "$WALLET_HEALTHY" != "true" ]]; then
1379
+ WALLET_STARTUP_MODE="degraded"
1380
+ fi
1381
+ fi
1382
+
1383
+ TAILSCALE_DNS_NAME=""
1384
+ TAILSCALE_ADMIN_URL="N/A"
1385
+ TAILSCALE_SSH_CMD="N/A"
1386
+ TAILSCALE_SERVE_READY=0
1387
+ if command -v tailscale >/dev/null 2>&1; then
1388
+ if [[ "${FASED_TAILSCALE_AUTO_SERVE:-1}" == "1" ]]; then
1389
+ tailscale serve --bg "http://127.0.0.1:${FASED_GATEWAY_PORT}" >/dev/null 2>&1 || \
1390
+ sudo tailscale serve --bg "http://127.0.0.1:${FASED_GATEWAY_PORT}" >/dev/null 2>&1 || \
1391
+ tailscale serve https / "http://127.0.0.1:${FASED_GATEWAY_PORT}" >/dev/null 2>&1 || true
1392
+ fi
1393
+ if tailscale serve status 2>/dev/null | grep -q "127.0.0.1:${FASED_GATEWAY_PORT}" || \
1394
+ sudo tailscale serve status 2>/dev/null | grep -q "127.0.0.1:${FASED_GATEWAY_PORT}"; then
1395
+ TAILSCALE_SERVE_READY=1
1396
+ fi
1397
+ TAILSCALE_DNS_NAME=$(tailscale status --json 2>/dev/null | jq -r '.Self.DNSName // empty' | tr -d '\n' || true)
1398
+ TAILSCALE_DNS_NAME="${TAILSCALE_DNS_NAME%.}"
1399
+ if [[ -n "$TAILSCALE_DNS_NAME" ]]; then
1400
+ TAILSCALE_SSH_CMD="tailscale ssh app@${TAILSCALE_DNS_NAME}"
1401
+ if [[ "$TAILSCALE_SERVE_READY" == "1" ]]; then
1402
+ TAILSCALE_ADMIN_URL="https://${TAILSCALE_DNS_NAME}"
1403
+ fi
1404
+ fi
1405
+ fi
1406
+
1407
+ FED_HANDLE=$(jq -r '.handle // "N/A"' "$TOKEN_PATH" 2>/dev/null || echo "N/A")
1408
+ FED_TOKEN_ID=$(jq -r '.tokenId // "N/A"' "$TOKEN_PATH" 2>/dev/null || echo "N/A")
1409
+ FED_EXPIRES_AT=$(jq -r '.expiresAt // "N/A"' "$TOKEN_PATH" 2>/dev/null || echo "N/A")
1410
+ FED_AGENT_SLUG=$(jq -r '.agentSlug // "N/A"' "$TOKEN_PATH" 2>/dev/null || echo "N/A")
1411
+ FED_PUBLIC_URL=$(jq -r '.publicUrl // "N/A"' "$TOKEN_PATH" 2>/dev/null || echo "N/A")
1412
+ FED_ZROK_TOKEN=$(jq -r '.zrokToken // empty' "$TOKEN_PATH" 2>/dev/null || true)
1413
+ FED_ZROK_TOKEN_MASKED="N/A"
1414
+ if [[ -n "$FED_ZROK_TOKEN" ]]; then
1415
+ FED_ZROK_TOKEN_MASKED=$(mask_secret "$FED_ZROK_TOKEN")
1416
+ fi
1417
+ RES_TOKEN_MASKED="N/A"
1418
+ if [[ -n "${RES_TOKEN:-}" ]]; then
1419
+ RES_TOKEN_MASKED=$(mask_secret "$RES_TOKEN")
1420
+ fi
1421
+ GATEWAY_TOKEN_MASKED=$(mask_secret "$(tr -d '\n' < "$GW_TOKEN_PATH")")
1422
+
1423
+ echo "==> Agent Running."
1424
+ echo ""
1425
+ echo "================== FASED STARTUP SUMMARY =================="
1426
+ echo "Gateway"
1427
+ echo " PID: $AGENT_PID"
1428
+ echo " Port: $FASED_GATEWAY_PORT"
1429
+ echo " Boot log: $GATEWAY_BOOT_LOG"
1430
+ echo "Federation"
1431
+ echo " Handle: $FED_HANDLE"
1432
+ echo " Token ID: $FED_TOKEN_ID"
1433
+ echo " Expires At: $FED_EXPIRES_AT"
1434
+ echo " Agent Slug: $FED_AGENT_SLUG"
1435
+ echo " Public URL (token): $FED_PUBLIC_URL"
1436
+ echo " zrokToken: $FED_ZROK_TOKEN_MASKED"
1437
+ echo "Federation/A2A Tunnel (zrok)"
1438
+ echo " PID: ${ZROK_PID:-N/A}"
1439
+ echo " Slug: ${SLUG:-N/A}"
1440
+ echo " Reservation token: $RES_TOKEN_MASKED"
1441
+ echo " Public URL (A2A): $FINAL_URL"
1442
+ echo " Runtime log: $ZROK_RUNTIME_LOG"
1443
+ if [[ "$MANAGED_TUNNEL_DISABLED" == "1" ]]; then
1444
+ echo " Startup Mode: degraded"
1445
+ echo " Warning: ${TUNNEL_ERROR:-Fased Network tunnel unavailable; dashboard gateway kept online}"
1446
+ fi
1447
+ echo "Wallet"
1448
+ echo " Healthy: $WALLET_HEALTHY"
1449
+ echo " Startup Mode: $WALLET_STARTUP_MODE"
1450
+ echo " Auth State: $WALLET_AUTH_STATE"
1451
+ echo " Auth Mode: $WALLET_AUTH_MODE"
1452
+ echo " Auth Source: $WALLET_AUTH_SOURCE"
1453
+ echo " Mode: $WALLET_MODE"
1454
+ echo " PID: $WALLET_PID"
1455
+ echo " Service: $WALLET_SERVICE"
1456
+ echo " Chains: $WALLET_CHAINS"
1457
+ echo " Direct Signing: $WALLET_DIRECT_SIGNING"
1458
+ echo " Tool Scope: $WALLET_TOOL_SCOPE"
1459
+ echo " Keys path: $WALLET_KEYS_PATH"
1460
+ echo " Runtime log: $WALLET_SETUP_LOG"
1461
+ if [[ "$WALLET_STARTUP_MODE" == "degraded" ]]; then
1462
+ echo " Warning: wallet degraded; gateway/tunnel kept online"
1463
+ if [[ -n "$WALLET_ERROR" ]]; then
1464
+ echo " Last Error: $WALLET_ERROR"
1465
+ fi
1466
+ fi
1467
+ echo "Signer"
1468
+ echo " Startup Mode: $SIGNERD_STARTUP_MODE"
1469
+ if [[ -n "$SIGNERD_ERROR" ]]; then
1470
+ echo " Last Error: $SIGNERD_ERROR"
1471
+ fi
1472
+ echo "Admin Access (Tailscale)"
1473
+ echo " Admin URL: $TAILSCALE_ADMIN_URL"
1474
+ if [[ "$TAILSCALE_ADMIN_URL" == "N/A" ]]; then
1475
+ if [[ -n "$TAILSCALE_DNS_NAME" ]]; then
1476
+ echo " Status: DNS present but tailscale serve is not active for 127.0.0.1:${FASED_GATEWAY_PORT}"
1477
+ echo " Fix: tailscale serve --bg http://127.0.0.1:${FASED_GATEWAY_PORT}"
1478
+ else
1479
+ echo " Status: tailscale DNS unavailable; run 'tailscale up --ssh' and verify with 'tailscale status'"
1480
+ fi
1481
+ else
1482
+ echo " Status: tailnet-only URL (not public internet)"
1483
+ fi
1484
+ echo " SSH command: $TAILSCALE_SSH_CMD"
1485
+ echo " Auth: Gateway token/password still required by Fased UI/API"
1486
+ echo " Public login link: disabled (no one-time public dashboard links)"
1487
+ echo "Secrets"
1488
+ echo " Gateway token file: $GW_TOKEN_PATH"
1489
+ echo " Gateway token: $GATEWAY_TOKEN_MASKED"
1490
+ echo " Federation token: $TOKEN_PATH"
1491
+ echo "==========================================================="
1492
+
1493
+ echo ""
1494
+ echo "Stream gateway logs: tail -f $GATEWAY_BOOT_LOG"
1495
+ echo "Stream zrok logs: tail -f $ZROK_RUNTIME_LOG"
1496
+
1497
+ cleanup_managed_runtime() {
1498
+ local current_zrok_pid=""
1499
+ local current_monitor_pid=""
1500
+ if [[ -f "$FASED_CONFIG_DIR/.zrok-pid" ]]; then
1501
+ current_zrok_pid="$(cat "$FASED_CONFIG_DIR/.zrok-pid" 2>/dev/null || true)"
1502
+ fi
1503
+ if [[ -f "$ZROK_MONITOR_PID_FILE" ]]; then
1504
+ current_monitor_pid="$(cat "$ZROK_MONITOR_PID_FILE" 2>/dev/null || true)"
1505
+ fi
1506
+ if [[ -n "${AGENT_PID:-}" ]] && kill -0 "$AGENT_PID" 2>/dev/null; then
1507
+ kill "$AGENT_PID" 2>/dev/null || true
1508
+ fi
1509
+ if [[ -n "$current_zrok_pid" ]] && kill -0 "$current_zrok_pid" 2>/dev/null; then
1510
+ kill "$current_zrok_pid" 2>/dev/null || true
1511
+ fi
1512
+ if [[ -n "${ZROK_PID:-}" ]] && kill -0 "$ZROK_PID" 2>/dev/null; then
1513
+ kill "$ZROK_PID" 2>/dev/null || true
1514
+ fi
1515
+ if [[ -n "$current_monitor_pid" ]] && kill -0 "$current_monitor_pid" 2>/dev/null; then
1516
+ kill "$current_monitor_pid" 2>/dev/null || true
1517
+ fi
1518
+ if [[ -n "${ZROK_MONITOR_PID:-}" ]] && kill -0 "$ZROK_MONITOR_PID" 2>/dev/null; then
1519
+ kill "$ZROK_MONITOR_PID" 2>/dev/null || true
1520
+ fi
1521
+ rm -f "$FASED_CONFIG_DIR/.zrok-pid" "$ZROK_MONITOR_PID_FILE" 2>/dev/null || true
1522
+ stop_existing_signerd >/dev/null 2>&1 || true
1523
+ force_stop_local_gateway
1524
+ }
1525
+
1526
+ MANAGED_RUNTIME_SHUTTING_DOWN=0
1527
+
1528
+ handle_managed_runtime_signal() {
1529
+ MANAGED_RUNTIME_SHUTTING_DOWN=1
1530
+ cleanup_managed_runtime
1531
+ exit 0
1532
+ }
1533
+
1534
+ trap cleanup_managed_runtime EXIT
1535
+ trap handle_managed_runtime_signal INT TERM
1536
+ if kill -0 "$AGENT_PID" 2>/dev/null; then
1537
+ wait "$AGENT_PID" || true
1538
+ fi
1539
+
1540
+ if [[ "$MANAGED_RUNTIME_SHUTTING_DOWN" == "1" ]]; then
1541
+ exit 0
1542
+ fi
1543
+
1544
+ echo "[gateway] supervising listener on port ${FASED_GATEWAY_PORT}."
1545
+ while true; do
1546
+ if ! is_gateway_listener_ready; then
1547
+ echo "[gateway] ERROR: Gateway listener stopped on port ${FASED_GATEWAY_PORT}."
1548
+ exit 1
1549
+ fi
1550
+ sleep 5
1551
+ done