agentengine-sdk-python 0.7.0__py3-none-any.whl → 0.8.0__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 (371) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
  2. agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
  3. ksadk/a2a/__init__.py +178 -11
  4. ksadk/a2a/bootstrap.py +334 -0
  5. ksadk/a2a/card.py +120 -0
  6. ksadk/a2a/context_store.py +203 -0
  7. ksadk/a2a/control_plane.py +818 -0
  8. ksadk/a2a/credential.py +99 -0
  9. ksadk/a2a/event_adapter.py +149 -0
  10. ksadk/a2a/executor.py +406 -89
  11. ksadk/a2a/external_transport.py +516 -0
  12. ksadk/a2a/identity.py +304 -0
  13. ksadk/a2a/ids.py +22 -0
  14. ksadk/a2a/langgraph.py +285 -0
  15. ksadk/a2a/resume_store.py +201 -0
  16. ksadk/a2a/routes.py +105 -0
  17. ksadk/a2a/server.py +107 -60
  18. ksadk/a2a/space_client.py +1101 -0
  19. ksadk/a2a/task_adapter.py +423 -0
  20. ksadk/a2a/task_event_dispatcher.py +167 -0
  21. ksadk/a2a/task_event_outbox.py +326 -0
  22. ksadk/a2a/task_store.py +206 -0
  23. ksadk/a2ui/__init__.py +27 -0
  24. ksadk/a2ui/core.py +186 -0
  25. ksadk/a2ui/models.py +166 -0
  26. ksadk/a2ui/renderer.py +198 -0
  27. ksadk/agui/__init__.py +9 -0
  28. ksadk/agui/a2ui_projection.py +92 -0
  29. ksadk/agui/agent.py +857 -0
  30. ksadk/agui/config.py +86 -0
  31. ksadk/agui/routes.py +55 -0
  32. ksadk/api/client.py +233 -191
  33. ksadk/builders/__init__.py +2 -0
  34. ksadk/builders/base.py +16 -14
  35. ksadk/builders/code_builder.py +318 -236
  36. ksadk/builders/container_builder.py +187 -167
  37. ksadk/builders/framework_requirements.py +29 -11
  38. ksadk/builders/ks3_uploader.py +58 -27
  39. ksadk/builders/managed_runtime_builder.py +145 -0
  40. ksadk/builders/mcp_builder.py +91 -92
  41. ksadk/builders/requirements_utils.py +1 -5
  42. ksadk/cli/__init__.py +89 -171
  43. ksadk/cli/cmd_a2a.py +254 -70
  44. ksadk/cli/cmd_agent.py +19 -5
  45. ksadk/cli/cmd_build.py +158 -29
  46. ksadk/cli/cmd_completion.py +47 -35
  47. ksadk/cli/cmd_config.py +267 -198
  48. ksadk/cli/cmd_create.py +500 -720
  49. ksadk/cli/cmd_dashboard.py +120 -45
  50. ksadk/cli/cmd_deploy.py +117 -58
  51. ksadk/cli/cmd_destroy.py +44 -14
  52. ksadk/cli/cmd_files.py +63 -24
  53. ksadk/cli/cmd_hermes.py +168 -89
  54. ksadk/cli/cmd_invoke.py +184 -122
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +98 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +483 -227
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +33 -10
  61. ksadk/cli/cmd_status.py +80 -31
  62. ksadk/cli/cmd_version.py +20 -13
  63. ksadk/cli/cmd_web.py +89 -13
  64. ksadk/cli/deploy_utils.py +20 -20
  65. ksadk/cli/dry_run.py +5 -3
  66. ksadk/cli/error_utils.py +37 -12
  67. ksadk/cli/global_options.py +1 -2
  68. ksadk/cli/local_runtime.py +1 -2
  69. ksadk/cli/model_catalog.py +6 -2
  70. ksadk/cli/network_options.py +34 -14
  71. ksadk/cli/resource_common.py +6 -2
  72. ksadk/cli/storage.py +0 -1
  73. ksadk/cli/ui.py +5 -3
  74. ksadk/cli/workflow_common.py +51 -17
  75. ksadk/codex/__init__.py +12 -0
  76. ksadk/codex/client.py +408 -0
  77. ksadk/codex/phase.py +143 -0
  78. ksadk/codex/runtime.py +449 -0
  79. ksadk/common/aicp_env.py +0 -1
  80. ksadk/common/auth.py +2 -2
  81. ksadk/common/constants.py +1 -3
  82. ksadk/common/llm_utils.py +17 -13
  83. ksadk/compat/__init__.py +5 -0
  84. ksadk/compat/adk_compat.py +200 -0
  85. ksadk/compat/copilotkit_a2ui.py +330 -0
  86. ksadk/configs/__init__.py +12 -12
  87. ksadk/configs/env_registry.py +500 -100
  88. ksadk/configs/global_config.py +2 -1
  89. ksadk/configs/settings.py +151 -124
  90. ksadk/conversations/__init__.py +7 -5
  91. ksadk/conversations/attachment_storage.py +16 -11
  92. ksadk/conversations/attachments.py +44 -36
  93. ksadk/conversations/compaction_pipeline.py +12 -12
  94. ksadk/conversations/compaction_prompt.py +9 -5
  95. ksadk/conversations/context.py +221 -6
  96. ksadk/conversations/message_projection.py +438 -51
  97. ksadk/conversations/model_context.py +12 -6
  98. ksadk/conversations/model_options.py +0 -1
  99. ksadk/conversations/normalize.py +20 -16
  100. ksadk/conversations/reasoning_markup.py +1 -1
  101. ksadk/conversations/runtime.py +339 -5255
  102. ksadk/conversations/runtime_compaction.py +333 -0
  103. ksadk/conversations/runtime_constants.py +27 -0
  104. ksadk/conversations/runtime_governance.py +185 -0
  105. ksadk/conversations/runtime_input.py +652 -0
  106. ksadk/conversations/runtime_invocation.py +395 -0
  107. ksadk/conversations/runtime_metadata.py +475 -0
  108. ksadk/conversations/runtime_observability.py +355 -0
  109. ksadk/conversations/runtime_payloads.py +272 -0
  110. ksadk/conversations/runtime_persistence.py +440 -0
  111. ksadk/conversations/runtime_preparation.py +418 -0
  112. ksadk/conversations/runtime_resume.py +816 -0
  113. ksadk/conversations/runtime_stream_events.py +902 -0
  114. ksadk/conversations/runtime_streaming.py +547 -0
  115. ksadk/conversations/semantic_summary.py +17 -5
  116. ksadk/conversations/session_title.py +6 -3
  117. ksadk/deployment/__init__.py +15 -16
  118. ksadk/deployment/agent_access.py +12 -23
  119. ksadk/deployment/base.py +51 -38
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +309 -202
  122. ksadk/deployment/registry.py +38 -32
  123. ksadk/deployment/state.py +11 -11
  124. ksadk/deployment/ui_config.py +1 -1
  125. ksadk/detection/__init__.py +1 -1
  126. ksadk/detection/detector.py +146 -83
  127. ksadk/detection/mcp_detector.py +9 -6
  128. ksadk/events/__init__.py +25 -0
  129. ksadk/events/parser.py +191 -0
  130. ksadk/events/replay.py +37 -0
  131. ksadk/events/runtime_event.py +264 -0
  132. ksadk/events/store.py +317 -0
  133. ksadk/harness/__init__.py +34 -0
  134. ksadk/harness/app.py +278 -0
  135. ksadk/harness/config.py +181 -0
  136. ksadk/harness/reasoner.py +105 -0
  137. ksadk/harness/runner.py +211 -0
  138. ksadk/harness/sandbox.py +141 -0
  139. ksadk/harness/tools.py +222 -0
  140. ksadk/hermes_terminal.py +23 -12
  141. ksadk/identity/resolver.py +15 -9
  142. ksadk/ids.py +32 -0
  143. ksadk/knowledge_base/adk_tool.py +1 -1
  144. ksadk/knowledge_base/client.py +23 -29
  145. ksadk/managed_runtime.py +188 -0
  146. ksadk/markdown.py +5 -2
  147. ksadk/mcp_runtime/__init__.py +9 -6
  148. ksadk/memory/__init__.py +1 -1
  149. ksadk/memory/adk/__init__.py +2 -0
  150. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  151. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  152. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  153. ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
  154. ksadk/memory/adk/long_term_memory.py +31 -35
  155. ksadk/memory/adk/resilient_session_service.py +4 -5
  156. ksadk/memory/adk/short_term_memory.py +12 -21
  157. ksadk/memory/adk_tool.py +3 -2
  158. ksadk/memory/backends/base.py +19 -19
  159. ksadk/memory/backends/memory.py +24 -25
  160. ksadk/memory/backends/redis.py +31 -30
  161. ksadk/memory/langchain_tool.py +2 -1
  162. ksadk/memory/manager.py +52 -47
  163. ksadk/memory/service.py +10 -8
  164. ksadk/model_policy.py +15 -7
  165. ksadk/model_proxy/__init__.py +51 -0
  166. ksadk/model_proxy/bootstrap.py +80 -0
  167. ksadk/model_proxy/cache.py +165 -0
  168. ksadk/model_proxy/config.py +45 -0
  169. ksadk/model_proxy/demo_webui.py +115 -0
  170. ksadk/model_proxy/detect.py +145 -0
  171. ksadk/model_proxy/gate.py +68 -0
  172. ksadk/model_proxy/namespace.py +173 -0
  173. ksadk/model_proxy/protocol_proxy.py +22 -0
  174. ksadk/model_proxy/server.py +300 -0
  175. ksadk/model_proxy/transform.py +691 -0
  176. ksadk/openclaw_gateway.py +29 -14
  177. ksadk/runners/adk_runner.py +345 -120
  178. ksadk/runners/base_runner.py +46 -29
  179. ksadk/runners/codex_runner.py +132 -0
  180. ksadk/runners/deepagents_runner.py +0 -1
  181. ksadk/runners/factory.py +17 -5
  182. ksadk/runners/langchain_runner.py +29 -742
  183. ksadk/runners/langgraph_runner.py +352 -90
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +134 -33
  186. ksadk/runners/usage_accumulator.py +1 -0
  187. ksadk/runners/utils/langfuse.py +3 -3
  188. ksadk/runners/utils/loader.py +7 -7
  189. ksadk/runtime/__init__.py +41 -0
  190. ksadk/runtime/adapter.py +345 -0
  191. ksadk/runtime/framework_adapters.py +124 -0
  192. ksadk/runtime/preprocessing.py +118 -0
  193. ksadk/runtime/runner_adapter.py +873 -0
  194. ksadk/runtime_context.py +18 -3
  195. ksadk/runtime_state.py +0 -1
  196. ksadk/sandbox/__init__.py +4 -1
  197. ksadk/sandbox/backends/__init__.py +4 -1
  198. ksadk/sandbox/backends/e2b.py +13 -5
  199. ksadk/sandbox/backends/local_process.py +11 -3
  200. ksadk/sandbox/base.py +10 -15
  201. ksadk/sandbox/factory.py +14 -4
  202. ksadk/sandbox/registry.py +57 -6
  203. ksadk/server/api_models.py +15 -5
  204. ksadk/server/app.py +455 -3885
  205. ksadk/server/composition.py +21 -0
  206. ksadk/server/factory.py +624 -0
  207. ksadk/server/routes/__init__.py +1 -0
  208. ksadk/server/routes/checkpoint_resolution.py +116 -0
  209. ksadk/server/routes/common.py +713 -0
  210. ksadk/server/routes/control.py +327 -0
  211. ksadk/server/routes/dependencies.py +98 -0
  212. ksadk/server/routes/misc.py +448 -0
  213. ksadk/server/routes/models.py +443 -0
  214. ksadk/server/routes/openai_compat.py +207 -0
  215. ksadk/server/routes/projection.py +795 -0
  216. ksadk/server/routes/routers.py +52 -0
  217. ksadk/server/routes/run.py +726 -0
  218. ksadk/server/routes/sessions.py +649 -0
  219. ksadk/server/routes/streaming.py +223 -0
  220. ksadk/server/routes/workspace.py +381 -0
  221. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
  222. ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
  224. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
  226. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-CFKwSk_B.js} +1 -1
  227. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
  228. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
  239. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
  240. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
  241. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
  242. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
  272. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
  273. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
  275. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
  276. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
  277. ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
  278. ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
  279. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
  280. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
  288. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
  289. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
  290. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
  292. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
  293. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
  297. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
  298. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
  299. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
  300. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
  301. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
  302. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
  303. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
  305. ksadk/server/static/index.html +2 -2
  306. ksadk/server/terminal_sessions.py +111 -58
  307. ksadk/sessions/__init__.py +69 -19
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +2 -3
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +143 -25
  313. ksadk/sessions/resilient.py +36 -8
  314. ksadk/skills/models.py +17 -5
  315. ksadk/skills/package_store.py +5 -2
  316. ksadk/skills/runtime/agent.py +20 -5
  317. ksadk/skills/runtime/artifacts.py +1 -5
  318. ksadk/skills/runtime/backends/e2b.py +10 -4
  319. ksadk/skills/runtime/backends/local.py +15 -3
  320. ksadk/skills/runtime/base.py +1 -2
  321. ksadk/skills/runtime/executor.py +10 -3
  322. ksadk/skills/runtime/factory.py +4 -2
  323. ksadk/skills/runtime/loader.py +1 -2
  324. ksadk/skills/runtime/registry.py +15 -10
  325. ksadk/skills/runtime/request.py +3 -1
  326. ksadk/skills/service_client.py +38 -17
  327. ksadk/skills/tool_defs.py +15 -10
  328. ksadk/terminal_exec_policy.py +3 -4
  329. ksadk/tools/gateway.py +92 -15
  330. ksadk/tools/result_budget.py +12 -4
  331. ksadk/toolsets/__init__.py +134 -36
  332. ksadk/toolsets/platform.py +21 -5
  333. ksadk/toolsets/sandbox.py +82 -16
  334. ksadk/toolsets/skills.py +200 -39
  335. ksadk/toolsets/web.py +123 -26
  336. ksadk/toolsets/workspace.py +212 -101
  337. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  338. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  339. ksadk/tracing/setup.py +69 -53
  340. ksadk/tracing/span_utils.py +5 -2
  341. ksadk/tui/loop.py +78 -30
  342. ksadk/tui/stream_render.py +1 -0
  343. ksadk/ui_config.py +5 -3
  344. ksadk/version.py +1 -1
  345. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  346. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  347. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  348. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  349. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  350. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  351. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  352. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  353. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  354. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  355. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  356. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  357. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  358. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  359. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  360. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  361. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  362. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  364. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  365. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  366. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  367. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  368. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  369. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  370. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  371. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,181 @@
