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.
- agentengine_sdk_python-0.6.9.dist-info/METADATA +179 -0
- agentengine_sdk_python-0.6.9.dist-info/RECORD +487 -0
- {agentengine_sdk_python-0.6.2.dist-info → agentengine_sdk_python-0.6.9.dist-info}/WHEEL +1 -1
- agentengine_sdk_python-0.6.9.dist-info/entry_points.txt +3 -0
- agentengine_sdk_python-0.6.9.dist-info/licenses/LICENSE +175 -0
- agentengine_sdk_python-0.6.9.dist-info/top_level.txt +2 -0
- ksadk/__init__.py +13 -0
- ksadk/__main__.py +4 -0
- ksadk/a2a/__init__.py +15 -0
- ksadk/a2a/card_builder.py +64 -0
- ksadk/a2a/client.py +309 -0
- ksadk/a2a/executor.py +201 -0
- ksadk/a2a/server.py +79 -0
- ksadk/agents/__init__.py +20 -0
- ksadk/agents/base.py +370 -0
- ksadk/agents/context.py +38 -0
- ksadk/agents/event.py +35 -0
- ksadk/agents/loop_agent.py +71 -0
- ksadk/agents/parallel_agent.py +109 -0
- ksadk/agents/runner_adapter.py +24 -0
- ksadk/agents/sequential_agent.py +34 -0
- ksadk/api/__init__.py +3 -0
- ksadk/api/client.py +2227 -0
- ksadk/builders/__init__.py +20 -0
- ksadk/builders/base.py +66 -0
- ksadk/builders/code_builder.py +1990 -0
- ksadk/builders/container_builder.py +786 -0
- ksadk/builders/framework_requirements.py +59 -0
- ksadk/builders/ks3_uploader.py +389 -0
- ksadk/builders/mcp_builder.py +476 -0
- ksadk/builders/requirements_utils.py +75 -0
- ksadk/cli/__init__.py +531 -0
- ksadk/cli/__main__.py +4 -0
- ksadk/cli/agent_ref.py +204 -0
- ksadk/cli/cmd_a2a.py +167 -0
- ksadk/cli/cmd_agent.py +201 -0
- ksadk/cli/cmd_build.py +270 -0
- ksadk/cli/cmd_completion.py +304 -0
- ksadk/cli/cmd_config.py +758 -0
- ksadk/cli/cmd_create.py +2277 -0
- ksadk/cli/cmd_dashboard.py +900 -0
- ksadk/cli/cmd_deploy.py +704 -0
- ksadk/cli/cmd_destroy.py +345 -0
- ksadk/cli/cmd_files.py +1227 -0
- ksadk/cli/cmd_hermes.py +1259 -0
- ksadk/cli/cmd_invoke.py +1336 -0
- ksadk/cli/cmd_launch.py +564 -0
- ksadk/cli/cmd_mcp.py +1174 -0
- ksadk/cli/cmd_model.py +302 -0
- ksadk/cli/cmd_openclaw.py +3918 -0
- ksadk/cli/cmd_run.py +232 -0
- ksadk/cli/cmd_status.py +588 -0
- ksadk/cli/cmd_version.py +522 -0
- ksadk/cli/cmd_web.py +288 -0
- ksadk/cli/deploy_utils.py +87 -0
- ksadk/cli/dry_run.py +214 -0
- ksadk/cli/env_options.py +82 -0
- ksadk/cli/error_utils.py +525 -0
- ksadk/cli/global_options.py +31 -0
- ksadk/cli/local_runtime.py +97 -0
- ksadk/cli/model_catalog.py +136 -0
- ksadk/cli/network_options.py +410 -0
- ksadk/cli/resource_common.py +621 -0
- ksadk/cli/storage.py +84 -0
- ksadk/cli/ui.py +431 -0
- ksadk/cli/workflow_common.py +552 -0
- ksadk/common/__init__.py +14 -0
- ksadk/common/aicp_env.py +128 -0
- ksadk/common/auth.py +190 -0
- ksadk/common/constants.py +84 -0
- ksadk/common/llm_utils.py +61 -0
- ksadk/configs/__init__.py +61 -0
- ksadk/configs/env_registry.py +342 -0
- ksadk/configs/global_config.py +200 -0
- ksadk/configs/settings.py +706 -0
- ksadk/conversations/__init__.py +95 -0
- ksadk/conversations/attachment_storage.py +379 -0
- ksadk/conversations/attachments.py +774 -0
- ksadk/conversations/compaction_pipeline.py +387 -0
- ksadk/conversations/compaction_prompt.py +128 -0
- ksadk/conversations/context.py +303 -0
- ksadk/conversations/model_context.py +360 -0
- ksadk/conversations/model_options.py +136 -0
- ksadk/conversations/normalize.py +446 -0
- ksadk/conversations/reasoning_markup.py +91 -0
- ksadk/conversations/run_kinds.py +61 -0
- ksadk/conversations/run_status.py +53 -0
- ksadk/conversations/runtime.py +5208 -0
- ksadk/conversations/semantic_summary.py +359 -0
- ksadk/conversations/session_title.py +254 -0
- ksadk/deployment/__init__.py +83 -0
- ksadk/deployment/agent_access.py +226 -0
- ksadk/deployment/base.py +229 -0
- ksadk/deployment/manager.py +332 -0
- ksadk/deployment/providers/__init__.py +10 -0
- ksadk/deployment/providers/serverless.py +1105 -0
- ksadk/deployment/registry.py +120 -0
- ksadk/deployment/state.py +78 -0
- ksadk/deployment/ui_config.py +21 -0
- ksadk/detection/__init__.py +7 -0
- ksadk/detection/detector.py +431 -0
- ksadk/detection/mcp_detector.py +250 -0
- ksadk/hermes_terminal.py +467 -0
- ksadk/identity/__init__.py +17 -0
- ksadk/identity/resolver.py +385 -0
- ksadk/knowledge_base/__init__.py +62 -0
- ksadk/knowledge_base/adk_tool.py +47 -0
- ksadk/knowledge_base/client.py +297 -0
- ksadk/knowledge_base/langchain_tool.py +55 -0
- ksadk/knowledge_base/service.py +66 -0
- ksadk/knowledge_base/tool.py +50 -0
- ksadk/markdown.py +169 -0
- ksadk/mcp_runtime/__init__.py +231 -0
- ksadk/memory/__init__.py +57 -0
- ksadk/memory/adk/__init__.py +51 -0
- ksadk/memory/adk/backends/__init__.py +5 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
- ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
- ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
- ksadk/memory/adk/long_term_memory.py +273 -0
- ksadk/memory/adk/short_term_memory.py +289 -0
- ksadk/memory/adk_tool.py +31 -0
- ksadk/memory/backends/__init__.py +6 -0
- ksadk/memory/backends/base.py +103 -0
- ksadk/memory/backends/memory.py +112 -0
- ksadk/memory/backends/redis.py +146 -0
- ksadk/memory/langchain_tool.py +39 -0
- ksadk/memory/ltm_backend_factory.py +24 -0
- ksadk/memory/manager.py +215 -0
- ksadk/memory/service.py +172 -0
- ksadk/memory/tool.py +96 -0
- ksadk/model_policy.py +223 -0
- ksadk/openclaw_gateway.py +378 -0
- ksadk/runners/__init__.py +8 -0
- ksadk/runners/adk_runner.py +1135 -0
- ksadk/runners/base_runner.py +305 -0
- ksadk/runners/deepagents_runner.py +13 -0
- ksadk/runners/factory.py +73 -0
- ksadk/runners/langchain_runner.py +539 -0
- ksadk/runners/langgraph_runner.py +852 -0
- ksadk/runners/patch_langchain.py +187 -0
- ksadk/runners/remote_runner.py +667 -0
- ksadk/runners/usage_accumulator.py +38 -0
- ksadk/runners/utils/__init__.py +19 -0
- ksadk/runners/utils/langfuse.py +230 -0
- ksadk/runners/utils/loader.py +62 -0
- ksadk/runtime_context.py +166 -0
- ksadk/runtime_state.py +25 -0
- ksadk/sandbox/__init__.py +28 -0
- ksadk/sandbox/backends/__init__.py +9 -0
- ksadk/sandbox/backends/e2b.py +154 -0
- ksadk/sandbox/backends/local_process.py +131 -0
- ksadk/sandbox/base.py +101 -0
- ksadk/sandbox/factory.py +53 -0
- ksadk/sandbox/registry.py +187 -0
- ksadk/server/__init__.py +7 -0
- ksadk/server/api_models.py +73 -0
- ksadk/server/app.py +3789 -0
- ksadk/server/static/assets/ArtifactsPanel-C0meuOdi.js +1 -0
- ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +9 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +8 -0
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
- ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
- ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +1 -0
- ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
- ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
- ksadk/server/static/assets/arc-bj2rZnP5.js +1 -0
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +1 -0
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-2u1ltDIj.js +36 -0
- ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
- ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
- ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-D6fvdK20.js +132 -0
- ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-BK82L5n5.js +10 -0
- ksadk/server/static/assets/channel-BSSOwFIb.js +1 -0
- ksadk/server/static/assets/chunk-2J33WTMH-DLVdkeyQ.js +1 -0
- ksadk/server/static/assets/chunk-4BX2VUAB-BbwsYcbW.js +1 -0
- ksadk/server/static/assets/chunk-55IACEB6-DOairxzw.js +1 -0
- ksadk/server/static/assets/chunk-727SXJPM-hCO487gs.js +206 -0
- ksadk/server/static/assets/chunk-AQP2D5EJ-XU8vDOuG.js +231 -0
- ksadk/server/static/assets/chunk-FMBD7UC4-PrapyfMu.js +15 -0
- ksadk/server/static/assets/chunk-ND2GUHAM-CboKhTZ3.js +1 -0
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +1 -0
- ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
- ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
- ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
- ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
- ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
- ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
- ksadk/server/static/assets/cose-bilkent-S5V4N54A-_N8bJ-EU.js +1 -0
- ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
- ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
- ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
- ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
- ksadk/server/static/assets/dagre-BM42HDAG-FoDlo5J2.js +4 -0
- ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
- ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
- ksadk/server/static/assets/diagram-2AECGRRQ-BBp6ebBw.js +43 -0
- ksadk/server/static/assets/diagram-5GNKFQAL-CqOvG1PR.js +10 -0
- ksadk/server/static/assets/diagram-KO2AKTUF-O9T3t6ba.js +3 -0
- ksadk/server/static/assets/diagram-LMA3HP47-CGG8ekBO.js +24 -0
- ksadk/server/static/assets/diagram-OG6HWLK6-4BmH1EEg.js +24 -0
- ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
- ksadk/server/static/assets/dist-6kSZvCZh.js +1 -0
- ksadk/server/static/assets/dist-B67uF2uK.js +1 -0
- ksadk/server/static/assets/dist-BMKM66B4.js +1 -0
- ksadk/server/static/assets/dist-BVzcWa6D.js +1 -0
- ksadk/server/static/assets/dist-Bk1l05Ni.js +1 -0
- ksadk/server/static/assets/dist-BkJjNih3.js +1 -0
- ksadk/server/static/assets/dist-BmuTWcm7.js +9 -0
- ksadk/server/static/assets/dist-C2DKRFVK.js +1 -0
- ksadk/server/static/assets/dist-CWiTDN_V.js +6 -0
- ksadk/server/static/assets/dist-CZ6C3x8B.js +23 -0
- ksadk/server/static/assets/dist-CiZHB6ZW.js +1 -0
- ksadk/server/static/assets/dist-Cp1_jKwf.js +1 -0
- ksadk/server/static/assets/dist-Cwhk4lwI.js +2 -0
- ksadk/server/static/assets/dist-D5JGcxBx.js +11 -0
- ksadk/server/static/assets/dist-D99wf6_X.js +1 -0
- ksadk/server/static/assets/dist-DCpZrjaK.js +1 -0
- ksadk/server/static/assets/dist-DI-oVAXw.js +1 -0
- ksadk/server/static/assets/dist-DLMjL01k.js +1 -0
- ksadk/server/static/assets/dist-DLeM1OCK.js +1 -0
- ksadk/server/static/assets/dist-DM0rEQPk.js +1 -0
- ksadk/server/static/assets/dist-DbaH3p5h.js +1 -0
- ksadk/server/static/assets/dist-S4klkK5W.js +1 -0
- ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
- ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
- ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
- ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
- ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
- ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
- ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
- ksadk/server/static/assets/erDiagram-TEJ5UH35-oJhaci7S.js +85 -0
- ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +1 -0
- ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
- ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DtTmUan-.js +162 -0
- ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
- ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BSsb_eij.js +292 -0
- ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
- ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-BR5s6Pws.js +106 -0
- ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
- ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
- ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
- ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
- ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
- ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
- ksadk/server/static/assets/index-BH7-pC0B.js +165 -0
- ksadk/server/static/assets/index-DAURbCsP.css +1 -0
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +1 -0
- ksadk/server/static/assets/infoDiagram-5YYISTIA-DYPc5cvh.js +2 -0
- ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
- ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-MoBdALDg.js +70 -0
- ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
- ksadk/server/static/assets/journeyDiagram-JHISSGLW-DgLiQT8l.js +139 -0
- ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
- ksadk/server/static/assets/kanban-definition-UN3LZRKU-CsFx112F.js +89 -0
- ksadk/server/static/assets/katex-DRsiwpM2.js +265 -0
- ksadk/server/static/assets/linear-TNRQ5Orv.js +1 -0
- ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
- ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
- ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
- ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
- ksadk/server/static/assets/mermaid-parser.core-D2eMVOgo.js +161 -0
- ksadk/server/static/assets/mindmap-definition-RKZ34NQL-BtByYFhL.js +96 -0
- ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
- ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
- ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
- ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
- ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
- ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
- ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
- ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
- ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
- ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
- ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +1 -0
- ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
- ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +1 -0
- ksadk/server/static/assets/pieDiagram-4H26LBE5-DPnuCB8c.js +30 -0
- ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
- ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
- ksadk/server/static/assets/properties-C357ku5U.js +1 -0
- ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
- ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
- ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
- ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
- ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
- ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-miFpCPv4.js +7 -0
- ksadk/server/static/assets/r-DADKenSm.js +1 -0
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +1 -0
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-mjB_yYkW.js +84 -0
- ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
- ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
- ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-D7hSlZZZ.js +40 -0
- ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
- ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
- ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-D9Cx6hC3.js +162 -0
- ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
- ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
- ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
- ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
- ksadk/server/static/assets/solr-BBopId9w.js +1 -0
- ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
- ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
- ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
- ksadk/server/static/assets/stateDiagram-AJRCARHV-D_zrg47o.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +1 -0
- ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
- ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
- ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
- ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
- ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
- ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
- ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
- ksadk/server/static/assets/timeline-definition-PNZ67QCA-CwSec0Wa.js +120 -0
- ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +1 -0
- ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
- ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
- ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
- ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
- ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
- ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
- ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-agCVobiF.js +34 -0
- ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
- ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-TmrlhWNt.js +78 -0
- ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
- ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
- ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-DV71V-ah.js +7 -0
- ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
- ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
- ksadk/server/static/favicon.svg +1 -0
- ksadk/server/static/icons.svg +24 -0
- ksadk/server/static/index.html +14 -0
- ksadk/server/terminal_sessions.py +551 -0
- ksadk/sessions/__init__.py +283 -0
- ksadk/sessions/base.py +354 -0
- ksadk/sessions/continuity.py +313 -0
- ksadk/sessions/errors.py +7 -0
- ksadk/sessions/in_memory.py +273 -0
- ksadk/sessions/local_service.py +949 -0
- ksadk/sessions/postgres_service.py +838 -0
- ksadk/sessions/sqlite_service.py +13 -0
- ksadk/skills/__init__.py +9 -0
- ksadk/skills/loader.py +46 -0
- ksadk/skills/models.py +120 -0
- ksadk/skills/package_store.py +104 -0
- ksadk/skills/runtime/__init__.py +17 -0
- ksadk/skills/runtime/agent.py +183 -0
- ksadk/skills/runtime/artifacts.py +39 -0
- ksadk/skills/runtime/backends/__init__.py +1 -0
- ksadk/skills/runtime/backends/disabled.py +8 -0
- ksadk/skills/runtime/backends/e2b.py +168 -0
- ksadk/skills/runtime/backends/local.py +95 -0
- ksadk/skills/runtime/base.py +103 -0
- ksadk/skills/runtime/executor.py +219 -0
- ksadk/skills/runtime/factory.py +37 -0
- ksadk/skills/runtime/loader.py +131 -0
- ksadk/skills/runtime/registry.py +150 -0
- ksadk/skills/runtime/request.py +67 -0
- ksadk/skills/service_client.py +239 -0
- ksadk/skills/service_env.py +58 -0
- ksadk/skills/tool_defs.py +221 -0
- ksadk/terminal_client.py +69 -0
- ksadk/terminal_exec_policy.py +229 -0
- ksadk/tools/__init__.py +17 -0
- ksadk/tools/gateway.py +230 -0
- ksadk/tools/result_budget.py +154 -0
- ksadk/toolsets/__init__.py +852 -0
- ksadk/toolsets/_langchain.py +11 -0
- ksadk/toolsets/platform.py +93 -0
- ksadk/toolsets/sandbox.py +310 -0
- ksadk/toolsets/skills.py +217 -0
- ksadk/toolsets/web.py +266 -0
- ksadk/toolsets/workspace.py +878 -0
- ksadk/toolsets/workspace_state.py +44 -0
- ksadk/tracing/__init__.py +37 -0
- ksadk/tracing/exporters/__init__.py +1 -0
- ksadk/tracing/exporters/inmemory_exporter.py +116 -0
- ksadk/tracing/exporters/langfuse_exporter.py +445 -0
- ksadk/tracing/setup.py +829 -0
- ksadk/tracing/span_utils.py +114 -0
- ksadk/tui/__init__.py +12 -0
- ksadk/tui/app.py +400 -0
- ksadk/tui/clipboard.py +100 -0
- ksadk/tui/widgets/__init__.py +22 -0
- ksadk/tui/widgets/assistant.py +89 -0
- ksadk/tui/widgets/base.py +54 -0
- ksadk/tui/widgets/chat_input.py +246 -0
- ksadk/tui/widgets/history.py +156 -0
- ksadk/tui/widgets/system.py +87 -0
- ksadk/tui/widgets/thinking.py +110 -0
- ksadk/tui/widgets/tool_call.py +161 -0
- ksadk/tui/widgets/user.py +37 -0
- ksadk/ui_config.py +168 -0
- ksadk/version.py +4 -0
- ksadk_runtime_common/__init__.py +20 -0
- ksadk_runtime_common/memory_backend/__init__.py +29 -0
- ksadk_runtime_common/memory_backend/manifest.py +79 -0
- ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
- ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
- ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
- ksadk_runtime_common/memory_backend/registry.py +56 -0
- ksadk_runtime_common/memory_backend/render.py +71 -0
- ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
- ksadk_runtime_common/workspace_files/__init__.py +29 -0
- ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
- ksadk_runtime_common/workspace_files/constants.py +9 -0
- ksadk_runtime_common/workspace_files/path_utils.py +71 -0
- ksadk_runtime_common/workspace_files/preview.py +96 -0
- ksadk_runtime_common/workspace_files/router.py +254 -0
- agentengine_sdk_python-0.6.2.dist-info/METADATA +0 -46
- agentengine_sdk_python-0.6.2.dist-info/RECORD +0 -4
- 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()
|