agentengine-sdk-python 0.6.2__py3-none-any.whl → 0.6.9__py3-none-any.whl

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 (490) hide show
  1. agentengine_sdk_python-0.6.9.dist-info/METADATA +179 -0
  2. agentengine_sdk_python-0.6.9.dist-info/RECORD +487 -0
  3. {agentengine_sdk_python-0.6.2.dist-info → agentengine_sdk_python-0.6.9.dist-info}/WHEEL +1 -1
  4. agentengine_sdk_python-0.6.9.dist-info/entry_points.txt +3 -0
  5. agentengine_sdk_python-0.6.9.dist-info/licenses/LICENSE +175 -0
  6. agentengine_sdk_python-0.6.9.dist-info/top_level.txt +2 -0
  7. ksadk/__init__.py +13 -0
  8. ksadk/__main__.py +4 -0
  9. ksadk/a2a/__init__.py +15 -0
  10. ksadk/a2a/card_builder.py +64 -0
  11. ksadk/a2a/client.py +309 -0
  12. ksadk/a2a/executor.py +201 -0
  13. ksadk/a2a/server.py +79 -0
  14. ksadk/agents/__init__.py +20 -0
  15. ksadk/agents/base.py +370 -0
  16. ksadk/agents/context.py +38 -0
  17. ksadk/agents/event.py +35 -0
  18. ksadk/agents/loop_agent.py +71 -0
  19. ksadk/agents/parallel_agent.py +109 -0
  20. ksadk/agents/runner_adapter.py +24 -0
  21. ksadk/agents/sequential_agent.py +34 -0
  22. ksadk/api/__init__.py +3 -0
  23. ksadk/api/client.py +2227 -0
  24. ksadk/builders/__init__.py +20 -0
  25. ksadk/builders/base.py +66 -0
  26. ksadk/builders/code_builder.py +1990 -0
  27. ksadk/builders/container_builder.py +786 -0
  28. ksadk/builders/framework_requirements.py +59 -0
  29. ksadk/builders/ks3_uploader.py +389 -0
  30. ksadk/builders/mcp_builder.py +476 -0
  31. ksadk/builders/requirements_utils.py +75 -0
  32. ksadk/cli/__init__.py +531 -0
  33. ksadk/cli/__main__.py +4 -0
  34. ksadk/cli/agent_ref.py +204 -0
  35. ksadk/cli/cmd_a2a.py +167 -0
  36. ksadk/cli/cmd_agent.py +201 -0
  37. ksadk/cli/cmd_build.py +270 -0
  38. ksadk/cli/cmd_completion.py +304 -0
  39. ksadk/cli/cmd_config.py +758 -0
  40. ksadk/cli/cmd_create.py +2277 -0
  41. ksadk/cli/cmd_dashboard.py +900 -0
  42. ksadk/cli/cmd_deploy.py +704 -0
  43. ksadk/cli/cmd_destroy.py +345 -0
  44. ksadk/cli/cmd_files.py +1227 -0
  45. ksadk/cli/cmd_hermes.py +1259 -0
  46. ksadk/cli/cmd_invoke.py +1336 -0
  47. ksadk/cli/cmd_launch.py +564 -0
  48. ksadk/cli/cmd_mcp.py +1174 -0
  49. ksadk/cli/cmd_model.py +302 -0
  50. ksadk/cli/cmd_openclaw.py +3918 -0
  51. ksadk/cli/cmd_run.py +232 -0
  52. ksadk/cli/cmd_status.py +588 -0
  53. ksadk/cli/cmd_version.py +522 -0
  54. ksadk/cli/cmd_web.py +288 -0
  55. ksadk/cli/deploy_utils.py +87 -0
  56. ksadk/cli/dry_run.py +214 -0
  57. ksadk/cli/env_options.py +82 -0
  58. ksadk/cli/error_utils.py +525 -0
  59. ksadk/cli/global_options.py +31 -0
  60. ksadk/cli/local_runtime.py +97 -0
  61. ksadk/cli/model_catalog.py +136 -0
  62. ksadk/cli/network_options.py +410 -0
  63. ksadk/cli/resource_common.py +621 -0
  64. ksadk/cli/storage.py +84 -0
  65. ksadk/cli/ui.py +431 -0
  66. ksadk/cli/workflow_common.py +552 -0
  67. ksadk/common/__init__.py +14 -0
  68. ksadk/common/aicp_env.py +128 -0
  69. ksadk/common/auth.py +190 -0
  70. ksadk/common/constants.py +84 -0
  71. ksadk/common/llm_utils.py +61 -0
  72. ksadk/configs/__init__.py +61 -0
  73. ksadk/configs/env_registry.py +342 -0
  74. ksadk/configs/global_config.py +200 -0
  75. ksadk/configs/settings.py +706 -0
  76. ksadk/conversations/__init__.py +95 -0
  77. ksadk/conversations/attachment_storage.py +379 -0
  78. ksadk/conversations/attachments.py +774 -0
  79. ksadk/conversations/compaction_pipeline.py +387 -0
  80. ksadk/conversations/compaction_prompt.py +128 -0
  81. ksadk/conversations/context.py +303 -0
  82. ksadk/conversations/model_context.py +360 -0
  83. ksadk/conversations/model_options.py +136 -0
  84. ksadk/conversations/normalize.py +446 -0
  85. ksadk/conversations/reasoning_markup.py +91 -0
  86. ksadk/conversations/run_kinds.py +61 -0
  87. ksadk/conversations/run_status.py +53 -0
  88. ksadk/conversations/runtime.py +5208 -0
  89. ksadk/conversations/semantic_summary.py +359 -0
  90. ksadk/conversations/session_title.py +254 -0
  91. ksadk/deployment/__init__.py +83 -0
  92. ksadk/deployment/agent_access.py +226 -0
  93. ksadk/deployment/base.py +229 -0
  94. ksadk/deployment/manager.py +332 -0
  95. ksadk/deployment/providers/__init__.py +10 -0
  96. ksadk/deployment/providers/serverless.py +1105 -0
  97. ksadk/deployment/registry.py +120 -0
  98. ksadk/deployment/state.py +78 -0
  99. ksadk/deployment/ui_config.py +21 -0
  100. ksadk/detection/__init__.py +7 -0
  101. ksadk/detection/detector.py +431 -0
  102. ksadk/detection/mcp_detector.py +250 -0
  103. ksadk/hermes_terminal.py +467 -0
  104. ksadk/identity/__init__.py +17 -0
  105. ksadk/identity/resolver.py +385 -0
  106. ksadk/knowledge_base/__init__.py +62 -0
  107. ksadk/knowledge_base/adk_tool.py +47 -0
  108. ksadk/knowledge_base/client.py +297 -0
  109. ksadk/knowledge_base/langchain_tool.py +55 -0
  110. ksadk/knowledge_base/service.py +66 -0
  111. ksadk/knowledge_base/tool.py +50 -0
  112. ksadk/markdown.py +169 -0
  113. ksadk/mcp_runtime/__init__.py +231 -0
  114. ksadk/memory/__init__.py +57 -0
  115. ksadk/memory/adk/__init__.py +51 -0
  116. ksadk/memory/adk/backends/__init__.py +5 -0
  117. ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
  118. ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
  119. ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
  120. ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
  121. ksadk/memory/adk/long_term_memory.py +273 -0
  122. ksadk/memory/adk/short_term_memory.py +289 -0
  123. ksadk/memory/adk_tool.py +31 -0
  124. ksadk/memory/backends/__init__.py +6 -0
  125. ksadk/memory/backends/base.py +103 -0
  126. ksadk/memory/backends/memory.py +112 -0
  127. ksadk/memory/backends/redis.py +146 -0
  128. ksadk/memory/langchain_tool.py +39 -0
  129. ksadk/memory/ltm_backend_factory.py +24 -0
  130. ksadk/memory/manager.py +215 -0
  131. ksadk/memory/service.py +172 -0
  132. ksadk/memory/tool.py +96 -0
  133. ksadk/model_policy.py +223 -0
  134. ksadk/openclaw_gateway.py +378 -0
  135. ksadk/runners/__init__.py +8 -0
  136. ksadk/runners/adk_runner.py +1135 -0
  137. ksadk/runners/base_runner.py +305 -0
  138. ksadk/runners/deepagents_runner.py +13 -0
  139. ksadk/runners/factory.py +73 -0
  140. ksadk/runners/langchain_runner.py +539 -0
  141. ksadk/runners/langgraph_runner.py +852 -0
  142. ksadk/runners/patch_langchain.py +187 -0
  143. ksadk/runners/remote_runner.py +667 -0
  144. ksadk/runners/usage_accumulator.py +38 -0
  145. ksadk/runners/utils/__init__.py +19 -0
  146. ksadk/runners/utils/langfuse.py +230 -0
  147. ksadk/runners/utils/loader.py +62 -0
  148. ksadk/runtime_context.py +166 -0
  149. ksadk/runtime_state.py +25 -0
  150. ksadk/sandbox/__init__.py +28 -0
  151. ksadk/sandbox/backends/__init__.py +9 -0
  152. ksadk/sandbox/backends/e2b.py +154 -0
  153. ksadk/sandbox/backends/local_process.py +131 -0
  154. ksadk/sandbox/base.py +101 -0
  155. ksadk/sandbox/factory.py +53 -0
  156. ksadk/sandbox/registry.py +187 -0
  157. ksadk/server/__init__.py +7 -0
  158. ksadk/server/api_models.py +73 -0
  159. ksadk/server/app.py +3789 -0
  160. ksadk/server/static/assets/ArtifactsPanel-C0meuOdi.js +1 -0
  161. ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +9 -0
  162. ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  163. ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  164. ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  165. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  166. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  167. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  168. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  169. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  170. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  171. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  172. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  173. ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  174. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  175. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  176. ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  177. ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  178. ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  179. ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  180. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  181. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  182. ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  183. ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  184. ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  185. ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  186. ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  187. ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  188. ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  189. ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  190. ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  191. ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  192. ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  193. ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  194. ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  195. ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  196. ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  197. ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  198. ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  199. ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  200. ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  201. ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  202. ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  203. ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  204. ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  205. ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  206. ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  207. ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  208. ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  209. ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  210. ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  211. ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  212. ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  213. ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  214. ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  215. ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  216. ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  217. ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  218. ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  219. ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  220. ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  221. ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +8 -0
  222. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
  223. ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +303 -0
  224. ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +1 -0
  226. ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
  227. ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
  228. ksadk/server/static/assets/arc-bj2rZnP5.js +1 -0
  229. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +1 -0
  230. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-2u1ltDIj.js +36 -0
  231. ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
  232. ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
  233. ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
  234. ksadk/server/static/assets/blockDiagram-GPEHLZMM-D6fvdK20.js +132 -0
  235. ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
  236. ksadk/server/static/assets/c4Diagram-AAUBKEIU-BK82L5n5.js +10 -0
  237. ksadk/server/static/assets/channel-BSSOwFIb.js +1 -0
  238. ksadk/server/static/assets/chunk-2J33WTMH-DLVdkeyQ.js +1 -0
  239. ksadk/server/static/assets/chunk-4BX2VUAB-BbwsYcbW.js +1 -0
  240. ksadk/server/static/assets/chunk-55IACEB6-DOairxzw.js +1 -0
  241. ksadk/server/static/assets/chunk-727SXJPM-hCO487gs.js +206 -0
  242. ksadk/server/static/assets/chunk-AQP2D5EJ-XU8vDOuG.js +231 -0
  243. ksadk/server/static/assets/chunk-FMBD7UC4-PrapyfMu.js +15 -0
  244. ksadk/server/static/assets/chunk-ND2GUHAM-CboKhTZ3.js +1 -0
  245. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +1 -0
  246. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +1 -0
  247. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +1 -0
  248. ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
  249. ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
  250. ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
  251. ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
  252. ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
  253. ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
  254. ksadk/server/static/assets/cose-bilkent-S5V4N54A-_N8bJ-EU.js +1 -0
  255. ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
  256. ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
  257. ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
  258. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
  259. ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
  260. ksadk/server/static/assets/dagre-BM42HDAG-FoDlo5J2.js +4 -0
  261. ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
  262. ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
  263. ksadk/server/static/assets/diagram-2AECGRRQ-BBp6ebBw.js +43 -0
  264. ksadk/server/static/assets/diagram-5GNKFQAL-CqOvG1PR.js +10 -0
  265. ksadk/server/static/assets/diagram-KO2AKTUF-O9T3t6ba.js +3 -0
  266. ksadk/server/static/assets/diagram-LMA3HP47-CGG8ekBO.js +24 -0
  267. ksadk/server/static/assets/diagram-OG6HWLK6-4BmH1EEg.js +24 -0
  268. ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
  269. ksadk/server/static/assets/dist-6kSZvCZh.js +1 -0
  270. ksadk/server/static/assets/dist-B67uF2uK.js +1 -0
  271. ksadk/server/static/assets/dist-BMKM66B4.js +1 -0
  272. ksadk/server/static/assets/dist-BVzcWa6D.js +1 -0
  273. ksadk/server/static/assets/dist-Bk1l05Ni.js +1 -0
  274. ksadk/server/static/assets/dist-BkJjNih3.js +1 -0
  275. ksadk/server/static/assets/dist-BmuTWcm7.js +9 -0
  276. ksadk/server/static/assets/dist-C2DKRFVK.js +1 -0
  277. ksadk/server/static/assets/dist-CWiTDN_V.js +6 -0
  278. ksadk/server/static/assets/dist-CZ6C3x8B.js +23 -0
  279. ksadk/server/static/assets/dist-CiZHB6ZW.js +1 -0
  280. ksadk/server/static/assets/dist-Cp1_jKwf.js +1 -0
  281. ksadk/server/static/assets/dist-Cwhk4lwI.js +2 -0
  282. ksadk/server/static/assets/dist-D5JGcxBx.js +11 -0
  283. ksadk/server/static/assets/dist-D99wf6_X.js +1 -0
  284. ksadk/server/static/assets/dist-DCpZrjaK.js +1 -0
  285. ksadk/server/static/assets/dist-DI-oVAXw.js +1 -0
  286. ksadk/server/static/assets/dist-DLMjL01k.js +1 -0
  287. ksadk/server/static/assets/dist-DLeM1OCK.js +1 -0
  288. ksadk/server/static/assets/dist-DM0rEQPk.js +1 -0
  289. ksadk/server/static/assets/dist-DbaH3p5h.js +1 -0
  290. ksadk/server/static/assets/dist-S4klkK5W.js +1 -0
  291. ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
  292. ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
  293. ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
  294. ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
  295. ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
  296. ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
  297. ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
  298. ksadk/server/static/assets/erDiagram-TEJ5UH35-oJhaci7S.js +85 -0
  299. ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
  300. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +1 -0
  301. ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
  302. ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
  303. ksadk/server/static/assets/flowDiagram-I6XJVG4X-DtTmUan-.js +162 -0
  304. ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
  305. ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
  306. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BSsb_eij.js +292 -0
  307. ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
  308. ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
  309. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +1 -0
  310. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-BR5s6Pws.js +106 -0
  311. ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
  312. ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
  313. ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
  314. ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
  315. ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
  316. ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
  317. ksadk/server/static/assets/index-BH7-pC0B.js +165 -0
  318. ksadk/server/static/assets/index-DAURbCsP.css +1 -0
  319. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +1 -0
  320. ksadk/server/static/assets/infoDiagram-5YYISTIA-DYPc5cvh.js +2 -0
  321. ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
  322. ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-MoBdALDg.js +70 -0
  323. ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
  324. ksadk/server/static/assets/journeyDiagram-JHISSGLW-DgLiQT8l.js +139 -0
  325. ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
  326. ksadk/server/static/assets/kanban-definition-UN3LZRKU-CsFx112F.js +89 -0
  327. ksadk/server/static/assets/katex-DRsiwpM2.js +265 -0
  328. ksadk/server/static/assets/linear-TNRQ5Orv.js +1 -0
  329. ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
  330. ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
  331. ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
  332. ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
  333. ksadk/server/static/assets/mermaid-parser.core-D2eMVOgo.js +161 -0
  334. ksadk/server/static/assets/mindmap-definition-RKZ34NQL-BtByYFhL.js +96 -0
  335. ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
  336. ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
  337. ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
  338. ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
  339. ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
  340. ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
  341. ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
  342. ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
  343. ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
  344. ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
  345. ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
  346. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +1 -0
  347. ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
  348. ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
  349. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +1 -0
  350. ksadk/server/static/assets/pieDiagram-4H26LBE5-DPnuCB8c.js +30 -0
  351. ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
  352. ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
  353. ksadk/server/static/assets/properties-C357ku5U.js +1 -0
  354. ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
  355. ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
  356. ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
  357. ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
  358. ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
  359. ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-miFpCPv4.js +7 -0
  360. ksadk/server/static/assets/r-DADKenSm.js +1 -0
  361. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +1 -0
  362. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-mjB_yYkW.js +84 -0
  363. ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
  364. ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
  365. ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-D7hSlZZZ.js +40 -0
  366. ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
  367. ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
  368. ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-D9Cx6hC3.js +162 -0
  369. ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
  370. ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
  371. ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
  372. ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
  373. ksadk/server/static/assets/solr-BBopId9w.js +1 -0
  374. ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
  375. ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
  376. ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
  377. ksadk/server/static/assets/stateDiagram-AJRCARHV-D_zrg47o.js +1 -0
  378. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +1 -0
  379. ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
  380. ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
  381. ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
  382. ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
  383. ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
  384. ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
  385. ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
  386. ksadk/server/static/assets/timeline-definition-PNZ67QCA-CwSec0Wa.js +120 -0
  387. ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
  388. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +1 -0
  389. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +1 -0
  390. ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
  391. ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
  392. ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
  393. ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
  394. ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
  395. ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
  396. ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
  397. ksadk/server/static/assets/vennDiagram-CIIHVFJN-agCVobiF.js +34 -0
  398. ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
  399. ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
  400. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +1 -0
  401. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-TmrlhWNt.js +78 -0
  402. ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
  403. ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
  404. ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
  405. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-DV71V-ah.js +7 -0
  406. ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
  407. ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
  408. ksadk/server/static/favicon.svg +1 -0
  409. ksadk/server/static/icons.svg +24 -0
  410. ksadk/server/static/index.html +14 -0
  411. ksadk/server/terminal_sessions.py +551 -0
  412. ksadk/sessions/__init__.py +283 -0
  413. ksadk/sessions/base.py +354 -0
  414. ksadk/sessions/continuity.py +313 -0
  415. ksadk/sessions/errors.py +7 -0
  416. ksadk/sessions/in_memory.py +273 -0
  417. ksadk/sessions/local_service.py +949 -0
  418. ksadk/sessions/postgres_service.py +838 -0
  419. ksadk/sessions/sqlite_service.py +13 -0
  420. ksadk/skills/__init__.py +9 -0
  421. ksadk/skills/loader.py +46 -0
  422. ksadk/skills/models.py +120 -0
  423. ksadk/skills/package_store.py +104 -0
  424. ksadk/skills/runtime/__init__.py +17 -0
  425. ksadk/skills/runtime/agent.py +183 -0
  426. ksadk/skills/runtime/artifacts.py +39 -0
  427. ksadk/skills/runtime/backends/__init__.py +1 -0
  428. ksadk/skills/runtime/backends/disabled.py +8 -0
  429. ksadk/skills/runtime/backends/e2b.py +168 -0
  430. ksadk/skills/runtime/backends/local.py +95 -0
  431. ksadk/skills/runtime/base.py +103 -0
  432. ksadk/skills/runtime/executor.py +219 -0
  433. ksadk/skills/runtime/factory.py +37 -0
  434. ksadk/skills/runtime/loader.py +131 -0
  435. ksadk/skills/runtime/registry.py +150 -0
  436. ksadk/skills/runtime/request.py +67 -0
  437. ksadk/skills/service_client.py +239 -0
  438. ksadk/skills/service_env.py +58 -0
  439. ksadk/skills/tool_defs.py +221 -0
  440. ksadk/terminal_client.py +69 -0
  441. ksadk/terminal_exec_policy.py +229 -0
  442. ksadk/tools/__init__.py +17 -0
  443. ksadk/tools/gateway.py +230 -0
  444. ksadk/tools/result_budget.py +154 -0
  445. ksadk/toolsets/__init__.py +852 -0
  446. ksadk/toolsets/_langchain.py +11 -0
  447. ksadk/toolsets/platform.py +93 -0
  448. ksadk/toolsets/sandbox.py +310 -0
  449. ksadk/toolsets/skills.py +217 -0
  450. ksadk/toolsets/web.py +266 -0
  451. ksadk/toolsets/workspace.py +878 -0
  452. ksadk/toolsets/workspace_state.py +44 -0
  453. ksadk/tracing/__init__.py +37 -0
  454. ksadk/tracing/exporters/__init__.py +1 -0
  455. ksadk/tracing/exporters/inmemory_exporter.py +116 -0
  456. ksadk/tracing/exporters/langfuse_exporter.py +445 -0
  457. ksadk/tracing/setup.py +829 -0
  458. ksadk/tracing/span_utils.py +114 -0
  459. ksadk/tui/__init__.py +12 -0
  460. ksadk/tui/app.py +400 -0
  461. ksadk/tui/clipboard.py +100 -0
  462. ksadk/tui/widgets/__init__.py +22 -0
  463. ksadk/tui/widgets/assistant.py +89 -0
  464. ksadk/tui/widgets/base.py +54 -0
  465. ksadk/tui/widgets/chat_input.py +246 -0
  466. ksadk/tui/widgets/history.py +156 -0
  467. ksadk/tui/widgets/system.py +87 -0
  468. ksadk/tui/widgets/thinking.py +110 -0
  469. ksadk/tui/widgets/tool_call.py +161 -0
  470. ksadk/tui/widgets/user.py +37 -0
  471. ksadk/ui_config.py +168 -0
  472. ksadk/version.py +4 -0
  473. ksadk_runtime_common/__init__.py +20 -0
  474. ksadk_runtime_common/memory_backend/__init__.py +29 -0
  475. ksadk_runtime_common/memory_backend/manifest.py +79 -0
  476. ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
  477. ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
  478. ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
  479. ksadk_runtime_common/memory_backend/registry.py +56 -0
  480. ksadk_runtime_common/memory_backend/render.py +71 -0
  481. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
  482. ksadk_runtime_common/workspace_files/__init__.py +29 -0
  483. ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
  484. ksadk_runtime_common/workspace_files/constants.py +9 -0
  485. ksadk_runtime_common/workspace_files/path_utils.py +71 -0
  486. ksadk_runtime_common/workspace_files/preview.py +96 -0
  487. ksadk_runtime_common/workspace_files/router.py +254 -0
  488. agentengine_sdk_python-0.6.2.dist-info/METADATA +0 -46
  489. agentengine_sdk_python-0.6.2.dist-info/RECORD +0 -4
  490. agentengine_sdk_python-0.6.2.dist-info/top_level.txt +0 -1
