@researai/deepscientist 1.5.14 → 1.5.16

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 (225) hide show
  1. package/README.md +336 -90
  2. package/assets/branding/logo-raster.png +0 -0
  3. package/bin/ds.js +816 -131
  4. package/docs/en/00_QUICK_START.md +36 -15
  5. package/docs/en/01_SETTINGS_REFERENCE.md +53 -4
  6. package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
  7. package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
  8. package/docs/en/05_TUI_GUIDE.md +6 -0
  9. package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
  10. package/docs/en/09_DOCTOR.md +11 -5
  11. package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
  12. package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
  13. package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
  14. package/docs/en/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
  15. package/docs/en/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
  16. package/docs/en/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
  17. package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
  18. package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
  19. package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
  20. package/docs/en/README.md +24 -0
  21. package/docs/zh/00_QUICK_START.md +36 -15
  22. package/docs/zh/01_SETTINGS_REFERENCE.md +53 -4
  23. package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
  24. package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
  25. package/docs/zh/05_TUI_GUIDE.md +6 -0
  26. package/docs/zh/09_DOCTOR.md +11 -5
  27. package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
  28. package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
  29. package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
  30. package/docs/zh/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
  31. package/docs/zh/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
  32. package/docs/zh/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
  33. package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
  34. package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
  35. package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
  36. package/docs/zh/README.md +24 -0
  37. package/install.sh +2 -0
  38. package/package.json +1 -1
  39. package/pyproject.toml +1 -1
  40. package/src/deepscientist/__init__.py +1 -1
  41. package/src/deepscientist/acp/envelope.py +6 -0
  42. package/src/deepscientist/artifact/charts.py +567 -0
  43. package/src/deepscientist/artifact/guidance.py +50 -10
  44. package/src/deepscientist/artifact/metrics.py +228 -5
  45. package/src/deepscientist/artifact/schemas.py +3 -0
  46. package/src/deepscientist/artifact/service.py +4276 -308
  47. package/src/deepscientist/bash_exec/models.py +23 -0
  48. package/src/deepscientist/bash_exec/monitor.py +147 -67
  49. package/src/deepscientist/bash_exec/runtime.py +218 -156
  50. package/src/deepscientist/bash_exec/service.py +309 -69
  51. package/src/deepscientist/bash_exec/shells.py +87 -0
  52. package/src/deepscientist/bridges/connectors.py +51 -2
  53. package/src/deepscientist/cli.py +115 -19
  54. package/src/deepscientist/codex_cli_compat.py +232 -0
  55. package/src/deepscientist/config/models.py +8 -4
  56. package/src/deepscientist/config/service.py +38 -11
  57. package/src/deepscientist/connector/weixin_support.py +122 -1
  58. package/src/deepscientist/daemon/api/handlers.py +199 -9
  59. package/src/deepscientist/daemon/api/router.py +5 -0
  60. package/src/deepscientist/daemon/app.py +1458 -289
  61. package/src/deepscientist/doctor.py +51 -0
  62. package/src/deepscientist/file_lock.py +48 -0
  63. package/src/deepscientist/gitops/__init__.py +10 -1
  64. package/src/deepscientist/gitops/diff.py +296 -1
  65. package/src/deepscientist/gitops/service.py +4 -1
  66. package/src/deepscientist/mcp/server.py +212 -5
  67. package/src/deepscientist/process_control.py +161 -0
  68. package/src/deepscientist/prompts/builder.py +501 -453
  69. package/src/deepscientist/quest/layout.py +15 -2
  70. package/src/deepscientist/quest/service.py +2539 -195
  71. package/src/deepscientist/quest/stage_views.py +177 -1
  72. package/src/deepscientist/runners/base.py +2 -0
  73. package/src/deepscientist/runners/codex.py +169 -31
  74. package/src/deepscientist/runners/runtime_overrides.py +17 -1
  75. package/src/deepscientist/skills/__init__.py +2 -2
  76. package/src/deepscientist/skills/installer.py +196 -5
  77. package/src/deepscientist/skills/registry.py +66 -0
  78. package/src/prompts/connectors/qq.md +18 -8
  79. package/src/prompts/connectors/weixin.md +16 -6
  80. package/src/prompts/contracts/shared_interaction.md +24 -4
  81. package/src/prompts/system.md +921 -72
  82. package/src/prompts/system_copilot.md +43 -0
  83. package/src/skills/analysis-campaign/SKILL.md +32 -2
  84. package/src/skills/analysis-campaign/references/artifact-orchestration.md +1 -1
  85. package/src/skills/analysis-campaign/references/writing-facing-slice-examples.md +65 -0
  86. package/src/skills/baseline/SKILL.md +10 -0
  87. package/src/skills/decision/SKILL.md +27 -2
  88. package/src/skills/experiment/SKILL.md +16 -2
  89. package/src/skills/figure-polish/SKILL.md +1 -0
  90. package/src/skills/finalize/SKILL.md +19 -0
  91. package/src/skills/idea/SKILL.md +79 -0
  92. package/src/skills/idea/references/idea-generation-playbook.md +100 -0
  93. package/src/skills/idea/references/outline-seeding-example.md +60 -0
  94. package/src/skills/intake-audit/SKILL.md +9 -1
  95. package/src/skills/mentor/SKILL.md +217 -0
  96. package/src/skills/mentor/references/correction-rules.md +210 -0
  97. package/src/skills/mentor/references/knowledge-profile.md +91 -0
  98. package/src/skills/mentor/references/persona-profile.md +138 -0
  99. package/src/skills/mentor/references/taste-profile.md +128 -0
  100. package/src/skills/mentor/references/thought-style-profile.md +138 -0
  101. package/src/skills/mentor/references/work-profile.md +289 -0
  102. package/src/skills/mentor/references/workflow-profile.md +240 -0
  103. package/src/skills/optimize/SKILL.md +1645 -0
  104. package/src/skills/rebuttal/SKILL.md +3 -1
  105. package/src/skills/review/SKILL.md +3 -1
  106. package/src/skills/scout/SKILL.md +8 -0
  107. package/src/skills/write/SKILL.md +81 -12
  108. package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
  109. package/src/tui/dist/app/AppContainer.js +22 -11
  110. package/src/tui/dist/index.js +4 -1
  111. package/src/tui/dist/lib/api.js +33 -3
  112. package/src/tui/package.json +1 -1
  113. package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
  114. package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
  115. package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
  116. package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
  117. package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
  118. package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
  119. package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
  120. package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
  121. package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
  122. package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
  123. package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
  124. package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
  125. package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
  126. package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
  127. package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
  128. package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
  129. package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
  130. package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
  131. package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
  132. package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
  133. package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
  134. package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
  135. package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
  136. package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
  137. package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
  138. package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
  139. package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
  140. package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
  141. package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
  142. package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
  143. package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
  144. package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
  145. package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
  146. package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
  147. package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
  148. package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
  149. package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
  150. package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
  151. package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
  152. package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
  153. package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
  154. package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
  155. package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
  156. package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
  157. package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
  158. package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
  159. package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
  160. package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
  161. package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
  162. package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
  163. package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
  164. package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
  165. package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
  166. package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
  167. package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
  168. package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
  169. package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
  170. package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
  171. package/src/ui/dist/index.html +5 -2
  172. package/src/ui/dist/assets/AiManusChatView-DaF9Nge_.js +0 -26597
  173. package/src/ui/dist/assets/AnalysisPlugin-BSVx6dXE.js +0 -123
  174. package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
  175. package/src/ui/dist/assets/CodeEditorPlugin-DU9G0Tox.js +0 -427
  176. package/src/ui/dist/assets/CodeViewerPlugin-DoX_fI9l.js +0 -905
  177. package/src/ui/dist/assets/DocViewerPlugin-C4FWIXuU.js +0 -278
  178. package/src/ui/dist/assets/GitDiffViewerPlugin-BgfFMgtf.js +0 -2661
  179. package/src/ui/dist/assets/ImageViewerPlugin-tcPkfY_x.js +0 -500
  180. package/src/ui/dist/assets/LabCopilotPanel-_dKV60Bf.js +0 -4104
  181. package/src/ui/dist/assets/LabPlugin-Bje0ayoC.js +0 -2677
  182. package/src/ui/dist/assets/LatexPlugin-CVsBzAln.js +0 -1792
  183. package/src/ui/dist/assets/MarkdownViewerPlugin-xjmrqv_8.js +0 -308
  184. package/src/ui/dist/assets/MarketplacePlugin-mMM2A8wP.js +0 -413
  185. package/src/ui/dist/assets/NotebookEditor-3kVDSOBo.js +0 -4214
  186. package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
  187. package/src/ui/dist/assets/NotebookEditor-SoJ8X-MO.js +0 -84873
  188. package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
  189. package/src/ui/dist/assets/PdfLoader-DElVuHl9.js +0 -25468
  190. package/src/ui/dist/assets/PdfMarkdownPlugin-Bq88XT4G.js +0 -409
  191. package/src/ui/dist/assets/PdfViewerPlugin-CsCXMo9S.js +0 -3095
  192. package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
  193. package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
  194. package/src/ui/dist/assets/SearchPlugin-oUPvy19k.js +0 -741
  195. package/src/ui/dist/assets/TextViewerPlugin-CRkT9yNy.js +0 -472
  196. package/src/ui/dist/assets/VNCViewer-BgbuvWhR.js +0 -18821
  197. package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
  198. package/src/ui/dist/assets/bot-v_RASACv.js +0 -21
  199. package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
  200. package/src/ui/dist/assets/code-5hC9d0VH.js +0 -17
  201. package/src/ui/dist/assets/file-content-D1PxfOrp.js +0 -377
  202. package/src/ui/dist/assets/file-diff-panel-DG1oT_Hj.js +0 -92
  203. package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
  204. package/src/ui/dist/assets/file-socket-BmdFYQlk.js +0 -58
  205. package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
  206. package/src/ui/dist/assets/image-Dqe2X2tW.js +0 -18
  207. package/src/ui/dist/assets/index-BQG-1s2o.css +0 -12553
  208. package/src/ui/dist/assets/index-DVsMKK_y.js +0 -25
  209. package/src/ui/dist/assets/index-Duvz8Ip0.js +0 -159
  210. package/src/ui/dist/assets/index-Nt9hS4ck.js +0 -244829
  211. package/src/ui/dist/assets/index-RDlNXXx1.js +0 -120
  212. package/src/ui/dist/assets/monaco-DIXge1CP.js +0 -623
  213. package/src/ui/dist/assets/pdf-effect-queue-BBTTQaO-.js +0 -47
  214. package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
  215. package/src/ui/dist/assets/popover-BWlolyxo.js +0 -476
  216. package/src/ui/dist/assets/project-sync-BM5PkFH4.js +0 -297
  217. package/src/ui/dist/assets/select-D4dAtrA8.js +0 -1690
  218. package/src/ui/dist/assets/sigma-CKbE5jJT.js +0 -22
  219. package/src/ui/dist/assets/square-check-big-CZNGMgiB.js +0 -17
  220. package/src/ui/dist/assets/trash-DaB37xAz.js +0 -32
  221. package/src/ui/dist/assets/useCliAccess-C2OmAcWe.js +0 -957
  222. package/src/ui/dist/assets/useFileDiffOverlay-Dowd1Ij4.js +0 -53
  223. package/src/ui/dist/assets/wrap-text-BGjAhAUq.js +0 -35
  224. package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
  225. package/src/ui/dist/assets/zoom-out-dMZQMXzc.js +0 -34