1
+ """HarnessApp yaml 配置 — 最小子集 + 严格校验 (goal-08)。
2
+
3
+ 本期硬边界(防膨胀):**只支持最小子集** ``model`` / ``prompt``(instruction)/
4
+ ``mcp_tools`` / ``sandbox``。超出子集的字段(memory/knowledge/workflow/tracing 等)
5
+ **明确报"暂不支持"**,不硬翻、不静默忽略。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from typing import Any, Optional
13
+
14
+ import yaml # type: ignore[import-untyped]
15
+
16
+ #: 顶层允许的最小子集字段。
17
+ _ALLOWED_TOP_LEVEL = frozenset({"model", "prompt", "mcp_tools", "sandbox", "runtime"})
18
+ #: runtime 允许的值。
19
+ _ALLOWED_RUNTIME = frozenset({"yaml", "codex"})
20
+ #: mcp_tools 单项允许字段。
21
+ _ALLOWED_MCP_TOOL = frozenset({"name", "url", "api_key", "tool_filter", "tool_name_prefix"})
22
+ #: sandbox 允许字段。
23
+ _ALLOWED_SANDBOX = frozenset({"read_only"})
24
+
25
+ #: 已知但本期不支持的字段,给出更明确的指引。
26
+ _KNOWN_UNSUPPORTED = {
27
+ "memory": "memory(记忆)暂不支持,后续阶段提供",
28
+ "knowledge": "knowledge(知识库)暂不支持,后续阶段提供",
29
+ "workflow": "workflow(工作流编排)暂不支持,后续阶段提供",
30
+ "tracing": "tracing(可观测)暂不支持,后续阶段提供",
31
+ "skills": "skills(技能)暂不支持,后续阶段提供",
32
+ }
33
+
34
+
35
+ class HarnessConfigError(ValueError):
36
+ """HarnessApp yaml 配置错误(含"暂不支持"指引)。"""
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class McpToolSpec:
41
+ """MCP 工具条目(最小子集)。"""
42
+
43
+ name: str
44
+ url: str
45
+ api_key: Optional[str] = None
46
+ tool_filter: tuple[str, ...] = ()
47
+ tool_name_prefix: Optional[str] = None
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class SandboxPolicy:
52
+ """sandbox 策略。默认 read-only(goal-08:sandbox 默认 read-only)。"""
53
+
54
+ read_only: bool = True
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class HarnessConfig:
59
+ """HarnessApp 的最小 yaml 配置。"""
60
+
61
+ model: str
62
+ prompt: str
63
+ mcp_tools: tuple[McpToolSpec, ...] = ()
64
+ sandbox: SandboxPolicy = field(default_factory=SandboxPolicy)
65
+ runtime: str = "yaml"
66
+ """runtime 后端:``yaml``(YamlAgentRunner+LiteLLM)| ``codex``(CodexRunner+codex CLI)。"""
67
+
68
+ @classmethod
69
+ def from_dict(cls, data: dict[str, Any], *, source: str = "yaml") -> "HarnessConfig":
70
+ if not isinstance(data, dict):
71
+ raise HarnessConfigError(f"{source}: 顶层必须是 mapping,得到 {type(data).__name__}")
72
+
73
+ _reject_unknown_keys(data, _ALLOWED_TOP_LEVEL, where=source)
74
+
75
+ model = _require_str(data, "model", source)
76
+ prompt = _require_str(data, "prompt", source)
77
+
78
+ runtime = str(data.get("runtime", "yaml")).strip().lower()
79
+ if runtime not in _ALLOWED_RUNTIME:
80
+ raise HarnessConfigError(
81
+ f"{source}: runtime 仅支持 {sorted(_ALLOWED_RUNTIME)},得到 {runtime!r}"
82
+ )
83
+
84
+ mcp_tools = tuple(
85
+ _parse_mcp_tool(item, index, source)
86
+ for index, item in enumerate(
87
+ _require_list(data.get("mcp_tools", []), "mcp_tools", source)
88
+ )
89
+ )
90
+ sandbox = _parse_sandbox(data.get("sandbox", {}), source)
91
+ return cls(
92
+ model=model, prompt=prompt, mcp_tools=mcp_tools, sandbox=sandbox, runtime=runtime
93
+ )
94
+
95
+ @classmethod
96
+ def from_yaml(cls, path: str | Path) -> "HarnessConfig":
97
+ path = Path(path)
98
+ try:
99
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
100
+ except yaml.YAMLError as exc:
101
+ raise HarnessConfigError(f"{path}: YAML 解析失败: {exc}") from exc
102
+ if data is None:
103
+ data = {}
104
+ return cls.from_dict(data, source=str(path))
105
+
106
+
107
+ def _reject_unknown_keys(data: dict[str, Any], allowed: frozenset[str], *, where: str) -> None:
108
+ for key in data:
109
+ if key in allowed:
110
+ continue
111
+ if key in _KNOWN_UNSUPPORTED:
112
+ raise HarnessConfigError(f"{where}: 字段 {key!r} —— {_KNOWN_UNSUPPORTED[key]}")
113
+ raise HarnessConfigError(
114
+ f"{where}: 未知字段 {key!r};本期最小子集仅支持 {sorted(allowed)},"
115
+ "超出子集的字段不予支持(不静默忽略)"
116
+ )
117
+
118
+
119
+ def _require_str(data: dict[str, Any], key: str, where: str) -> str:
120
+ value = data.get(key)
121
+ if not isinstance(value, str) or not value.strip():
122
+ raise HarnessConfigError(f"{where}: 字段 {key!r} 必填且必须是非空字符串")
123
+ return value.strip()
124
+
125
+
126
+ def _require_list(value: Any, key: str, where: str) -> list:
127
+ if value is None:
128
+ return []
129
+ if not isinstance(value, list):
130
+ raise HarnessConfigError(f"{where}: 字段 {key!r} 必须是 list")
131
+ return value
132
+
133
+
134
+ def _parse_mcp_tool(item: Any, index: int, where: str) -> McpToolSpec:
135
+ if not isinstance(item, dict):
136
+ raise HarnessConfigError(f"{where}: mcp_tools[{index}] 必须是 mapping")
137
+ _reject_unknown_keys(item, _ALLOWED_MCP_TOOL, where=f"{where}.mcp_tools[{index}]")
138
+ name = _require_str(item, "name", f"{where}.mcp_tools[{index}]")
139
+ url = _require_str(item, "url", f"{where}.mcp_tools[{index}]")
140
+ tool_filter = item.get("tool_filter", ())
141
+ if not isinstance(tool_filter, (list, tuple)) or not all(
142
+ isinstance(t, str) for t in tool_filter
143
+ ):
144
+ raise HarnessConfigError(f"{where}.mcp_tools[{index}].tool_filter 必须是字符串 list")
145
+ api_key = item.get("api_key")
146
+ if api_key is not None and not isinstance(api_key, str):
147
+ raise HarnessConfigError(f"{where}.mcp_tools[{index}].api_key 必须是字符串")
148
+ prefix = item.get("tool_name_prefix")
149
+ if prefix is not None and not isinstance(prefix, str):
150
+ raise HarnessConfigError(f"{where}.mcp_tools[{index}].tool_name_prefix 必须是字符串")
151
+ return McpToolSpec(
152
+ name=name,
153
+ url=url,
154
+ api_key=api_key,
155
+ tool_filter=tuple(tool_filter),
156
+ tool_name_prefix=prefix,
157
+ )
158
+
159
+
160
+ def _parse_sandbox(value: Any, where: str) -> SandboxPolicy:
161
+ if value is None:
162
+ return SandboxPolicy()
163
+ if not isinstance(value, dict):
164
+ raise HarnessConfigError(f"{where}: sandbox 必须是 mapping")
165
+ _reject_unknown_keys(value, _ALLOWED_SANDBOX, where=f"{where}.sandbox")
166
+ read_only = value.get("read_only", True)
167
+ if not isinstance(read_only, bool):
168
+ raise HarnessConfigError(f"{where}.sandbox.read_only 必须是 bool")
169
+ if not read_only:
170
+ raise HarnessConfigError(
171
+ f"{where}.sandbox.read_only=false 暂不支持;Harness 当前只提供执行层强制的只读策略"
172
+ )
173
+ return SandboxPolicy(read_only=read_only)
174
+
175
+
176
+ __all__ = [
177
+ "HarnessConfig",
178
+ "HarnessConfigError",
179
+ "McpToolSpec",
180
+ "SandboxPolicy",
181
+ ]
@@ -0,0 +1,105 @@
1
+ """Reasoning boundary used by the Harness runner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from dataclasses import dataclass
8
+ from typing import Any, Protocol, Sequence
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class HarnessToolCall:
13
+ call_id: str
14
+ name: str
15
+ arguments: dict[str, Any]
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class HarnessReasoningTurn:
20
+ final_text: str | None = None
21
+ tool_calls: tuple[HarnessToolCall, ...] = ()
22
+
23
+
24
+ class HarnessReasoner(Protocol):
25
+ async def complete(
26
+ self,
27
+ *,
28
+ model: str,
29
+ prompt: str,
30
+ messages: Sequence[dict[str, Any]],
31
+ tools: Sequence[Any],
32
+ ) -> HarnessReasoningTurn: ...
33
+
34
+
35
+ class LiteLLMHarnessReasoner:
36
+ """Use the project's OpenAI-compatible LiteLLM configuration for tool reasoning."""
37
+
38
+ async def complete(
39
+ self,
40
+ *,
41
+ model: str,
42
+ prompt: str,
43
+ messages: Sequence[dict[str, Any]],
44
+ tools: Sequence[Any],
45
+ ) -> HarnessReasoningTurn:
46
+ del prompt
47
+ try:
48
+ from litellm import acompletion
49
+ except ImportError as exc: # pragma: no cover - depends on optional install
50
+ raise RuntimeError(
51
+ "Harness reasoning requires the 'adk' extra (litellm); "
52
+ "install ksadk[adk] or inject a HarnessReasoner"
53
+ ) from exc
54
+
55
+ resolved_model = model if "/" in model else f"openai/{model}"
56
+ kwargs: dict[str, Any] = {
57
+ "model": resolved_model,
58
+ "messages": list(messages),
59
+ "tools": [tool.openai_schema for tool in tools],
60
+ "tool_choice": "auto",
61
+ }
62
+ base_url = os.getenv("OPENAI_BASE_URL")
63
+ api_key = os.getenv("OPENAI_API_KEY")
64
+ if base_url:
65
+ kwargs["base_url"] = base_url
66
+ if api_key:
67
+ kwargs["api_key"] = api_key
68
+ response = await acompletion(**kwargs)
69
+ choices = getattr(response, "choices", None) or []
70
+ if not choices:
71
+ raise RuntimeError(f"Harness model {model!r} returned no choices")
72
+ message = choices[0].message
73
+ calls: list[HarnessToolCall] = []
74
+ for index, call in enumerate(getattr(message, "tool_calls", None) or []):
75
+ function = getattr(call, "function", None)
76
+ name = str(getattr(function, "name", "") or "").strip()
77
+ raw_arguments = getattr(function, "arguments", None) or "{}"
78
+ try:
79
+ arguments = (
80
+ json.loads(raw_arguments) if isinstance(raw_arguments, str) else raw_arguments
81
+ )
82
+ except json.JSONDecodeError as exc:
83
+ raise RuntimeError(
84
+ f"Harness model emitted invalid JSON arguments for tool {name!r}"
85
+ ) from exc
86
+ if not name or not isinstance(arguments, dict):
87
+ raise RuntimeError("Harness model emitted an invalid tool call")
88
+ calls.append(
89
+ HarnessToolCall(
90
+ call_id=str(getattr(call, "id", "") or f"tool-call-{index}"),
91
+ name=name,
92
+ arguments=dict(arguments),
93
+ )
94
+ )
95
+ content = getattr(message, "content", None)
96
+ final_text = str(content) if content is not None else None
97
+ return HarnessReasoningTurn(final_text=final_text, tool_calls=tuple(calls))
98
+
99
+
100
+ __all__ = [
101
+ "HarnessReasoner",
102
+ "HarnessReasoningTurn",
103
+ "HarnessToolCall",
104
+ "LiteLLMHarnessReasoner",
105
+ ]
@@ -0,0 +1,211 @@
1
+ """Runner that composes model reasoning with real MCP and sandbox tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from pathlib import Path
8
+ from types import SimpleNamespace
9
+ from typing import Any, AsyncIterator, Dict
10
+
11
+ from ksadk.harness.config import HarnessConfig
12
+ from ksadk.harness.reasoner import (
13
+ HarnessReasoner,
14
+ HarnessReasoningTurn,
15
+ HarnessToolCall,
16
+ LiteLLMHarnessReasoner,
17
+ )
18
+ from ksadk.harness.sandbox import HarnessSandboxExecutor
19
+ from ksadk.harness.tools import HarnessTool, load_mcp_tools, sandbox_tools, tool_result_text
20
+ from ksadk.runners.base_runner import BaseRunner
21
+
22
+ _MAX_REASONING_TURNS = 8
23
+
24
+
25
+ class YamlAgentRunner(BaseRunner):
26
+ """Execute one Harness config through the shared runner/runtime contract."""
27
+
28
+ def __init__(
29
+ self,
30
+ config: HarnessConfig,
31
+ *,
32
+ agent_name: str = "harness-agent",
33
+ reasoner: HarnessReasoner | None = None,
34
+ workspace_root: str | Path = ".",
35
+ ) -> None:
36
+ super().__init__(
37
+ detection_result=SimpleNamespace(
38
+ name=agent_name,
39
+ type=SimpleNamespace(value="harness"),
40
+ ),
41
+ project_dir=str(workspace_root),
42
+ )
43
+ self._config = config
44
+ self._reasoner = reasoner or LiteLLMHarnessReasoner()
45
+ self._sandbox = HarnessSandboxExecutor(
46
+ workspace_root=workspace_root,
47
+ read_only=config.sandbox.read_only,
48
+ )
49
+ self._loaded = False
50
+ self._load_lock = asyncio.Lock()
51
+ self._tools: tuple[HarnessTool, ...] | None = None
52
+ self._mcp_toolsets: list[Any] = []
53
+
54
+ @property
55
+ def harness_config(self) -> HarnessConfig:
56
+ return self._config
57
+
58
+ @property
59
+ def sandbox(self) -> HarnessSandboxExecutor:
60
+ return self._sandbox
61
+
62
+ @property
63
+ def workspace_root(self) -> Path:
64
+ return self._sandbox.workspace_root
65
+
66
+ def load_agent(self) -> None:
67
+ # MCP discovery is async and therefore happens lazily on first invocation.
68
+ self._loaded = True
69
+
70
+ def prepare_for_request(self, model: str | None) -> None:
71
+ # Request model is carried in input_data. Do not mutate process-wide model env.
72
+ del model
73
+
74
+ def _effective(self, input_data: Dict[str, Any]) -> tuple[str, str]:
75
+ metadata = input_data.get("metadata") or {}
76
+ if not isinstance(metadata, dict):
77
+ metadata = {}
78
+ model = str(
79
+ metadata.get("model_override") or input_data.get("model") or self._config.model
80
+ ).strip()
81
+ prompt = str(
82
+ metadata.get("prompt_override") or input_data.get("instructions") or self._config.prompt
83
+ ).strip()
84
+ return model, prompt
85
+
86
+ async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
87
+ tools = await self._ensure_tools()
88
+ model, prompt = self._effective(input_data)
89
+ user_input = str(input_data.get("input") or "")
90
+ messages: list[dict[str, Any]] = [
91
+ {"role": "system", "content": prompt},
92
+ {"role": "user", "content": user_input},
93
+ ]
94
+ execution_log: list[dict[str, Any]] = []
95
+
96
+ for _turn_number in range(_MAX_REASONING_TURNS):
97
+ turn = await self._reasoner.complete(
98
+ model=model,
99
+ prompt=prompt,
100
+ messages=tuple(messages),
101
+ tools=tools,
102
+ )
103
+ if turn.tool_calls:
104
+ messages.append(
105
+ {
106
+ "role": "assistant",
107
+ "content": turn.final_text,
108
+ "tool_calls": [
109
+ {
110
+ "id": call.call_id,
111
+ "type": "function",
112
+ "function": {
113
+ "name": call.name,
114
+ "arguments": json.dumps(call.arguments, ensure_ascii=False),
115
+ },
116
+ }
117
+ for call in turn.tool_calls
118
+ ],
119
+ }
120
+ )
121
+ for call in turn.tool_calls:
122
+ tool = next((item for item in tools if item.name == call.name), None)
123
+ if tool is None:
124
+ raise RuntimeError(
125
+ f"Harness tool {call.name!r} is not available; it may be filtered "
126
+ "or was not exposed by its MCP server"
127
+ )
128
+ result = await tool.call(call.arguments, call_id=call.call_id)
129
+ content = tool_result_text(result)
130
+ execution_log.append(
131
+ {
132
+ "call_id": call.call_id,
133
+ "name": call.name,
134
+ "source": tool.source,
135
+ "result": result,
136
+ }
137
+ )
138
+ messages.append(
139
+ {
140
+ "role": "tool",
141
+ "tool_call_id": call.call_id,
142
+ "name": call.name,
143
+ "content": content,
144
+ }
145
+ )
146
+ continue
147
+ if turn.final_text is None:
148
+ raise RuntimeError(
149
+ "Harness reasoner returned neither a final response nor a tool call"
150
+ )
151
+ return {
152
+ "output": turn.final_text,
153
+ "model": model,
154
+ "prompt": prompt,
155
+ "tool_calls": execution_log,
156
+ "sandbox_read_only": self._config.sandbox.read_only,
157
+ }
158
+ raise RuntimeError(f"Harness reasoning exceeded {_MAX_REASONING_TURNS} turns")
159
+
160
+ async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
161
+ result = await self.invoke(input_data)
162
+ yield {"delta": result["output"], "type": "text"}
163
+ yield {
164
+ "output": result["output"],
165
+ "type": "final",
166
+ "model": result["model"],
167
+ "tool_calls": result["tool_calls"],
168
+ "sandbox_read_only": result["sandbox_read_only"],
169
+ }
170
+
171
+ async def close(self) -> None:
172
+ toolsets, self._mcp_toolsets = self._mcp_toolsets, []
173
+ self._tools = None
174
+ if toolsets:
175
+ await asyncio.gather(*(toolset.close() for toolset in toolsets), return_exceptions=True)
176
+
177
+ async def _ensure_tools(self) -> tuple[HarnessTool, ...]:
178
+ if self._tools is not None:
179
+ return self._tools
180
+ async with self._load_lock:
181
+ if self._tools is not None:
182
+ return self._tools
183
+ tools = sandbox_tools(self._sandbox)
184
+ try:
185
+ for spec in self._config.mcp_tools:
186
+ toolset, discovered = await load_mcp_tools(spec)
187
+ self._mcp_toolsets.append(toolset)
188
+ tools.extend(discovered)
189
+ duplicates = sorted(
190
+ {
191
+ tool.name
192
+ for tool in tools
193
+ if sum(item.name == tool.name for item in tools) > 1
194
+ }
195
+ )
196
+ if duplicates:
197
+ raise RuntimeError(f"Harness tool names are not unique: {duplicates}")
198
+ except Exception:
199
+ await self.close()
200
+ raise
201
+ self._tools = tuple(tools)
202
+ return self._tools
203
+
204
+
205
+ __all__ = [
206
+ "HarnessReasoner",
207
+ "HarnessReasoningTurn",
208
+ "HarnessToolCall",
209
+ "LiteLLMHarnessReasoner",
210
+ "YamlAgentRunner",
211
+ ]
@@ -0,0 +1,141 @@
1
+ """Invocation-owned execution policy for Harness sandbox tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import os
7
+ import shlex
8
+ import subprocess
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ class SandboxPolicyDenied(PermissionError):
14
+ """Raised before execution when a Harness sandbox action violates policy."""
15
+
16
+
17
+ _READ_COMMANDS = frozenset({"cat", "head", "ls", "pwd", "stat", "tail", "wc"})
18
+ _NO_PATH_COMMANDS = frozenset({"pwd"})
19
+ _REQUIRE_PATH_COMMANDS = frozenset({"cat", "head", "stat", "tail", "wc"})
20
+ _VALUE_OPTIONS = frozenset({"-n", "--bytes", "--lines", "--max-unchanged-stats"})
21
+ _SHELL_OPERATORS = frozenset({"|", "||", "&", "&&", ";", ">", ">>", "<", "<<"})
22
+
23
+
24
+ class HarnessSandboxExecutor:
25
+ """Run a deliberately small read-only command surface inside one workspace.
26
+
27
+ Commands are executed without a shell. Path operands are resolved before the
28
+ child process starts, including symlinks, so reads cannot escape the configured
29
+ workspace. The narrow command surface also excludes network clients and process
30
+ launchers entirely.
31
+ """
32
+
33
+ def __init__(self, *, workspace_root: str | Path, read_only: bool = True) -> None:
34
+ self._workspace_root = Path(workspace_root).expanduser().resolve()
35
+ self._workspace_root.mkdir(parents=True, exist_ok=True)
36
+ self._read_only = read_only
37
+
38
+ @property
39
+ def workspace_root(self) -> Path:
40
+ return self._workspace_root
41
+
42
+ @property
43
+ def read_only(self) -> bool:
44
+ return self._read_only
45
+
46
+ async def read_file(self, path: str) -> dict[str, Any]:
47
+ target = self._resolve_path(path)
48
+ content = await asyncio.to_thread(target.read_text, encoding="utf-8")
49
+ return {
50
+ "ok": True,
51
+ "path": target.relative_to(self._workspace_root).as_posix(),
52
+ "content": content,
53
+ "policy": "read-only",
54
+ }
55
+
56
+ async def run_command(self, command: str) -> dict[str, Any]:
57
+ argv = self._validate_command(command)
58
+ completed = await asyncio.to_thread(
59
+ subprocess.run,
60
+ argv,
61
+ cwd=self._workspace_root,
62
+ env={"PATH": os.environ.get("PATH", "")},
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=10,
66
+ check=False,
67
+ )
68
+ return {
69
+ "ok": completed.returncode == 0,
70
+ "command": " ".join(argv),
71
+ "stdout": completed.stdout,
72
+ "stderr": completed.stderr,
73
+ "exit_code": completed.returncode,
74
+ "policy": "read-only",
75
+ }
76
+
77
+ def _validate_command(self, command: str) -> list[str]:
78
+ try:
79
+ argv = shlex.split(str(command or ""), posix=True)
80
+ except ValueError as exc:
81
+ raise SandboxPolicyDenied(f"read-only sandbox denied malformed command: {exc}") from exc
82
+ if not argv:
83
+ raise SandboxPolicyDenied("read-only sandbox denied empty command")
84
+ if any(argument in _SHELL_OPERATORS for argument in argv):
85
+ raise SandboxPolicyDenied(
86
+ "read-only sandbox denied shell redirection, pipelines, or command chaining"
87
+ )
88
+ executable = argv[0]
89
+ if executable not in _READ_COMMANDS:
90
+ raise SandboxPolicyDenied(
91
+ f"read-only sandbox denied command {executable!r}; network, write, and "
92
+ "dangerous process actions are not allowed"
93
+ )
94
+ if executable in _NO_PATH_COMMANDS:
95
+ if len(argv) != 1:
96
+ raise SandboxPolicyDenied(
97
+ f"read-only sandbox denied arguments for command {executable!r}"
98
+ )
99
+ return argv
100
+
101
+ normalized = [executable]
102
+ skip_option_value = False
103
+ saw_path = False
104
+ for argument in argv[1:]:
105
+ if skip_option_value:
106
+ normalized.append(argument)
107
+ skip_option_value = False
108
+ continue
109
+ if argument == "--":
110
+ normalized.append(argument)
111
+ continue
112
+ if argument in _VALUE_OPTIONS:
113
+ normalized.append(argument)
114
+ skip_option_value = True
115
+ continue
116
+ if argument.startswith("-"):
117
+ normalized.append(argument)
118
+ continue
119
+ target = self._resolve_path(argument)
120
+ normalized.append(str(target))
121
+ saw_path = True
122
+ if executable in _REQUIRE_PATH_COMMANDS and not saw_path:
123
+ raise SandboxPolicyDenied(
124
+ f"read-only sandbox denied command {executable!r} without a workspace path"
125
+ )
126
+ return normalized
127
+
128
+ def _resolve_path(self, path: str) -> Path:
129
+ raw = str(path or "").strip()
130
+ if not raw:
131
+ raise SandboxPolicyDenied("read-only sandbox requires a workspace path")
132
+ candidate = Path(raw)
133
+ if not candidate.is_absolute():
134
+ candidate = self._workspace_root / candidate
135
+ resolved = candidate.expanduser().resolve()
136
+ if resolved != self._workspace_root and self._workspace_root not in resolved.parents:
137
+ raise SandboxPolicyDenied(f"read-only sandbox denied path outside workspace: {raw!r}")
138
+ return resolved
139
+
140
+
141
+ __all__ = ["HarnessSandboxExecutor", "SandboxPolicyDenied"]