@@ -0,0 +1,551 @@
1
+ """Native terminal session manager for hosted Hermes/OpenClaw UIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import contextlib
7
+ import json
8
+ import os
9
+ import shutil
10
+ import signal
11
+ import time
12
+ import uuid
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path, PurePosixPath
15
+ from typing import Any, Callable
16
+
17
+ from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
18
+ from fastapi.responses import JSONResponse
19
+ from starlette.websockets import WebSocketState
20
+
21
+ from ksadk.hermes_terminal import TERMINAL_SUBPROTOCOL
22
+ from ksadk.terminal_exec_policy import (
23
+ GENERIC_TERMINAL_EXEC_POLICY,
24
+ HERMES_TERMINAL_EXEC_POLICY,
25
+ OPENCLAW_TERMINAL_EXEC_POLICY,
26
+ )
27
+ from ksadk.terminal_exec_policy import (
28
+ validate_terminal_exec_argv as validate_exec_argv_with_policy,
29
+ )
30
+
31
+ try:
32
+ import pty
33
+ except ImportError: # pragma: no cover - exercised through subprocess import test
34
+ pty = None # type: ignore[assignment]
35
+
36
+ try:
37
+ import select
38
+ except ImportError: # pragma: no cover - Windows compatibility
39
+ select = None # type: ignore[assignment]
40
+
41
+ try:
42
+ import termios
43
+ except ImportError: # pragma: no cover - exercised through subprocess import test
44
+ termios = None # type: ignore[assignment]
45
+
46
+
47
+ TERMINAL_REPLAY_BUFFER_BYTES = 64 * 1024
48
+ TERMINAL_DETACHED_TTL_SECONDS = 24 * 60 * 60
49
+ MAX_TERMINAL_SESSIONS_PER_BUSINESS_SESSION = 3
50
+ MAX_TERMINAL_SESSIONS_PER_RUNTIME = 32
51
+
52
+
53
+ @dataclass
54
+ class TerminalSession:
55
+ id: str
56
+ session_id: str = ""
57
+ mode: str = "tui"
58
+ framework: str = ""
59
+ argv: list[str] = field(default_factory=list)
60
+ cols: int = 80
61
+ rows: int = 24
62
+ cwd: str = ""
63
+ status: str = "running"
64
+ created_at: float = field(default_factory=time.time)
65
+ updated_at: float = field(default_factory=time.time)
66
+ pid: int | None = None
67
+ fd: int | None = None
68
+ exit_code: int | None = None
69
+ deleted: bool = False
70
+ transient: bool = False
71
+ reader_task: asyncio.Task | None = None
72
+ wait_task: asyncio.Task | None = None
73
+ attachments: set[WebSocket] = field(default_factory=set)
74
+ replay_buffer: bytearray = field(default_factory=bytearray)
75
+
76
+
77
+ def _utc_timestamp(value: float) -> str:
78
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(value))
79
+
80
+
81
+ def _env_int(name: str, default: int) -> int:
82
+ raw = os.getenv(name)
83
+ if raw is None:
84
+ return default
85
+ try:
86
+ return int(raw)
87
+ except (TypeError, ValueError):
88
+ return default
89
+
90
+
91
+ def _env_bool(name: str, default: bool) -> bool:
92
+ raw = os.getenv(name)
93
+ if raw is None:
94
+ return default
95
+ return raw.strip().lower() not in {"0", "false", "no", "off"}
96
+
97
+
98
+ def native_terminal_supported() -> bool:
99
+ return (
100
+ os.name != "nt"
101
+ and pty is not None
102
+ and select is not None
103
+ and termios is not None
104
+ )
105
+
106
+
107
+ class TerminalSessionManager:
108
+ def __init__(
109
+ self,
110
+ *,
111
+ workspace_root_getter: Callable[[], Path],
112
+ framework_getter: Callable[[], str],
113
+ ):
114
+ self.workspace_root_getter = workspace_root_getter
115
+ self.framework_getter = framework_getter
116
+ self.sessions: dict[str, TerminalSession] = {}
117
+ self._lock = asyncio.Lock()
118
+
119
+ def reset_for_tests(self) -> None:
120
+ for session in list(self.sessions.values()):
121
+ self._terminate_session(session)
122
+ self.sessions.clear()
123
+
124
+ def serialize(self, session: TerminalSession) -> dict[str, Any]:
125
+ return {
126
+ "terminal_session_id": session.id,
127
+ "session_id": session.session_id,
128
+ "mode": session.mode,
129
+ "status": session.status,
130
+ "cols": session.cols,
131
+ "rows": session.rows,
132
+ "cwd": session.cwd,
133
+ "created_at": _utc_timestamp(session.created_at),
134
+ "updated_at": _utc_timestamp(session.updated_at),
135
+ "exit_code": session.exit_code,
136
+ }
137
+
138
+ async def create_or_reuse(self, payload: dict[str, Any]) -> TerminalSession:
139
+ session_id = str(
140
+ payload.get("session_id") or payload.get("sessionId") or payload.get("SessionId") or ""
141
+ ).strip()
142
+ mode = str(payload.get("mode") or "tui").strip().lower()
143
+ force_new = bool(payload.get("force_new") or payload.get("forceNew"))
144
+ async with self._lock:
145
+ self._cleanup_expired_locked()
146
+ if not force_new:
147
+ existing = self._find_reusable_locked(session_id=session_id, mode=mode)
148
+ if existing:
149
+ existing.cols = int(payload.get("cols") or existing.cols or 80)
150
+ existing.rows = int(payload.get("rows") or existing.rows or 24)
151
+ existing.updated_at = time.time()
152
+ if existing.fd is not None:
153
+ _set_winsize(existing.fd, existing.rows, existing.cols)
154
+ return existing
155
+
156
+ self._enforce_limits_locked(session_id=session_id)
157
+ session = TerminalSession(
158
+ id=f"term-{uuid.uuid4().hex[:12]}",
159
+ session_id=session_id,
160
+ mode=mode,
161
+ framework=self._current_framework(),
162
+ argv=[str(item) for item in (payload.get("argv") or [])],
163
+ cols=int(payload.get("cols") or 80),
164
+ rows=int(payload.get("rows") or 24),
165
+ cwd=str(payload.get("cwd") or "").strip(),
166
+ )
167
+ self._resolve_terminal_command(session)
168
+ self.sessions[session.id] = session
169
+ self._spawn_session(session)
170
+ self._persist_metadata(session)
171
+ return session
172
+
173
+ async def list_sessions(self, *, session_id: str = "", mode: str = "") -> list[TerminalSession]:
174
+ async with self._lock:
175
+ self._cleanup_expired_locked()
176
+ normalized_session_id = str(session_id or "").strip()
177
+ normalized_mode = str(mode or "").strip().lower()
178
+ sessions = [
179
+ session
180
+ for session in self.sessions.values()
181
+ if not session.deleted
182
+ and (not normalized_session_id or session.session_id == normalized_session_id)
183
+ and (not normalized_mode or session.mode == normalized_mode)
184
+ ]
185
+ return sorted(
186
+ sessions,
187
+ key=lambda item: (
188
+ item.status in {"running", "detached"},
189
+ item.updated_at,
190
+ ),
191
+ reverse=True,
192
+ )
193
+
194
+ async def delete(self, terminal_session_id: str) -> TerminalSession | None:
195
+ async with self._lock:
196
+ session = self.sessions.get(terminal_session_id)
197
+ if not session or session.deleted:
198
+ return None
199
+ session.deleted = True
200
+ session.status = "deleted"
201
+ session.updated_at = time.time()
202
+ self._terminate_session(session)
203
+ self._persist_metadata(session)
204
+ return session
205
+
206
+ async def attach(self, ws: WebSocket, terminal_session_id: str) -> None:
207
+ session = self.sessions.get(terminal_session_id)
208
+ if not session or session.deleted:
209
+ raise ValueError("terminal session not found")
210
+ await self._attach_existing(ws, session)
211
+
212
+ async def legacy_start(self, ws: WebSocket, payload: dict[str, Any]) -> None:
213
+ session = TerminalSession(
214
+ id=f"term-{uuid.uuid4().hex[:12]}",
215
+ session_id=str(
216
+ payload.get("session_id") or payload.get("sessionId") or payload.get("SessionId") or ""
217
+ ).strip(),
218
+ mode=str(payload.get("mode") or "tui").strip().lower(),
219
+ framework=self._current_framework(),
220
+ argv=[str(item) for item in (payload.get("argv") or [])],
221
+ cols=int(payload.get("cols") or 80),
222
+ rows=int(payload.get("rows") or 24),
223
+ cwd=str(payload.get("cwd") or "").strip(),
224
+ transient=True,
225
+ )
226
+ self._resolve_terminal_command(session)
227
+ self.sessions[session.id] = session
228
+ self._spawn_session(session)
229
+ try:
230
+ await self._attach_existing(ws, session)
231
+ finally:
232
+ self._terminate_session(session)
233
+ self.sessions.pop(session.id, None)
234
+
235
+ def _current_framework(self) -> str:
236
+ return str(self.framework_getter() or "").strip().lower()
237
+
238
+ def _find_reusable_locked(self, *, session_id: str, mode: str) -> TerminalSession | None:
239
+ if not session_id:
240
+ return None
241
+ candidates = [
242
+ session
243
+ for session in self.sessions.values()
244
+ if not session.deleted
245
+ and session.session_id == session_id
246
+ and session.mode == mode
247
+ and session.status in {"running", "detached"}
248
+ ]
249
+ if not candidates:
250
+ return None
251
+ return max(candidates, key=lambda item: item.updated_at)
252
+
253
+ def _cleanup_expired_locked(self) -> None:
254
+ now = time.time()
255
+ for session in list(self.sessions.values()):
256
+ if session.deleted:
257
+ continue
258
+ if session.status == "detached" and now - session.updated_at > TERMINAL_DETACHED_TTL_SECONDS:
259
+ session.deleted = True
260
+ session.status = "deleted"
261
+ self._terminate_session(session)
262
+
263
+ def _enforce_limits_locked(self, *, session_id: str) -> None:
264
+ active_sessions = [
265
+ session
266
+ for session in self.sessions.values()
267
+ if not session.deleted and session.status in {"running", "detached"}
268
+ ]
269
+ if len(active_sessions) >= _env_int(
270
+ "AGENTENGINE_TERMINAL_MAX_RUNTIME_SESSIONS",
271
+ MAX_TERMINAL_SESSIONS_PER_RUNTIME,
272
+ ):
273
+ raise ValueError("too many terminal sessions in this runtime")
274
+ if session_id:
275
+ per_business_session = [session for session in active_sessions if session.session_id == session_id]
276
+ if len(per_business_session) >= _env_int(
277
+ "AGENTENGINE_TERMINAL_MAX_SESSIONS_PER_BUSINESS_SESSION",
278
+ MAX_TERMINAL_SESSIONS_PER_BUSINESS_SESSION,
279
+ ):
280
+ raise ValueError("too many terminal sessions for this session_id")
281
+
282
+ def _resolve_terminal_command(self, session: TerminalSession) -> list[str]:
283
+ mode = session.mode
284
+ framework = session.framework
285
+ if mode == "tui":
286
+ return self._resolve_tui_command(session)
287
+ if mode == "exec":
288
+ policy = HERMES_TERMINAL_EXEC_POLICY if framework == "hermes" else OPENCLAW_TERMINAL_EXEC_POLICY
289
+ if framework not in {"hermes", "openclaw"}:
290
+ policy = GENERIC_TERMINAL_EXEC_POLICY
291
+ return validate_exec_argv_with_policy(session.argv, policy=policy)
292
+ raise ValueError(f"unsupported terminal mode: {mode}")
293
+
294
+ def _resolve_tui_command(self, session: TerminalSession) -> list[str]:
295
+ framework = session.framework
296
+ if framework == "hermes" and shutil.which("hermes"):
297
+ command = ["hermes", "chat"]
298
+ if session.session_id and _env_bool("HERMES_TERMINAL_RESUME_ENABLED", True):
299
+ command.extend([os.getenv("HERMES_TERMINAL_RESUME_FLAG", "--resume"), session.session_id])
300
+ session.argv = command
301
+ return command
302
+ if framework == "openclaw" and shutil.which("openclaw"):
303
+ command = ["openclaw", "tui"]
304
+ if session.session_id:
305
+ command.extend([os.getenv("OPENCLAW_TERMINAL_SESSION_FLAG", "--session"), session.session_id])
306
+ session.argv = command
307
+ return command
308
+ shell = os.getenv("SHELL") or "/bin/sh"
309
+ session.argv = [shell]
310
+ return session.argv
311
+
312
+ def _resolve_terminal_cwd(self, cwd: str | None) -> Path | None:
313
+ raw = str(cwd or "").strip().replace("\\", "/")
314
+ if not raw:
315
+ return None
316
+
317
+ workspace_root = self.workspace_root_getter().resolve()
318
+ if raw in {".", "/"}:
319
+ resolved = workspace_root
320
+ else:
321
+ parts = [part for part in PurePosixPath(raw.lstrip("/")).parts if part not in {"", "."}]
322
+ if any(part == ".." for part in parts):
323
+ raise ValueError("workspace cwd escapes the workspace root")
324
+ resolved = workspace_root.joinpath(*parts).resolve()
325
+
326
+ try:
327
+ resolved.relative_to(workspace_root)
328
+ except ValueError as exc:
329
+ raise ValueError("workspace cwd escapes the workspace root") from exc
330
+ if not resolved.exists():
331
+ raise ValueError(f"workspace cwd does not exist: {raw}")
332
+ if not resolved.is_dir():
333
+ raise ValueError(f"workspace cwd is not a directory: {raw}")
334
+ return resolved
335
+
336
+ def _spawn_session(self, session: TerminalSession) -> None:
337
+ if session.pid is not None:
338
+ return
339
+ if not native_terminal_supported():
340
+ raise ValueError("native terminal sessions are not supported on this platform")
341
+ command = self._resolve_terminal_command(session)
342
+ terminal_cwd = self._resolve_terminal_cwd(session.cwd)
343
+ assert pty is not None
344
+ pid, fd = pty.fork()
345
+ if pid == 0:
346
+ if terminal_cwd is not None:
347
+ os.chdir(str(terminal_cwd))
348
+ os.execvp(command[0], command)
349
+ session.pid = pid
350
+ session.fd = fd
351
+ session.status = "running"
352
+ session.updated_at = time.time()
353
+ _set_winsize(fd, session.rows, session.cols)
354
+ session.reader_task = asyncio.create_task(self._session_reader(session))
355
+ session.wait_task = asyncio.create_task(self._session_waiter(session))
356
+
357
+ def _terminate_session(self, session: TerminalSession) -> None:
358
+ if session.pid is not None and session.status not in {"exited", "deleted"}:
359
+ with contextlib.suppress(ProcessLookupError):
360
+ os.kill(session.pid, signal.SIGTERM)
361
+ for task in (session.reader_task, session.wait_task):
362
+ if task and not task.done():
363
+ task.cancel()
364
+ if session.fd is not None:
365
+ with contextlib.suppress(OSError):
366
+ os.close(session.fd)
367
+ session.fd = None
368
+
369
+ async def _session_reader(self, session: TerminalSession) -> None:
370
+ if session.fd is None:
371
+ return
372
+ loop = asyncio.get_running_loop()
373
+ while True:
374
+ await loop.run_in_executor(None, lambda: select.select([session.fd], [], [], None))
375
+ try:
376
+ data = os.read(session.fd, 4096)
377
+ except OSError:
378
+ return
379
+ if not data:
380
+ return
381
+ await self._broadcast_bytes(session, data)
382
+
383
+ async def _session_waiter(self, session: TerminalSession) -> None:
384
+ if session.pid is None:
385
+ return
386
+ code = await _wait_process(session.pid)
387
+ session.exit_code = code
388
+ session.status = "exited"
389
+ session.updated_at = time.time()
390
+ await self._broadcast_control(session, {"type": "exit", "code": code})
391
+ if session.fd is not None:
392
+ with contextlib.suppress(OSError):
393
+ os.close(session.fd)
394
+ session.fd = None
395
+ self._persist_metadata(session)
396
+
397
+ async def _attach_existing(self, ws: WebSocket, session: TerminalSession) -> None:
398
+ session.attachments.add(ws)
399
+ session.status = "running" if session.status == "detached" and session.pid is not None else session.status
400
+ session.updated_at = time.time()
401
+ try:
402
+ await ws.send_text(
403
+ json.dumps({"type": "ready", "terminal_session_id": session.id}, ensure_ascii=False)
404
+ )
405
+ if session.replay_buffer:
406
+ await ws.send_bytes(bytes(session.replay_buffer))
407
+ while True:
408
+ message = await ws.receive()
409
+ if message.get("type") == "websocket.disconnect":
410
+ return
411
+ if message.get("bytes") is not None:
412
+ if session.fd is not None and session.status == "running":
413
+ os.write(session.fd, message["bytes"])
414
+ continue
415
+ text = message.get("text")
416
+ if not text:
417
+ continue
418
+ control = json.loads(text)
419
+ if control.get("type") == "ping":
420
+ await ws.send_text(json.dumps({"type": "pong"}))
421
+ elif control.get("type") == "resize":
422
+ session.rows = int(control.get("rows") or session.rows or 24)
423
+ session.cols = int(control.get("cols") or session.cols or 80)
424
+ session.updated_at = time.time()
425
+ if session.fd is not None:
426
+ _set_winsize(session.fd, session.rows, session.cols)
427
+ elif control.get("type") == "signal" and session.pid is not None:
428
+ sig = signal.SIGINT if control.get("signal") == "SIGINT" else signal.SIGTERM
429
+ os.kill(session.pid, sig)
430
+ elif control.get("type") == "stdin_eof":
431
+ continue
432
+ finally:
433
+ session.attachments.discard(ws)
434
+ if not session.attachments and session.status == "running":
435
+ session.status = "detached"
436
+ session.updated_at = time.time()
437
+ self._persist_metadata(session)
438
+
439
+ async def _broadcast_bytes(self, session: TerminalSession, data: bytes) -> None:
440
+ session.replay_buffer.extend(data)
441
+ overflow = len(session.replay_buffer) - _env_int(
442
+ "AGENTENGINE_TERMINAL_REPLAY_BUFFER_BYTES",
443
+ TERMINAL_REPLAY_BUFFER_BYTES,
444
+ )
445
+ if overflow > 0:
446
+ del session.replay_buffer[:overflow]
447
+ stale: list[WebSocket] = []
448
+ for attached in list(session.attachments):
449
+ try:
450
+ await attached.send_bytes(data)
451
+ except Exception:
452
+ stale.append(attached)
453
+ for attached in stale:
454
+ session.attachments.discard(attached)
455
+
456
+ async def _broadcast_control(self, session: TerminalSession, payload: dict[str, Any]) -> None:
457
+ stale: list[WebSocket] = []
458
+ text = json.dumps(payload, ensure_ascii=False)
459
+ for attached in list(session.attachments):
460
+ try:
461
+ await attached.send_text(text)
462
+ except Exception:
463
+ stale.append(attached)
464
+ for attached in stale:
465
+ session.attachments.discard(attached)
466
+
467
+ def _persist_metadata(self, session: TerminalSession) -> None:
468
+ try:
469
+ state_dir = Path(
470
+ os.getenv("AGENTENGINE_TERMINAL_STATE_DIR", "/home/node/.agentengine/terminal")
471
+ )
472
+ state_dir.mkdir(parents=True, exist_ok=True)
473
+ (state_dir / f"{session.id}.json").write_text(
474
+ json.dumps(self.serialize(session), ensure_ascii=False, indent=2),
475
+ encoding="utf-8",
476
+ )
477
+ except OSError:
478
+ pass
479
+
480
+
481
+ def _set_winsize(fd: int, rows: int, cols: int) -> None:
482
+ if termios is None:
483
+ return
484
+ with contextlib.suppress(Exception):
485
+ termios.tcsetwinsize(fd, (int(rows or 24), int(cols or 80)))
486
+
487
+
488
+ async def _wait_process(pid: int) -> int:
489
+ loop = asyncio.get_running_loop()
490
+ _, status = await loop.run_in_executor(None, os.waitpid, pid, 0)
491
+ if os.WIFEXITED(status):
492
+ return os.WEXITSTATUS(status)
493
+ if os.WIFSIGNALED(status):
494
+ return 128 + os.WTERMSIG(status)
495
+ return status
496
+
497
+
498
+ def register_terminal_routes(app: FastAPI, manager: TerminalSessionManager) -> None:
499
+ @app.post("/_ksadk/terminal/sessions")
500
+ async def create_terminal_session(request: Request) -> JSONResponse:
501
+ payload = await request.json()
502
+ try:
503
+ session = await manager.create_or_reuse(payload if isinstance(payload, dict) else {})
504
+ except ValueError as exc:
505
+ return JSONResponse({"error": str(exc)}, status_code=400)
506
+ return JSONResponse({"session": manager.serialize(session)})
507
+
508
+ @app.get("/_ksadk/terminal/sessions")
509
+ async def list_terminal_sessions(request: Request) -> JSONResponse:
510
+ sessions = await manager.list_sessions(
511
+ session_id=str(request.query_params.get("session_id") or ""),
512
+ mode=str(request.query_params.get("mode") or ""),
513
+ )
514
+ return JSONResponse({"sessions": [manager.serialize(session) for session in sessions]})
515
+
516
+ @app.delete("/_ksadk/terminal/sessions/{terminal_session_id}")
517
+ async def delete_terminal_session(terminal_session_id: str) -> JSONResponse:
518
+ session = await manager.delete(terminal_session_id)
519
+ if not session:
520
+ return JSONResponse(
521
+ {"deleted": False, "terminal_session_id": terminal_session_id},
522
+ status_code=404,
523
+ )
524
+ return JSONResponse({"deleted": True, "terminal_session_id": terminal_session_id})
525
+
526
+ @app.websocket("/_ksadk/terminal/ws")
527
+ async def terminal_ws(ws: WebSocket) -> None:
528
+ if TERMINAL_SUBPROTOCOL not in (ws.headers.get("sec-websocket-protocol") or ""):
529
+ await ws.close(code=4400, reason="missing ks-terminal.v1 subprotocol")
530
+ return
531
+ await ws.accept(subprotocol=TERMINAL_SUBPROTOCOL)
532
+ try:
533
+ query_session_id = str(ws.query_params.get("terminal_session_id") or "").strip()
534
+ if query_session_id:
535
+ await manager.attach(ws, query_session_id)
536
+ return
537
+ first = await ws.receive_text()
538
+ payload = json.loads(first)
539
+ if payload.get("type") == "attach":
540
+ terminal_session_id = str(payload.get("terminal_session_id") or "").strip()
541
+ await manager.attach(ws, terminal_session_id)
542
+ return
543
+ if payload.get("type") != "start":
544
+ raise ValueError("first frame must be start")
545
+ await manager.legacy_start(ws, payload)
546
+ except WebSocketDisconnect:
547
+ return
548
+ except Exception as exc:
549
+ if ws.client_state == WebSocketState.CONNECTED:
550
+ await ws.send_text(json.dumps({"type": "error", "message": str(exc)}))
551
+ await ws.close()