@@ -1,957 +0,0 @@
1
- import { x as supportsSocketIo, D as resolveApiBaseUrl, y as lookup, z as useAuthStore, O as ConnectionState, r as reactExports, Y as checkProjectAccess } from './index-Nt9hS4ck.js';
2
-
3
- const SOCKET_CACHE = /* @__PURE__ */ new Map();
4
- const CLIENT_ID_KEY = "ds_cli_client_id";
5
- let cachedClientId = null;
6
- function createNoopCliSocket() {
7
- const socket = {
8
- connected: false,
9
- connect: () => socket,
10
- disconnect: () => socket,
11
- on: () => socket,
12
- off: () => socket,
13
- emit: () => true
14
- };
15
- return socket;
16
- }
17
- function getCliClientId() {
18
- if (cachedClientId) return cachedClientId;
19
- let clientId = "";
20
- if (typeof window !== "undefined") {
21
- try {
22
- clientId = window.localStorage.getItem(CLIENT_ID_KEY) || "";
23
- } catch {
24
- clientId = "";
25
- }
26
- }
27
- if (!clientId) {
28
- try {
29
- clientId = crypto.randomUUID();
30
- } catch {
31
- clientId = `cli-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
32
- }
33
- if (typeof window !== "undefined") {
34
- try {
35
- window.localStorage.setItem(CLIENT_ID_KEY, clientId);
36
- } catch {
37
- }
38
- }
39
- }
40
- cachedClientId = clientId;
41
- return clientId;
42
- }
43
- function acquireCliSocket(options = {}) {
44
- if (!supportsSocketIo()) {
45
- return {
46
- socket: createNoopCliSocket(),
47
- release: () => {
48
- }
49
- };
50
- }
51
- const endpoint = resolveApiBaseUrl();
52
- const cacheKey = `${endpoint}::user`;
53
- let entry = SOCKET_CACHE.get(cacheKey);
54
- if (!entry) {
55
- const socket = lookup(`${endpoint}/cli`, {
56
- path: "/ws/socket.io",
57
- autoConnect: false,
58
- transports: ["websocket", "polling"],
59
- auth: (cb) => {
60
- const client_id = getCliClientId();
61
- const protocol_version = "cli.v1";
62
- const token = useAuthStore.getState().accessToken || (typeof window !== "undefined" ? window.localStorage.getItem("ds_access_token") : null);
63
- cb({ token: token || null, client_id, protocol_version });
64
- }
65
- });
66
- entry = { socket, refCount: 0 };
67
- SOCKET_CACHE.set(cacheKey, entry);
68
- }
69
- entry.refCount += 1;
70
- if (!entry.socket.connected) {
71
- entry.socket.connect();
72
- }
73
- return {
74
- socket: entry.socket,
75
- release: () => {
76
- const current = SOCKET_CACHE.get(cacheKey);
77
- if (!current) return;
78
- current.refCount -= 1;
79
- if (current.refCount <= 0) {
80
- current.socket.disconnect();
81
- SOCKET_CACHE.delete(cacheKey);
82
- }
83
- }
84
- };
85
- }
86
- function unwrapPayload(data) {
87
- if (data && typeof data === "object" && "payload" in data) {
88
- return data.payload;
89
- }
90
- return data;
91
- }
92
-
93
- let seqCounter = 0;
94
- function nextSeq() {
95
- seqCounter += 1;
96
- return seqCounter;
97
- }
98
- function wrapEnvelope(payload, meta = {}) {
99
- return {
100
- protocol_version: "cli.v1",
101
- ts: (/* @__PURE__ */ new Date()).toISOString(),
102
- payload,
103
- ...meta
104
- };
105
- }
106
-
107
- const connectionTransitions = {
108
- [ConnectionState.DISCONNECTED]: {
109
- CONNECT: ConnectionState.CONNECTING
110
- },
111
- [ConnectionState.CONNECTING]: {
112
- AUTH_SUCCESS: ConnectionState.AUTHENTICATING,
113
- DISCONNECT: ConnectionState.DISCONNECTED,
114
- CONNECTION_LOST: ConnectionState.RECONNECTING
115
- },
116
- [ConnectionState.AUTHENTICATING]: {
117
- AUTH_SUCCESS: ConnectionState.CONNECTED,
118
- AUTH_FAILURE: ConnectionState.FAILED,
119
- CONNECTION_LOST: ConnectionState.RECONNECTING
120
- },
121
- [ConnectionState.CONNECTED]: {
122
- DISCONNECT: ConnectionState.DISCONNECTED,
123
- CONNECTION_LOST: ConnectionState.RECONNECTING,
124
- SUSPEND: ConnectionState.SUSPENDED
125
- },
126
- [ConnectionState.RECONNECTING]: {
127
- RECONNECT_SUCCESS: ConnectionState.CONNECTED,
128
- MAX_RETRIES_EXCEEDED: ConnectionState.FAILED,
129
- DISCONNECT: ConnectionState.DISCONNECTED
130
- },
131
- [ConnectionState.SUSPENDED]: {
132
- RESUME: ConnectionState.CONNECTING,
133
- DISCONNECT: ConnectionState.DISCONNECTED
134
- },
135
- [ConnectionState.FAILED]: {
136
- CONNECT: ConnectionState.CONNECTING,
137
- DISCONNECT: ConnectionState.DISCONNECTED
138
- }
139
- };
140
- class ConnectionStateMachine {
141
- state = ConnectionState.DISCONNECTED;
142
- listeners = /* @__PURE__ */ new Set();
143
- getState() {
144
- return this.state;
145
- }
146
- sendEvent(event) {
147
- const nextState = connectionTransitions[this.state]?.[event.type];
148
- if (!nextState) {
149
- console.warn(`Invalid connection transition: ${this.state} -> ${event.type}`);
150
- return false;
151
- }
152
- const prevState = this.state;
153
- this.state = nextState;
154
- this.notifyListeners(nextState, event);
155
- if (prevState !== nextState) {
156
- console.debug(`[CLI] Connection state: ${prevState} -> ${nextState} (${event.type})`);
157
- }
158
- return true;
159
- }
160
- subscribe(listener) {
161
- this.listeners.add(listener);
162
- return () => this.listeners.delete(listener);
163
- }
164
- canSendEvent(eventType) {
165
- return Boolean(connectionTransitions[this.state]?.[eventType]);
166
- }
167
- reset() {
168
- this.state = ConnectionState.DISCONNECTED;
169
- }
170
- notifyListeners(state, event) {
171
- this.listeners.forEach((listener) => listener(state, event));
172
- }
173
- }
174
-
175
- class ConnectionMetricsCollector {
176
- metrics = {
177
- totalConnections: 0,
178
- successfulConnections: 0,
179
- failedConnections: 0,
180
- reconnectionAttempts: 0,
181
- averageReconnectTime: 0,
182
- messagesBuffered: 0,
183
- messagesDropped: 0
184
- };
185
- recordConnection() {
186
- this.metrics.totalConnections += 1;
187
- }
188
- recordConnectionSuccess() {
189
- this.metrics.successfulConnections += 1;
190
- }
191
- recordConnectionFailure() {
192
- this.metrics.failedConnections += 1;
193
- }
194
- recordReconnectionAttempt(durationMs) {
195
- this.metrics.reconnectionAttempts += 1;
196
- const attempts = this.metrics.reconnectionAttempts;
197
- this.metrics.averageReconnectTime = (this.metrics.averageReconnectTime * (attempts - 1) + durationMs) / attempts;
198
- }
199
- recordMessageBuffered(count = 1) {
200
- this.metrics.messagesBuffered += count;
201
- }
202
- recordMessageDropped(count = 1) {
203
- this.metrics.messagesDropped += count;
204
- }
205
- getMetrics() {
206
- return { ...this.metrics };
207
- }
208
- }
209
-
210
- class MessageBuffer {
211
- buffer = [];
212
- maxSize;
213
- onOverflow;
214
- constructor(options = {}) {
215
- this.maxSize = options.maxSize ?? 1e3;
216
- this.onOverflow = options.onOverflow ?? "drop_oldest";
217
- }
218
- enqueue(message) {
219
- this.pruneExpired();
220
- if (this.buffer.length >= this.maxSize) {
221
- switch (this.onOverflow) {
222
- case "drop_oldest": {
223
- this.buffer.sort((a, b) => {
224
- const priorityOrder = { high: 0, normal: 1, low: 2 };
225
- return priorityOrder[b.priority] - priorityOrder[a.priority] || a.timestamp - b.timestamp;
226
- });
227
- this.buffer.pop();
228
- break;
229
- }
230
- case "drop_newest":
231
- return false;
232
- case "reject":
233
- throw new Error("Message buffer overflow");
234
- }
235
- }
236
- const bufferedMessage = {
237
- ...message,
238
- id: crypto.randomUUID(),
239
- timestamp: Date.now()
240
- };
241
- this.buffer.push(bufferedMessage);
242
- return true;
243
- }
244
- flush() {
245
- this.pruneExpired();
246
- const messages = [...this.buffer].sort((a, b) => {
247
- const priorityOrder = { high: 0, normal: 1, low: 2 };
248
- const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
249
- return priorityDiff !== 0 ? priorityDiff : a.timestamp - b.timestamp;
250
- });
251
- this.buffer = [];
252
- return messages;
253
- }
254
- getStatus() {
255
- return {
256
- size: this.buffer.length,
257
- maxSize: this.maxSize,
258
- oldestTimestamp: this.buffer.length > 0 ? Math.min(...this.buffer.map((message) => message.timestamp)) : null
259
- };
260
- }
261
- clear() {
262
- this.buffer = [];
263
- }
264
- countByPriority(priority) {
265
- return this.buffer.filter((message) => message.priority === priority).length;
266
- }
267
- pruneExpired() {
268
- const now = Date.now();
269
- this.buffer = this.buffer.filter((message) => {
270
- if (!message.maxAge) return true;
271
- return now - message.timestamp < message.maxAge;
272
- });
273
- }
274
- }
275
-
276
- const DEFAULT_RECONNECTION_CONFIG = {
277
- initialDelay: 1e3,
278
- maxDelay: 3e5,
279
- maxRetries: 30,
280
- backoffMultiplier: 1.5,
281
- jitterFactor: 0.2
282
- };
283
- class ReconnectionManager {
284
- config;
285
- retryCount = 0;
286
- currentTimeout = null;
287
- listeners = /* @__PURE__ */ new Set();
288
- constructor(config = {}) {
289
- this.config = { ...DEFAULT_RECONNECTION_CONFIG, ...config };
290
- }
291
- calculateDelay() {
292
- const baseDelay = Math.min(
293
- this.config.initialDelay * Math.pow(this.config.backoffMultiplier, this.retryCount),
294
- this.config.maxDelay
295
- );
296
- const jitter = baseDelay * this.config.jitterFactor * (Math.random() * 2 - 1);
297
- return Math.max(0, Math.floor(baseDelay + jitter));
298
- }
299
- scheduleReconnect(onReconnect) {
300
- if (this.retryCount >= this.config.maxRetries) {
301
- this.notifyListeners({
302
- status: "failed",
303
- message: `Max retries reached (${this.config.maxRetries})`
304
- });
305
- return;
306
- }
307
- const delay = this.calculateDelay();
308
- this.retryCount += 1;
309
- this.notifyListeners({
310
- status: "waiting",
311
- retryCount: this.retryCount,
312
- maxRetries: this.config.maxRetries,
313
- nextRetryIn: delay
314
- });
315
- this.currentTimeout = setTimeout(async () => {
316
- this.notifyListeners({ status: "reconnecting", retryCount: this.retryCount });
317
- try {
318
- const success = await onReconnect();
319
- if (success) {
320
- this.reset();
321
- this.notifyListeners({ status: "connected" });
322
- return;
323
- }
324
- } catch (error) {
325
- console.error("[CLI] Reconnect failed:", error);
326
- }
327
- this.scheduleReconnect(onReconnect);
328
- }, delay);
329
- }
330
- reset() {
331
- this.retryCount = 0;
332
- if (this.currentTimeout) {
333
- clearTimeout(this.currentTimeout);
334
- this.currentTimeout = null;
335
- }
336
- }
337
- cancel() {
338
- this.reset();
339
- this.notifyListeners({ status: "cancelled" });
340
- }
341
- retryNow(onReconnect) {
342
- this.cancel();
343
- this.retryCount = 0;
344
- this.scheduleReconnect(onReconnect);
345
- }
346
- subscribe(listener) {
347
- this.listeners.add(listener);
348
- return () => this.listeners.delete(listener);
349
- }
350
- getRetryCount() {
351
- return this.retryCount;
352
- }
353
- notifyListeners(state) {
354
- this.listeners.forEach((listener) => listener(state));
355
- }
356
- }
357
-
358
- const CLI_OFFLINE_MESSAGE = "CLI server offline. Please ensure the CLI is running.";
359
- const normalizeGatewayError = (message) => {
360
- const lower = message.toLowerCase();
361
- if (lower.includes("cli server") && (lower.includes("not connected") || lower.includes("offline")) || lower.includes("cli_server_not_connected") || lower.includes("cli_server_offline")) {
362
- return CLI_OFFLINE_MESSAGE;
363
- }
364
- return message;
365
- };
366
- function useCliSocket(options) {
367
- const { projectId, serverId, handlers } = options;
368
- const clientIdRef = reactExports.useRef(getCliClientId());
369
- const reconnectRef = reactExports.useRef(new ReconnectionManager());
370
- const reconnectingRef = reactExports.useRef(false);
371
- const bufferRef = reactExports.useRef(new MessageBuffer());
372
- const stateMachineRef = reactExports.useRef(new ConnectionStateMachine());
373
- const metricsRef = reactExports.useRef(new ConnectionMetricsCollector());
374
- const reconnectStartedAtRef = reactExports.useRef(null);
375
- const subscribedRef = reactExports.useRef(false);
376
- const subscribeInFlightRef = reactExports.useRef(null);
377
- const scheduleReconnectRef = reactExports.useRef(() => {
378
- });
379
- const [status, setStatus] = reactExports.useState(() => ({
380
- state: stateMachineRef.current.getState(),
381
- bufferedMessages: 0,
382
- metrics: metricsRef.current.getMetrics()
383
- }));
384
- const { socket, release } = reactExports.useMemo(() => acquireCliSocket(), []);
385
- const syncMetrics = reactExports.useCallback(() => {
386
- setStatus((prev) => ({
387
- ...prev,
388
- metrics: metricsRef.current.getMetrics()
389
- }));
390
- }, []);
391
- const updateBufferedStatus = reactExports.useCallback(() => {
392
- const bufferStatus = bufferRef.current.getStatus();
393
- setStatus((prev) => ({
394
- ...prev,
395
- bufferedMessages: bufferStatus.size,
396
- metrics: metricsRef.current.getMetrics()
397
- }));
398
- }, []);
399
- const emitEnvelope = reactExports.useCallback(
400
- (event, payload, meta = {}, options2) => {
401
- if (!projectId || !serverId) return;
402
- const envelope = wrapEnvelope(payload, {
403
- project_id: projectId,
404
- server_id: serverId,
405
- client_id: clientIdRef.current,
406
- ...meta
407
- });
408
- if (!socket.connected) {
409
- try {
410
- const accepted = bufferRef.current.enqueue({
411
- type: event,
412
- payload: envelope,
413
- priority: options2?.priority ?? "normal",
414
- maxAge: options2?.maxAge
415
- });
416
- if (!accepted) {
417
- metricsRef.current.recordMessageDropped();
418
- syncMetrics();
419
- handlers?.onError?.("Message buffer full; dropping newest message");
420
- } else {
421
- metricsRef.current.recordMessageBuffered();
422
- }
423
- } catch (error) {
424
- metricsRef.current.recordMessageDropped();
425
- syncMetrics();
426
- handlers?.onError?.(error instanceof Error ? error.message : "Message buffer error");
427
- }
428
- updateBufferedStatus();
429
- return;
430
- }
431
- socket.emit(event, envelope);
432
- },
433
- [projectId, serverId, socket, handlers, updateBufferedStatus, syncMetrics]
434
- );
435
- const flushBuffer = reactExports.useCallback(() => {
436
- if (!socket.connected) return;
437
- const messages = bufferRef.current.flush();
438
- messages.forEach((message) => socket.emit(message.type, message.payload));
439
- updateBufferedStatus();
440
- }, [socket, updateBufferedStatus]);
441
- const subscribe = reactExports.useCallback(async () => {
442
- if (!projectId || !serverId) return false;
443
- if (subscribeInFlightRef.current) {
444
- return subscribeInFlightRef.current;
445
- }
446
- const startedAt = Date.now();
447
- const envelope = wrapEnvelope({}, {
448
- project_id: projectId,
449
- server_id: serverId,
450
- client_id: clientIdRef.current
451
- });
452
- metricsRef.current.recordConnection();
453
- syncMetrics();
454
- const promise = new Promise((resolve) => {
455
- socket.timeout(3e3).emit("cli:subscribe", envelope, (err) => {
456
- subscribeInFlightRef.current = null;
457
- if (err) {
458
- subscribedRef.current = false;
459
- metricsRef.current.recordConnectionFailure();
460
- syncMetrics();
461
- const currentState2 = stateMachineRef.current.getState();
462
- if (currentState2 === ConnectionState.AUTHENTICATING && stateMachineRef.current.canSendEvent("AUTH_FAILURE")) {
463
- stateMachineRef.current.sendEvent({ type: "AUTH_FAILURE", error: err.message });
464
- }
465
- if (currentState2 === ConnectionState.RECONNECTING) {
466
- scheduleReconnectRef.current();
467
- }
468
- setStatus((prev) => ({ ...prev, lastError: err.message }));
469
- resolve(false);
470
- return;
471
- }
472
- const latencyMs = Date.now() - startedAt;
473
- const currentState = stateMachineRef.current.getState();
474
- if (currentState === ConnectionState.RECONNECTING) {
475
- stateMachineRef.current.sendEvent({ type: "RECONNECT_SUCCESS" });
476
- } else if (currentState === ConnectionState.AUTHENTICATING) {
477
- stateMachineRef.current.sendEvent({ type: "AUTH_SUCCESS" });
478
- }
479
- subscribedRef.current = true;
480
- metricsRef.current.recordConnectionSuccess();
481
- syncMetrics();
482
- reconnectRef.current.reset();
483
- reconnectingRef.current = false;
484
- setStatus((prev) => ({
485
- ...prev,
486
- latencyMs,
487
- lastConnectedAt: Date.now()
488
- }));
489
- resolve(true);
490
- });
491
- });
492
- subscribeInFlightRef.current = promise;
493
- return promise;
494
- }, [projectId, serverId, socket, syncMetrics]);
495
- const attemptReconnect = reactExports.useCallback(async () => {
496
- if (socket.connected && subscribedRef.current) return true;
497
- if (!socket.connected) {
498
- const connected = await new Promise((resolve) => {
499
- let settled = false;
500
- const timeout = window.setTimeout(() => {
501
- if (settled) return;
502
- settled = true;
503
- cleanup();
504
- resolve(false);
505
- }, 4e3);
506
- const handleConnect = () => {
507
- if (settled) return;
508
- settled = true;
509
- cleanup();
510
- resolve(true);
511
- };
512
- const handleError = () => {
513
- if (settled) return;
514
- settled = true;
515
- cleanup();
516
- resolve(false);
517
- };
518
- const cleanup = () => {
519
- window.clearTimeout(timeout);
520
- socket.off("connect", handleConnect);
521
- socket.off("connect_error", handleError);
522
- };
523
- socket.once("connect", handleConnect);
524
- socket.once("connect_error", handleError);
525
- socket.connect();
526
- });
527
- if (!connected) return false;
528
- }
529
- const subscribed = await subscribe();
530
- if (subscribed) {
531
- flushBuffer();
532
- }
533
- return subscribed;
534
- }, [socket, subscribe, flushBuffer]);
535
- const scheduleReconnect = reactExports.useCallback(() => {
536
- if (reconnectingRef.current) return;
537
- reconnectingRef.current = true;
538
- reconnectRef.current.scheduleReconnect(attemptReconnect);
539
- }, [attemptReconnect]);
540
- scheduleReconnectRef.current = scheduleReconnect;
541
- reactExports.useEffect(() => {
542
- const stateMachine = stateMachineRef.current;
543
- const unsubscribeState = stateMachine.subscribe((nextState, event) => {
544
- setStatus((prev) => ({
545
- ...prev,
546
- state: nextState,
547
- lastError: event.type === "AUTH_FAILURE" ? event.error : prev.lastError
548
- }));
549
- });
550
- const unsubscribeReconnect = reconnectRef.current.subscribe((reconnectState) => {
551
- if (reconnectState.status === "waiting") {
552
- reconnectStartedAtRef.current = null;
553
- setStatus((prev) => ({
554
- ...prev,
555
- reconnectAttempts: reconnectState.retryCount,
556
- nextRetryIn: reconnectState.nextRetryIn
557
- }));
558
- return;
559
- }
560
- if (reconnectState.status === "reconnecting") {
561
- if (!reconnectStartedAtRef.current) {
562
- reconnectStartedAtRef.current = Date.now();
563
- }
564
- setStatus((prev) => ({
565
- ...prev,
566
- reconnectAttempts: reconnectState.retryCount,
567
- nextRetryIn: void 0
568
- }));
569
- return;
570
- }
571
- if (reconnectState.status === "failed") {
572
- reconnectingRef.current = false;
573
- metricsRef.current.recordConnectionFailure();
574
- syncMetrics();
575
- if (stateMachine.canSendEvent("MAX_RETRIES_EXCEEDED")) {
576
- stateMachine.sendEvent({ type: "MAX_RETRIES_EXCEEDED" });
577
- }
578
- setStatus((prev) => ({
579
- ...prev,
580
- lastError: reconnectState.message
581
- }));
582
- return;
583
- }
584
- if (reconnectState.status === "connected") {
585
- reconnectingRef.current = false;
586
- if (reconnectStartedAtRef.current) {
587
- metricsRef.current.recordReconnectionAttempt(
588
- Date.now() - reconnectStartedAtRef.current
589
- );
590
- syncMetrics();
591
- reconnectStartedAtRef.current = null;
592
- }
593
- setStatus((prev) => ({
594
- ...prev,
595
- reconnectAttempts: 0,
596
- nextRetryIn: void 0
597
- }));
598
- return;
599
- }
600
- if (reconnectState.status === "cancelled") {
601
- reconnectingRef.current = false;
602
- reconnectStartedAtRef.current = null;
603
- setStatus((prev) => ({
604
- ...prev,
605
- reconnectAttempts: 0,
606
- nextRetryIn: void 0
607
- }));
608
- }
609
- });
610
- return () => {
611
- unsubscribeState();
612
- unsubscribeReconnect();
613
- };
614
- }, [syncMetrics]);
615
- reactExports.useEffect(() => {
616
- if (!projectId || !serverId) return;
617
- const stateMachine = stateMachineRef.current;
618
- if (stateMachine.canSendEvent("CONNECT")) {
619
- stateMachine.sendEvent({ type: "CONNECT" });
620
- }
621
- const handleConnect = async () => {
622
- const currentState = stateMachine.getState();
623
- if (currentState === ConnectionState.CONNECTING && stateMachine.canSendEvent("AUTH_SUCCESS")) {
624
- stateMachine.sendEvent({ type: "AUTH_SUCCESS" });
625
- }
626
- if (currentState !== ConnectionState.RECONNECTING) {
627
- const subscribed = await subscribe();
628
- if (subscribed) {
629
- flushBuffer();
630
- }
631
- }
632
- };
633
- const handleDisconnect = (reason) => {
634
- subscribedRef.current = false;
635
- if (stateMachine.canSendEvent("CONNECTION_LOST")) {
636
- stateMachine.sendEvent({ type: "CONNECTION_LOST" });
637
- }
638
- if (reason) {
639
- setStatus((prev) => ({ ...prev, lastError: `Disconnected: ${reason}` }));
640
- }
641
- scheduleReconnect();
642
- };
643
- const handleError = (error) => {
644
- subscribedRef.current = false;
645
- const message = error instanceof Error ? error.message : "Connection error";
646
- setStatus((prev) => ({ ...prev, lastError: message }));
647
- handlers?.onError?.(message);
648
- metricsRef.current.recordConnectionFailure();
649
- syncMetrics();
650
- if (stateMachine.canSendEvent("CONNECTION_LOST")) {
651
- stateMachine.sendEvent({ type: "CONNECTION_LOST" });
652
- }
653
- scheduleReconnect();
654
- };
655
- const handleTerminalOutput = (payload) => {
656
- handlers?.onTerminalOutput?.(payload);
657
- };
658
- const handleStatusUpdate = (payload) => {
659
- handlers?.onStatusUpdate?.(payload);
660
- };
661
- const handleFileResponse = (payload) => {
662
- handlers?.onFileResponse?.(payload);
663
- };
664
- const handleConfirm = (payload) => {
665
- handlers?.onConfirmRequired?.(payload);
666
- };
667
- const handleBlocked = (payload) => {
668
- handlers?.onBlocked?.(payload);
669
- };
670
- const handleSessionCreated = (payload) => {
671
- handlers?.onSessionCreated?.(payload);
672
- };
673
- const handleSessionAttached = (payload) => {
674
- handlers?.onSessionAttached?.(payload);
675
- };
676
- const handleSessionDetached = (payload) => {
677
- handlers?.onSessionDetached?.(payload);
678
- };
679
- const handleSessionClosed = (payload) => {
680
- handlers?.onSessionClosed?.(payload);
681
- };
682
- const handleSessionError = (payload) => {
683
- handlers?.onSessionError?.(payload);
684
- };
685
- const handleMethodStatus = (payload) => {
686
- handlers?.onMethodStatus?.(payload);
687
- };
688
- const handleMethodDone = (payload) => {
689
- handlers?.onMethodDone?.(payload);
690
- };
691
- const handleMethodError = (payload) => {
692
- handlers?.onMethodError?.(payload);
693
- };
694
- const handleMethodListResponse = (payload) => {
695
- handlers?.onMethodListResponse?.(payload);
696
- };
697
- const handleMethodGetResponse = (payload) => {
698
- handlers?.onMethodGetResponse?.(payload);
699
- };
700
- const handleGatewayError = (payload) => {
701
- const message = typeof payload?.message === "string" && payload.message ? payload.message : "CLI server error";
702
- const eventLabel = typeof payload?.event === "string" && payload.event ? ` (${payload.event})` : "";
703
- const normalized = normalizeGatewayError(message);
704
- handlers?.onError?.(`${normalized}${eventLabel}`);
705
- };
706
- socket.on("connect", handleConnect);
707
- socket.on("disconnect", handleDisconnect);
708
- socket.on("connect_error", handleError);
709
- socket.on("cli:terminal:output", handleTerminalOutput);
710
- socket.on("cli:status:update", handleStatusUpdate);
711
- socket.on("cli:file:list:response", handleFileResponse);
712
- socket.on("cli:file:content:response", handleFileResponse);
713
- socket.on("cli:file:download:response", handleFileResponse);
714
- socket.on("cli:file:write:response", handleFileResponse);
715
- socket.on("cli:file:delete:response", handleFileResponse);
716
- socket.on("cli:terminal:confirm_required", handleConfirm);
717
- socket.on("cli:terminal:blocked", handleBlocked);
718
- socket.on("cli:session:created", handleSessionCreated);
719
- socket.on("cli:session:attached", handleSessionAttached);
720
- socket.on("cli:session:detached", handleSessionDetached);
721
- socket.on("cli:session:closed", handleSessionClosed);
722
- socket.on("cli:session:error", handleSessionError);
723
- socket.on("cli:method:status", handleMethodStatus);
724
- socket.on("cli:method:done", handleMethodDone);
725
- socket.on("cli:method:error", handleMethodError);
726
- socket.on("cli:method:list:response", handleMethodListResponse);
727
- socket.on("cli:method:get:response", handleMethodGetResponse);
728
- socket.on("cli:error", handleGatewayError);
729
- if (!socket.connected) {
730
- socket.connect();
731
- }
732
- return () => {
733
- socket.off("connect", handleConnect);
734
- socket.off("disconnect", handleDisconnect);
735
- socket.off("connect_error", handleError);
736
- socket.off("cli:terminal:output", handleTerminalOutput);
737
- socket.off("cli:status:update", handleStatusUpdate);
738
- socket.off("cli:file:list:response", handleFileResponse);
739
- socket.off("cli:file:content:response", handleFileResponse);
740
- socket.off("cli:file:download:response", handleFileResponse);
741
- socket.off("cli:file:write:response", handleFileResponse);
742
- socket.off("cli:file:delete:response", handleFileResponse);
743
- socket.off("cli:terminal:confirm_required", handleConfirm);
744
- socket.off("cli:terminal:blocked", handleBlocked);
745
- socket.off("cli:session:created", handleSessionCreated);
746
- socket.off("cli:session:attached", handleSessionAttached);
747
- socket.off("cli:session:detached", handleSessionDetached);
748
- socket.off("cli:session:closed", handleSessionClosed);
749
- socket.off("cli:session:error", handleSessionError);
750
- socket.off("cli:method:status", handleMethodStatus);
751
- socket.off("cli:method:done", handleMethodDone);
752
- socket.off("cli:method:error", handleMethodError);
753
- socket.off("cli:method:list:response", handleMethodListResponse);
754
- socket.off("cli:method:get:response", handleMethodGetResponse);
755
- socket.off("cli:error", handleGatewayError);
756
- reconnectRef.current.cancel();
757
- release();
758
- };
759
- }, [projectId, serverId, socket, release, subscribe, flushBuffer, handlers, scheduleReconnect, syncMetrics]);
760
- const sendTerminalInput = reactExports.useCallback(
761
- (payload) => {
762
- const inputPayload = {
763
- data: payload.data,
764
- confirmed: payload.confirmed
765
- };
766
- if (payload.mode) {
767
- inputPayload.mode = payload.mode;
768
- }
769
- emitEnvelope(
770
- "cli:terminal:input",
771
- inputPayload,
772
- {
773
- session_id: payload.sessionId,
774
- seq: payload.seq,
775
- operation_id: payload.operationId
776
- },
777
- { priority: "high" }
778
- );
779
- },
780
- [emitEnvelope]
781
- );
782
- const sendTerminalResize = reactExports.useCallback(
783
- (payload) => {
784
- emitEnvelope(
785
- "cli:terminal:resize",
786
- { cols: payload.cols, rows: payload.rows },
787
- { session_id: payload.sessionId },
788
- { priority: "low", maxAge: 2e3 }
789
- );
790
- },
791
- [emitEnvelope]
792
- );
793
- const sendFileRequest = reactExports.useCallback(
794
- (event, payload, meta) => {
795
- emitEnvelope(event, payload, meta);
796
- },
797
- [emitEnvelope]
798
- );
799
- return {
800
- socket,
801
- status,
802
- emitEnvelope,
803
- sendTerminalInput,
804
- sendTerminalResize,
805
- sendFileRequest,
806
- unwrapPayload
807
- };
808
- }
809
-
810
- const CLI_SESSION_NAMESPACE = "6f07d9a4-0e31-4f69-9ad4-b6295b6e4d2e";
811
- function uuidToBytes(uuid) {
812
- const hex = uuid.replace(/-/g, "");
813
- const bytes = new Uint8Array(16);
814
- for (let i = 0; i < 16; i += 1) {
815
- bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
816
- }
817
- return bytes;
818
- }
819
- function bytesToUuid(bytes) {
820
- const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
821
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
822
- }
823
- function fnv1a(input, seed) {
824
- let hash = 2166136261 ^ seed;
825
- for (let i = 0; i < input.length; i += 1) {
826
- hash ^= input.charCodeAt(i);
827
- hash = Math.imul(hash, 16777619);
828
- }
829
- return hash >>> 0;
830
- }
831
- function fallbackUuid(input) {
832
- const h1 = fnv1a(input, 1).toString(16).padStart(8, "0");
833
- const h2 = fnv1a(input, 2).toString(16).padStart(8, "0");
834
- const h3 = fnv1a(input, 3).toString(16).padStart(8, "0");
835
- const h4 = fnv1a(input, 4).toString(16).padStart(8, "0");
836
- const raw = `${h1}${h2}${h3}${h4}`.slice(0, 32);
837
- return `${raw.slice(0, 8)}-${raw.slice(8, 12)}-5${raw.slice(13, 16)}-a${raw.slice(17, 20)}-${raw.slice(20, 32)}`;
838
- }
839
- async function uuidV5(name, namespace = CLI_SESSION_NAMESPACE) {
840
- const nsBytes = uuidToBytes(namespace);
841
- const nameBytes = new TextEncoder().encode(name);
842
- const data = new Uint8Array(nsBytes.length + nameBytes.length);
843
- data.set(nsBytes, 0);
844
- data.set(nameBytes, nsBytes.length);
845
- if (typeof crypto !== "undefined" && crypto.subtle?.digest) {
846
- const hash = new Uint8Array(await crypto.subtle.digest("SHA-1", data));
847
- const bytes = hash.slice(0, 16);
848
- bytes[6] = bytes[6] & 15 | 80;
849
- bytes[8] = bytes[8] & 63 | 128;
850
- return bytesToUuid(bytes);
851
- }
852
- return fallbackUuid(`${namespace}:${name}`);
853
- }
854
- async function buildDefaultSessionId(projectId, serverId) {
855
- return uuidV5(`${projectId}:${serverId}:default`);
856
- }
857
- async function buildConversationSessionId(projectId, serverId, conversationId) {
858
- return uuidV5(`${projectId}:${serverId}:${conversationId}`);
859
- }
860
- async function buildChatSessionId(projectId, serverId, chatSessionId) {
861
- return uuidV5(`${projectId}:${serverId}:${chatSessionId}`);
862
- }
863
-
864
- function mapProjectRoleToPermission(role) {
865
- if (!role) return "none";
866
- if (role === "owner") return "owner";
867
- if (role === "admin") return "admin";
868
- if (role === "editor") return "edit";
869
- if (role === "viewer") return "view";
870
- return "none";
871
- }
872
- function resolveCliCapabilities(level, granularity) {
873
- const allowTerminalInput = granularity?.allowTerminalInput ?? true;
874
- const allowFileEdit = granularity?.allowFileEdit ?? true;
875
- const isEdit = level === "edit";
876
- const isAdmin = level === "admin" || level === "owner";
877
- const baseView = {
878
- view_server_status: level !== "none",
879
- view_terminal_output: level !== "none",
880
- view_operation_logs: level !== "none",
881
- view_files: level !== "none",
882
- view_tasks: level !== "none",
883
- view_findings: level !== "none"
884
- };
885
- const editCaps = {
886
- terminal_input: level !== "none" && level !== "view" && allowTerminalInput,
887
- file_upload: level !== "none" && level !== "view" && allowFileEdit,
888
- file_download: isAdmin || isEdit && allowFileEdit,
889
- file_edit: level !== "none" && level !== "view" && allowFileEdit,
890
- file_delete: level !== "none" && level !== "view" && allowFileEdit
891
- };
892
- const adminCaps = {
893
- manage_sessions: level === "admin" || level === "owner",
894
- manage_permissions: level === "admin" || level === "owner",
895
- disconnect_server: level === "admin" || level === "owner",
896
- view_all_user_logs: level === "admin" || level === "owner",
897
- delete_server: level === "owner"
898
- };
899
- return {
900
- ...baseView,
901
- ...editCaps,
902
- ...adminCaps
903
- };
904
- }
905
-
906
- function useCliAccess(options) {
907
- const { projectId, serverId, readOnly } = options;
908
- const [permission, setPermission] = reactExports.useState("none");
909
- const [granularity, setGranularity] = reactExports.useState(null);
910
- const [isLoading, setIsLoading] = reactExports.useState(false);
911
- const [error, setError] = reactExports.useState(null);
912
- reactExports.useEffect(() => {
913
- if (!projectId || !serverId) {
914
- setPermission("none");
915
- setGranularity(null);
916
- return;
917
- }
918
- if (readOnly) {
919
- setPermission("view");
920
- setGranularity(null);
921
- return;
922
- }
923
- let cancelled = false;
924
- setIsLoading(true);
925
- setError(null);
926
- const resolveAccess = async () => {
927
- try {
928
- const access = await checkProjectAccess(projectId);
929
- if (cancelled) return;
930
- const rolePermission = mapProjectRoleToPermission(access?.role);
931
- setPermission(rolePermission);
932
- setGranularity(null);
933
- } catch (err) {
934
- if (cancelled) return;
935
- setPermission("none");
936
- setGranularity(null);
937
- setError("Failed to resolve CLI permissions");
938
- } finally {
939
- if (!cancelled) setIsLoading(false);
940
- }
941
- };
942
- void resolveAccess();
943
- return () => {
944
- cancelled = true;
945
- };
946
- }, [projectId, readOnly, serverId]);
947
- const capabilities = reactExports.useMemo(() => resolveCliCapabilities(permission, granularity), [permission, granularity]);
948
- return {
949
- permission,
950
- granularity,
951
- capabilities,
952
- isLoading,
953
- error
954
- };
955
- }
956
-
957
- export { useCliSocket as a, buildChatSessionId as b, acquireCliSocket as c, unwrapPayload as d, buildDefaultSessionId as e, buildConversationSessionId as f, getCliClientId as g, nextSeq as n, useCliAccess as u, wrapEnvelope as w };