agentengine-sdk-python 0.7.0__py3-none-any.whl → 0.8.1__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 (450) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/METADATA +52 -20
  2. agentengine_sdk_python-0.8.1.dist-info/RECORD +644 -0
  3. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/WHEEL +1 -1
  4. ksadk/a2a/__init__.py +180 -11
  5. ksadk/a2a/bootstrap.py +332 -0
  6. ksadk/a2a/card.py +15 -0
  7. ksadk/a2a/context_store.py +203 -0
  8. ksadk/a2a/control_plane.py +1056 -0
  9. ksadk/a2a/credential.py +99 -0
  10. ksadk/a2a/event_adapter.py +149 -0
  11. ksadk/a2a/executor.py +361 -137
  12. ksadk/a2a/external_transport.py +516 -0
  13. ksadk/a2a/identity.py +304 -0
  14. ksadk/a2a/ids.py +22 -0
  15. ksadk/a2a/langgraph.py +285 -0
  16. ksadk/a2a/resume_store.py +201 -0
  17. ksadk/a2a/routes.py +112 -0
  18. ksadk/a2a/server.py +102 -62
  19. ksadk/a2a/service_env.py +33 -0
  20. ksadk/a2a/space_client.py +1122 -0
  21. ksadk/a2a/task_adapter.py +423 -0
  22. ksadk/a2a/task_event_dispatcher.py +167 -0
  23. ksadk/a2a/task_event_outbox.py +326 -0
  24. ksadk/a2a/task_store.py +206 -0
  25. ksadk/a2a_card.py +101 -0
  26. ksadk/a2ui/__init__.py +27 -0
  27. ksadk/a2ui/core.py +186 -0
  28. ksadk/a2ui/models.py +166 -0
  29. ksadk/a2ui/renderer.py +198 -0
  30. ksadk/agui/__init__.py +9 -0
  31. ksadk/agui/a2ui_projection.py +92 -0
  32. ksadk/agui/agent.py +872 -0
  33. ksadk/agui/config.py +92 -0
  34. ksadk/agui/routes.py +47 -0
  35. ksadk/api/client.py +238 -204
  36. ksadk/api/environment.py +27 -0
  37. ksadk/builders/__init__.py +2 -0
  38. ksadk/builders/base.py +16 -14
  39. ksadk/builders/code_builder.py +375 -270
  40. ksadk/builders/container_builder.py +240 -200
  41. ksadk/builders/framework_requirements.py +31 -12
  42. ksadk/builders/ks3_uploader.py +60 -29
  43. ksadk/builders/managed_runtime_builder.py +173 -0
  44. ksadk/builders/mcp_builder.py +91 -92
  45. ksadk/builders/requirements_utils.py +1 -5
  46. ksadk/cli/__init__.py +97 -171
  47. ksadk/cli/cmd_a2a.py +255 -84
  48. ksadk/cli/cmd_agent.py +19 -5
  49. ksadk/cli/cmd_build.py +158 -29
  50. ksadk/cli/cmd_completion.py +47 -35
  51. ksadk/cli/cmd_config.py +267 -198
  52. ksadk/cli/cmd_create.py +507 -740
  53. ksadk/cli/cmd_dashboard.py +120 -45
  54. ksadk/cli/cmd_deploy.py +135 -59
  55. ksadk/cli/cmd_destroy.py +44 -14
  56. ksadk/cli/cmd_eval.py +386 -0
  57. ksadk/cli/cmd_files.py +63 -24
  58. ksadk/cli/cmd_hermes.py +313 -135
  59. ksadk/cli/cmd_invoke.py +184 -122
  60. ksadk/cli/cmd_launch.py +76 -28
  61. ksadk/cli/cmd_mcp.py +98 -64
  62. ksadk/cli/cmd_model.py +14 -5
  63. ksadk/cli/cmd_openclaw.py +564 -268
  64. ksadk/cli/cmd_replay.py +71 -0
  65. ksadk/cli/cmd_run.py +58 -73
  66. ksadk/cli/cmd_status.py +80 -31
  67. ksadk/cli/cmd_studio.py +110 -0
  68. ksadk/cli/cmd_version.py +20 -13
  69. ksadk/cli/cmd_web.py +95 -18
  70. ksadk/cli/deploy_utils.py +20 -20
  71. ksadk/cli/dry_run.py +7 -4
  72. ksadk/cli/env_options.py +83 -0
  73. ksadk/cli/error_utils.py +37 -12
  74. ksadk/cli/global_options.py +4 -3
  75. ksadk/cli/local_runtime.py +1 -2
  76. ksadk/cli/model_catalog.py +6 -2
  77. ksadk/cli/network_options.py +41 -19
  78. ksadk/cli/resource_common.py +6 -2
  79. ksadk/cli/runtime_bootstrap.py +51 -0
  80. ksadk/cli/storage.py +0 -1
  81. ksadk/cli/ui.py +11 -3
  82. ksadk/cli/workflow_common.py +52 -18
  83. ksadk/codex/__init__.py +12 -0
  84. ksadk/codex/client.py +1042 -0
  85. ksadk/codex/phase.py +143 -0
  86. ksadk/codex/runtime.py +940 -0
  87. ksadk/common/aicp_env.py +0 -1
  88. ksadk/common/auth.py +2 -2
  89. ksadk/common/constants.py +1 -3
  90. ksadk/common/kop_client.py +232 -0
  91. ksadk/common/llm_utils.py +17 -13
  92. ksadk/compat/__init__.py +5 -0
  93. ksadk/compat/adk_compat.py +200 -0
  94. ksadk/compat/copilotkit_a2ui.py +330 -0
  95. ksadk/configs/__init__.py +12 -14
  96. ksadk/configs/env_registry.py +661 -116
  97. ksadk/configs/global_config.py +3 -2
  98. ksadk/configs/settings.py +133 -209
  99. ksadk/conversations/__init__.py +85 -92
  100. ksadk/conversations/attachment_storage.py +16 -11
  101. ksadk/conversations/attachments.py +44 -36
  102. ksadk/conversations/compaction_pipeline.py +12 -12
  103. ksadk/conversations/compaction_prompt.py +9 -5
  104. ksadk/conversations/context.py +221 -6
  105. ksadk/conversations/message_projection.py +438 -51
  106. ksadk/conversations/model_context.py +12 -6
  107. ksadk/conversations/model_options.py +0 -1
  108. ksadk/conversations/normalize.py +20 -16
  109. ksadk/conversations/reasoning_markup.py +1 -1
  110. ksadk/conversations/runtime.py +339 -5255
  111. ksadk/conversations/runtime_compaction.py +333 -0
  112. ksadk/conversations/runtime_constants.py +27 -0
  113. ksadk/conversations/runtime_governance.py +185 -0
  114. ksadk/conversations/runtime_input.py +660 -0
  115. ksadk/conversations/runtime_invocation.py +395 -0
  116. ksadk/conversations/runtime_metadata.py +475 -0
  117. ksadk/conversations/runtime_observability.py +367 -0
  118. ksadk/conversations/runtime_payloads.py +272 -0
  119. ksadk/conversations/runtime_persistence.py +440 -0
  120. ksadk/conversations/runtime_preparation.py +426 -0
  121. ksadk/conversations/runtime_resume.py +836 -0
  122. ksadk/conversations/runtime_stream_events.py +902 -0
  123. ksadk/conversations/runtime_streaming.py +700 -0
  124. ksadk/conversations/semantic_summary.py +17 -5
  125. ksadk/conversations/session_title.py +6 -3
  126. ksadk/deployment/__init__.py +15 -16
  127. ksadk/deployment/agent_access.py +12 -23
  128. ksadk/deployment/base.py +51 -38
  129. ksadk/deployment/managed_runtime.py +33 -0
  130. ksadk/deployment/manager.py +85 -71
  131. ksadk/deployment/providers/serverless.py +379 -210
  132. ksadk/deployment/registry.py +38 -32
  133. ksadk/deployment/state.py +11 -11
  134. ksadk/deployment/ui_config.py +1 -1
  135. ksadk/detection/__init__.py +1 -1
  136. ksadk/detection/detector.py +146 -83
  137. ksadk/detection/mcp_detector.py +9 -6
  138. ksadk/evaluation/__init__.py +84 -0
  139. ksadk/evaluation/a2a_adapter.py +444 -0
  140. ksadk/evaluation/adapters.py +54 -0
  141. ksadk/evaluation/contracts.py +409 -0
  142. ksadk/evaluation/evalset.py +351 -0
  143. ksadk/evaluation/evaluators.py +461 -0
  144. ksadk/evaluation/executor.py +118 -0
  145. ksadk/evaluation/storage.py +68 -0
  146. ksadk/evaluation/target.py +73 -0
  147. ksadk/events/__init__.py +25 -0
  148. ksadk/events/parser.py +191 -0
  149. ksadk/events/replay.py +37 -0
  150. ksadk/events/runtime_event.py +273 -0
  151. ksadk/events/store.py +317 -0
  152. ksadk/harness/__init__.py +35 -0
  153. ksadk/harness/app.py +269 -0
  154. ksadk/harness/config.py +181 -0
  155. ksadk/harness/reasoner.py +105 -0
  156. ksadk/harness/runtime.py +364 -0
  157. ksadk/harness/sandbox.py +141 -0
  158. ksadk/harness/tools.py +222 -0
  159. ksadk/hermes_terminal.py +28 -12
  160. ksadk/identity/resolver.py +15 -9
  161. ksadk/ids.py +32 -0
  162. ksadk/knowledge_base/adk_tool.py +1 -1
  163. ksadk/knowledge_base/client.py +38 -33
  164. ksadk/managed_a2a_card.py +160 -0
  165. ksadk/managed_runtime.py +195 -0
  166. ksadk/markdown.py +5 -2
  167. ksadk/mcp_runtime/__init__.py +9 -6
  168. ksadk/memory/__init__.py +1 -1
  169. ksadk/memory/adk/__init__.py +2 -0
  170. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  171. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  172. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  173. ksadk/memory/adk/backends/sdk_ltm_backend.py +36 -36
  174. ksadk/memory/adk/long_term_memory.py +34 -37
  175. ksadk/memory/adk/resilient_session_service.py +4 -5
  176. ksadk/memory/adk/short_term_memory.py +12 -21
  177. ksadk/memory/adk_tool.py +3 -2
  178. ksadk/memory/backends/base.py +19 -19
  179. ksadk/memory/backends/memory.py +24 -25
  180. ksadk/memory/backends/redis.py +31 -30
  181. ksadk/memory/langchain_tool.py +2 -1
  182. ksadk/memory/manager.py +52 -47
  183. ksadk/memory/service.py +16 -6
  184. ksadk/memory/tool.py +4 -0
  185. ksadk/model_policy.py +15 -7
  186. ksadk/model_proxy/__init__.py +51 -0
  187. ksadk/model_proxy/bootstrap.py +80 -0
  188. ksadk/model_proxy/cache.py +165 -0
  189. ksadk/model_proxy/config.py +66 -0
  190. ksadk/model_proxy/demo_webui.py +115 -0
  191. ksadk/model_proxy/detect.py +145 -0
  192. ksadk/model_proxy/gate.py +68 -0
  193. ksadk/model_proxy/namespace.py +201 -0
  194. ksadk/model_proxy/protocol_proxy.py +22 -0
  195. ksadk/model_proxy/server.py +443 -0
  196. ksadk/model_proxy/transform.py +735 -0
  197. ksadk/openclaw_gateway.py +29 -14
  198. ksadk/runners/adk_runner.py +354 -146
  199. ksadk/runners/base_runner.py +71 -29
  200. ksadk/runners/deepagents_runner.py +0 -1
  201. ksadk/runners/factory.py +18 -5
  202. ksadk/runners/langchain_runner.py +29 -742
  203. ksadk/runners/langgraph_runner.py +417 -97
  204. ksadk/runners/patch_langchain.py +16 -16
  205. ksadk/runners/remote_runner.py +134 -33
  206. ksadk/runners/usage_accumulator.py +1 -0
  207. ksadk/runners/utils/__init__.py +0 -10
  208. ksadk/runners/utils/loader.py +23 -7
  209. ksadk/runtime/__init__.py +67 -0
  210. ksadk/runtime/adapter.py +412 -0
  211. ksadk/runtime/conversation_execution.py +553 -0
  212. ksadk/runtime/executor.py +251 -0
  213. ksadk/runtime/factory.py +156 -0
  214. ksadk/runtime/framework_adapters.py +118 -0
  215. ksadk/runtime/launch.py +48 -0
  216. ksadk/runtime/preprocessing.py +131 -0
  217. ksadk/runtime/runner_adapter.py +1032 -0
  218. ksadk/runtime/runner_loading.py +36 -0
  219. ksadk/runtime_context.py +18 -3
  220. ksadk/runtime_state.py +0 -1
  221. ksadk/sandbox/__init__.py +4 -1
  222. ksadk/sandbox/backends/__init__.py +4 -1
  223. ksadk/sandbox/backends/e2b.py +13 -5
  224. ksadk/sandbox/backends/local_process.py +11 -3
  225. ksadk/sandbox/base.py +10 -15
  226. ksadk/sandbox/factory.py +14 -4
  227. ksadk/sandbox/registry.py +57 -6
  228. ksadk/server/__init__.py +39 -4
  229. ksadk/server/api_models.py +15 -5
  230. ksadk/server/app.py +24 -3928
  231. ksadk/server/composition.py +47 -0
  232. ksadk/server/factory.py +622 -0
  233. ksadk/server/routes/__init__.py +1 -0
  234. ksadk/server/routes/checkpoint_resolution.py +116 -0
  235. ksadk/server/routes/common.py +682 -0
  236. ksadk/server/routes/control.py +331 -0
  237. ksadk/server/routes/dependencies.py +98 -0
  238. ksadk/server/routes/misc.py +425 -0
  239. ksadk/server/routes/models.py +451 -0
  240. ksadk/server/routes/openai_compat.py +229 -0
  241. ksadk/server/routes/projection.py +848 -0
  242. ksadk/server/routes/routers.py +52 -0
  243. ksadk/server/routes/run.py +519 -0
  244. ksadk/server/routes/sessions.py +644 -0
  245. ksadk/server/routes/streaming.py +265 -0
  246. ksadk/server/routes/workspace.py +414 -0
  247. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-CakCqxEN.js} +1 -1
  248. ksadk/server/static/assets/CodeBlock-CynLknyO.js +9 -0
  249. ksadk/server/static/assets/MathMessageMarkdown-3sQdOysS.js +8 -0
  250. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +303 -0
  251. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +1 -0
  252. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-DrgjcKhl.js} +1 -1
  253. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +1 -0
  254. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-BXoVRbQ6.js} +1 -1
  255. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-BnaB3Ywv.js} +1 -1
  256. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-Dw4weZcp.js} +1 -1
  257. ksadk/server/static/assets/channel-Dt3rQTFI.js +1 -0
  258. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-ChjpLK7j.js} +1 -1
  259. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-B9drTgOW.js} +1 -1
  260. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-CK4ROATy.js} +1 -1
  261. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B9KtTTgw.js} +1 -1
  262. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-1O1mki3m.js} +1 -1
  263. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-B_fH2YuH.js} +1 -1
  264. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D6rFLaVV.js} +1 -1
  265. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +1 -0
  266. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +1 -0
  267. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +1 -0
  268. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-ChNs_Mdk.js} +1 -1
  269. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-D0QXg_MQ.js} +1 -1
  270. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-DBDDKwBQ.js} +1 -1
  271. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-BkwDOxmO.js} +1 -1
  272. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Bv8waYvY.js} +1 -1
  273. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-e1VECDdu.js} +1 -1
  274. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-Ds9adfRl.js} +1 -1
  275. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-4N2NMVYg.js} +1 -1
  276. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-B-jNR6Vn.js} +1 -1
  277. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-B5J2uhNk.js} +1 -1
  278. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-B5oS1zcj.js} +1 -1
  279. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BB3-OwTQ.js} +1 -1
  280. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-BIlKoW0k.js} +1 -1
  281. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-BjOdlDjj.js} +1 -1
  282. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-C1sz5Eo3.js} +1 -1
  283. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-CWV4lDr1.js} +1 -1
  284. ksadk/server/static/assets/{dist-D-xOD048.js → dist-CmZHzlwZ.js} +1 -1
  285. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-D6MpCZtz.js} +1 -1
  286. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-DJN_6Y39.js} +1 -1
  287. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-DQU9EWw-.js} +1 -1
  288. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-DWKByxWV.js} +1 -1
  289. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-Dli7qW-B.js} +1 -1
  290. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-DsHPdk3W.js} +1 -1
  291. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-X4TnqwOK.js} +1 -1
  292. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-_pHPTyZ-.js} +1 -1
  293. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-fhOuWds5.js} +1 -1
  294. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-gLnrGsoU.js} +1 -1
  295. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-j6Dxmf_j.js} +1 -1
  296. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-o2OVmKvt.js} +1 -1
  297. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-BPItFKH6.js} +1 -1
  298. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +1 -0
  299. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-BbYB9w5Y.js} +1 -1
  300. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-TQXa0KXv.js} +1 -1
  301. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +1 -0
  302. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-bKblCnpa.js} +1 -1
  303. ksadk/server/static/assets/index-R52pJJvg.js +177 -0
  304. ksadk/server/static/assets/index-jJr3_955.css +2 -0
  305. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +1 -0
  306. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-BGVT7J_i.js} +1 -1
  307. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-K0OOfVna.js} +1 -1
  308. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-8LeOJaZp.js} +1 -1
  309. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-C6uCgZrW.js} +1 -1
  310. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-CYWseY5E.js} +1 -1
  311. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-hdBcYzM8.js} +1 -1
  312. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-BWrDjSZM.js} +2 -2
  313. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-Bjy_2v4_.js} +1 -1
  314. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +1 -0
  315. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +1 -0
  316. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-GmmqNiVH.js} +1 -1
  317. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-BmkRbwCg.js} +1 -1
  318. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +1 -0
  319. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DSWFqL7c.js} +1 -1
  320. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-l6iXmU65.js} +1 -1
  321. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-Cq6UBeQB.js} +1 -1
  322. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-qzTQpg3Y.js} +1 -1
  323. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +1 -0
  324. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-DEz24vV0.js} +1 -1
  325. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +1 -0
  326. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +1 -0
  327. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-CMCixki2.js} +1 -1
  328. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +1 -0
  329. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-CVzB8F9K.js} +1 -1
  330. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-BFBqBLc1.js} +1 -1
  331. ksadk/server/static/index.html +2 -2
  332. ksadk/server/terminal_sessions.py +111 -58
  333. ksadk/sessions/__init__.py +69 -19
  334. ksadk/sessions/base.py +31 -11
  335. ksadk/sessions/continuity.py +2 -3
  336. ksadk/sessions/in_memory.py +46 -2
  337. ksadk/sessions/local_service.py +148 -17
  338. ksadk/sessions/postgres_service.py +143 -25
  339. ksadk/sessions/resilient.py +36 -8
  340. ksadk/skills/models.py +17 -5
  341. ksadk/skills/package_store.py +5 -2
  342. ksadk/skills/runtime/agent.py +20 -5
  343. ksadk/skills/runtime/artifacts.py +1 -5
  344. ksadk/skills/runtime/backends/e2b.py +10 -4
  345. ksadk/skills/runtime/backends/local.py +15 -3
  346. ksadk/skills/runtime/base.py +1 -2
  347. ksadk/skills/runtime/executor.py +10 -3
  348. ksadk/skills/runtime/factory.py +4 -2
  349. ksadk/skills/runtime/loader.py +1 -2
  350. ksadk/skills/runtime/registry.py +15 -10
  351. ksadk/skills/runtime/request.py +3 -1
  352. ksadk/skills/service_client.py +47 -16
  353. ksadk/skills/tool_defs.py +15 -10
  354. ksadk/studio/__init__.py +5 -0
  355. ksadk/studio/agent_avatar_assets.py +120 -0
  356. ksadk/studio/agent_lifecycle.py +51 -0
  357. ksadk/studio/api.py +1046 -0
  358. ksadk/studio/api_catalog_routes.py +324 -0
  359. ksadk/studio/api_contracts.py +177 -0
  360. ksadk/studio/api_helpers.py +132 -0
  361. ksadk/studio/authoring.py +524 -0
  362. ksadk/studio/authoring_coordinator.py +285 -0
  363. ksadk/studio/builder.py +320 -0
  364. ksadk/studio/capabilities.py +351 -0
  365. ksadk/studio/cloud.py +278 -0
  366. ksadk/studio/codex_agent_service.py +583 -0
  367. ksadk/studio/codex_builder.py +324 -0
  368. ksadk/studio/codex_manifest.py +278 -0
  369. ksadk/studio/codex_run.py +352 -0
  370. ksadk/studio/compiler.py +189 -0
  371. ksadk/studio/contracts.py +644 -0
  372. ksadk/studio/errors.py +47 -0
  373. ksadk/studio/evaluation.py +192 -0
  374. ksadk/studio/event_store.py +284 -0
  375. ksadk/studio/framework_run.py +114 -0
  376. ksadk/studio/identifiers.py +33 -0
  377. ksadk/studio/mcp_runtime.py +229 -0
  378. ksadk/studio/model_client.py +543 -0
  379. ksadk/studio/model_profile_service.py +214 -0
  380. ksadk/studio/operations.py +243 -0
  381. ksadk/studio/otel_trace.py +754 -0
  382. ksadk/studio/pagination.py +182 -0
  383. ksadk/studio/python_tool_inspection.py +183 -0
  384. ksadk/studio/repository.py +306 -0
  385. ksadk/studio/resource_catalog.py +1217 -0
  386. ksadk/studio/run_service.py +707 -0
  387. ksadk/studio/runtime_catalog.py +74 -0
  388. ksadk/studio/runtime_source.py +339 -0
  389. ksadk/studio/service.py +1141 -0
  390. ksadk/studio/shared_web.py +994 -0
  391. ksadk/studio/skill_discovery.py +628 -0
  392. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
  393. ksadk/studio/static/assets/index-CaEN0eTR.css +1 -0
  394. ksadk/studio/static/assets/index-DI2eY_NH.js +252 -0
  395. ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
  396. ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  397. ksadk/studio/static/index.html +15 -0
  398. ksadk/studio/templates.py +630 -0
  399. ksadk/studio/validator.py +246 -0
  400. ksadk/studio/workspace.py +137 -0
  401. ksadk/terminal_exec_policy.py +3 -4
  402. ksadk/tools/gateway.py +101 -15
  403. ksadk/tools/result_budget.py +12 -4
  404. ksadk/toolsets/__init__.py +161 -37
  405. ksadk/toolsets/a2a.py +426 -0
  406. ksadk/toolsets/platform.py +21 -5
  407. ksadk/toolsets/sandbox.py +82 -16
  408. ksadk/toolsets/skills.py +200 -39
  409. ksadk/toolsets/web.py +123 -26
  410. ksadk/toolsets/workspace.py +212 -101
  411. ksadk/tracing/__init__.py +19 -21
  412. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  413. ksadk/tracing/setup.py +135 -506
  414. ksadk/tracing/span_utils.py +0 -41
  415. ksadk/tui/loop.py +78 -30
  416. ksadk/tui/stream_render.py +1 -0
  417. ksadk/ui_config.py +5 -3
  418. ksadk/version.py +1 -1
  419. ksadk_runtime_common/memory_backend/registry.py +56 -56
  420. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  421. ksadk_runtime_common/workspace_files/router.py +8 -2
  422. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  423. ksadk/a2a/card_builder.py +0 -64
  424. ksadk/a2a/client.py +0 -309
  425. ksadk/runners/utils/langfuse.py +0 -230
  426. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  427. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  428. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  429. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  430. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  431. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  432. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  433. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  434. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  435. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  436. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  437. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  438. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  439. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  440. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  441. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  442. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  443. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  444. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  445. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  446. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  447. ksadk/tracing/exporters/langfuse_exporter.py +0 -445
  448. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/entry_points.txt +0 -0
  449. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/licenses/LICENSE +0 -0
  450. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/top_level.txt +0 -0
ksadk/cli/cmd_openclaw.py CHANGED
@@ -10,11 +10,11 @@ agentengine openclaw - OpenClaw 资源管理
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import asyncio
13
14
  import copy
14
15
  import io
15
- import os
16
- import asyncio
17
16
  import json
17
+ import os
18
18
  import re
19
19
  import secrets
20
20
  import shutil
@@ -26,23 +26,28 @@ import time
26
26
  import webbrowser
27
27
  from datetime import datetime, timedelta, timezone
28
28
  from pathlib import Path
29
- from typing import Optional, Dict, Any
29
+ from typing import Any, Dict, Optional, cast
30
30
 
31
31
  import click
32
32
  from click.core import ParameterSource
33
+ from rich.console import Console
33
34
  from rich.measure import Measurement
34
35
  from rich.table import Table as RichTable
35
36
 
36
37
  from ksadk.api.client import DryRunExit
38
+ from ksadk.builders.container_builder import (
39
+ registry_kind_label,
40
+ resolve_registry_credentials,
41
+ )
37
42
  from ksadk.cli.agent_ref import resolve_openclaw_ref
38
- from ksadk.cli.dry_run import dry_run_option, run_async_with_dry_run, effective_dry_run
43
+ from ksadk.cli.dry_run import dry_run_option, effective_dry_run, run_async_with_dry_run
44
+ from ksadk.cli.env_options import env_options, parse_env_pairs
39
45
  from ksadk.cli.error_utils import abort_with_cli_error, remote_error, resolution_error
46
+ from ksadk.cli.model_catalog import fetch_provider_model_catalog, find_model_in_catalog
40
47
  from ksadk.cli.network_options import build_network_payload, network_cli_kwargs, network_options
41
- from ksadk.cli.storage import build_storage_config
42
- from ksadk.cli.resource_common import ResourceActionDescriptor
43
48
  from ksadk.cli.resource_common import (
44
49
  CONTEXT_SETTINGS,
45
- ResourceActionSet,
50
+ ResourceActionDescriptor,
46
51
  ResourceDescriptor,
47
52
  ResourceListSchema,
48
53
  ResourceStatusSchema,
@@ -51,18 +56,17 @@ from ksadk.cli.resource_common import (
51
56
  confirm_destructive,
52
57
  confirm_options,
53
58
  pagination_options,
54
- print_next_action_hint,
59
+ region_option,
55
60
  render_descriptor_list,
56
61
  render_descriptor_status,
57
- region_option,
58
62
  )
63
+ from ksadk.cli.storage import build_storage_config
59
64
  from ksadk.cli.ui import (
60
65
  emit_json,
61
66
  get_console,
62
67
  is_json_output,
63
68
  is_stdout_tty,
64
69
  json_dumps,
65
- output_option as cli_output_option,
66
70
  print_info,
67
71
  print_kv,
68
72
  print_rule,
@@ -71,17 +75,20 @@ from ksadk.cli.ui import (
71
75
  print_warn,
72
76
  status_rich_style,
73
77
  )
74
- from ksadk.deployment.agent_access import get_latest_agent_access
75
- from ksadk.cli.model_catalog import fetch_provider_model_catalog, find_model_in_catalog
78
+ from ksadk.cli.ui import (
79
+ output_option as cli_output_option,
80
+ )
81
+ from ksadk.configs.env_registry import is_sensitive_env_var
76
82
  from ksadk.conversations.model_context import normalize_model_metadata
83
+ from ksadk.deployment.agent_access import get_latest_agent_access
77
84
  from ksadk.model_policy import build_runtime_model_policy_env
78
- from ksadk.builders.container_builder import (
79
- registry_kind_label,
80
- resolve_registry_credentials,
85
+ from ksadk.openclaw_gateway import (
86
+ OpenClawGatewayClient,
87
+ OpenClawGatewayError,
88
+ OpenClawGatewayRequestError,
81
89
  )
82
- from ksadk.openclaw_gateway import OpenClawGatewayClient, OpenClawGatewayError, OpenClawGatewayRequestError
83
- from ksadk.terminal_exec_policy import OPENCLAW_TERMINAL_EXEC_POLICY
84
90
  from ksadk.terminal_client import run_terminal_session
91
+ from ksadk.terminal_exec_policy import OPENCLAW_TERMINAL_EXEC_POLICY
85
92
 
86
93
  console = get_console()
87
94
  # 默认 OpenClaw 镜像。
@@ -102,7 +109,6 @@ DEFAULT_TRUSTED_PROXY_CIDRS = [
102
109
  _GLOBAL_ENV_CACHE: Optional[Dict[str, str]] = None
103
110
  OPENCLAW_SECURITY_PROFILES = ("relaxed", "strict", "strictest")
104
111
  OPENCLAW_CHANNELS = ("weixin", "feishu", "wps-xiezuo")
105
- OPENCLAW_ENV_KEY_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
106
112
  WEIXIN_PLUGIN_ID = "openclaw-weixin"
107
113
  FEISHU_PLUGIN_ID = "openclaw-lark"
108
114
  FEISHU_CHANNEL_KEY = "feishu"
@@ -136,7 +142,8 @@ OPENCLAW_CHANNEL_CONNECT_HELP = """连接指定 channel。
136
142
  飞书:启动官方 onboarding 流程。
137
143
  agentengine openclaw channel connect <id> --channel feishu
138
144
  WPS 协作:写入开放平台 appId/appSecret 并启动长连接。
139
- agentengine openclaw channel connect <id> --channel wps-xiezuo --app-id <appId> --app-secret <appSecret>
145
+ agentengine openclaw channel connect <id> --channel wps-xiezuo \
146
+ --app-id <appId> --app-secret <appSecret>
140
147
 
141
148
  \b
142
149
  WPS 协作说明:
@@ -347,6 +354,7 @@ def _get_global_env() -> Dict[str, str]:
347
354
 
348
355
  try:
349
356
  from ksadk.configs.global_config import get_env_from_global_config
357
+
350
358
  _GLOBAL_ENV_CACHE = {
351
359
  str(k): str(v).strip()
352
360
  for k, v in get_env_from_global_config().items()
@@ -395,6 +403,7 @@ def _resolve_model_base_url(cli_value: Optional[str]) -> Optional[str]:
395
403
 
396
404
  try:
397
405
  from ksadk.configs.settings import settings
406
+
398
407
  api_base = settings.model.api_base
399
408
  if api_base and str(api_base).strip():
400
409
  return str(api_base).strip()
@@ -434,8 +443,9 @@ def _summarize_openclaw_region(agents: list[Dict[str, Any]], fallback_region: Op
434
443
 
435
444
  def _print_openclaw_list_summary(table: RichTable, summary_text: str) -> None:
436
445
  """将摘要贴在表格下方;宽度不足时退化成普通单行。"""
437
- table_width = Measurement.get(console, console.options, table).maximum
438
- summary_width = Measurement.get(console, console.options, summary_text).maximum
446
+ rich_console = cast(Console, console)
447
+ table_width = Measurement.get(rich_console, rich_console.options, table).maximum
448
+ summary_width = Measurement.get(rich_console, rich_console.options, summary_text).maximum
439
449
  if table_width >= summary_width:
440
450
  summary_grid = RichTable.grid(expand=False)
441
451
  summary_grid.add_column(justify="right", width=table_width)
@@ -456,7 +466,12 @@ def _normalize_ui_locale(raw: Optional[str]) -> str:
456
466
 
457
467
  if low in {"c", "c-utf-8", "c.utf-8", "posix"}:
458
468
  return "zh-CN"
459
- if low.startswith("zh-tw") or low.startswith("zh-hk") or low.startswith("zh-mo") or low.startswith("zh-hant"):
469
+ if (
470
+ low.startswith("zh-tw")
471
+ or low.startswith("zh-hk")
472
+ or low.startswith("zh-mo")
473
+ or low.startswith("zh-hant")
474
+ ):
460
475
  return "zh-TW"
461
476
  if low.startswith("zh"):
462
477
  return "zh-CN"
@@ -531,7 +546,9 @@ def _strip_provider_prefix(provider_id: str, model_id: str) -> str:
531
546
 
532
547
 
533
548
  def _default_openclaw_model_inputs(provider_id: str, model_id: str) -> list[str]:
534
- if str(provider_id or "").strip().lower() == "ksyun" and str(model_id or "").strip().lower() in {"glm-5.1", "glm-5.2"}:
549
+ if str(provider_id or "").strip().lower() == "ksyun" and str(
550
+ model_id or ""
551
+ ).strip().lower() in {"glm-5.1", "glm-5.2"}:
535
552
  return ["text"]
536
553
  return ["text", "image"]
537
554
 
@@ -597,9 +614,11 @@ def _openclaw_catalog_item_from_metadata(
597
614
  "api": str(raw_model.get("api") or provider_api or "openai-completions"),
598
615
  "reasoning": bool(raw_model.get("reasoning", True)),
599
616
  "input": inputs or _default_openclaw_model_inputs(provider_id, model_id),
600
- "cost": raw_model.get("cost")
601
- if isinstance(raw_model.get("cost"), dict)
602
- else {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
617
+ "cost": (
618
+ raw_model.get("cost")
619
+ if isinstance(raw_model.get("cost"), dict)
620
+ else {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}
621
+ ),
603
622
  "contextWindow": int(metadata.get("context_window_tokens") or 200_000),
604
623
  "maxTokens": int(metadata.get("max_output_tokens") or 20_000),
605
624
  }
@@ -613,7 +632,9 @@ def _apply_openclaw_provider_model_catalog(
613
632
  return False
614
633
 
615
634
  provider_id = str(env.get("OPENCLAW_MODEL_PROVIDER_ID") or "ksyun").strip() or "ksyun"
616
- provider_api = str(env.get("OPENCLAW_MODEL_API") or "openai-completions").strip() or "openai-completions"
635
+ provider_api = (
636
+ str(env.get("OPENCLAW_MODEL_API") or "openai-completions").strip() or "openai-completions"
637
+ )
617
638
  raw_catalog = str(env.get("OPENCLAW_MODEL_CATALOG_JSON") or "").strip()
618
639
  catalog: list[Dict[str, Any]] = []
619
640
  if raw_catalog:
@@ -632,16 +653,17 @@ def _apply_openclaw_provider_model_catalog(
632
653
 
633
654
  changed = False
634
655
  for raw_model in raw_models:
635
- if not isinstance(raw_model, dict):
636
- raw_model = {"id": str(raw_model or "").strip()}
637
- item = _openclaw_catalog_item_from_metadata(
638
- raw_model,
656
+ raw_model_dict: Dict[str, Any] = (
657
+ dict(raw_model) if isinstance(raw_model, dict) else {"id": str(raw_model or "").strip()}
658
+ )
659
+ catalog_item = _openclaw_catalog_item_from_metadata(
660
+ raw_model_dict,
639
661
  provider_id=provider_id,
640
662
  provider_api=provider_api,
641
663
  )
642
- if not item:
664
+ if not catalog_item:
643
665
  continue
644
- model_key = str(item["id"])
666
+ model_key = str(catalog_item["id"])
645
667
  existing_index = next(
646
668
  (
647
669
  index
@@ -651,13 +673,13 @@ def _apply_openclaw_provider_model_catalog(
651
673
  None,
652
674
  )
653
675
  if existing_index is not None:
654
- merged = {**catalog[existing_index], **item}
676
+ merged = {**catalog[existing_index], **catalog_item}
655
677
  if merged != catalog[existing_index]:
656
678
  catalog[existing_index] = merged
657
679
  changed = True
658
680
  continue
659
681
  seen.add(model_key)
660
- catalog.append(item)
682
+ catalog.append(catalog_item)
661
683
  changed = True
662
684
 
663
685
  if not catalog or not changed:
@@ -679,20 +701,12 @@ def _apply_openclaw_provider_model_metadata(
679
701
 
680
702
  def _openclaw_requested_model_ids(env: Dict[str, str]) -> list[str]:
681
703
  raw_allowlist = str(
682
- env.get("OPENCLAW_MODEL_ALLOWLIST")
683
- or env.get("AGENTENGINE_MODEL_ALLOWLIST")
684
- or ""
704
+ env.get("OPENCLAW_MODEL_ALLOWLIST") or env.get("AGENTENGINE_MODEL_ALLOWLIST") or ""
685
705
  ).strip()
686
706
  if raw_allowlist:
687
- return [
688
- item.strip()
689
- for item in raw_allowlist.replace(";", ",").split(",")
690
- if item.strip()
691
- ]
707
+ return [item.strip() for item in raw_allowlist.replace(";", ",").split(",") if item.strip()]
692
708
  primary_model = str(
693
- env.get("OPENCLAW_DEFAULT_MODEL")
694
- or env.get("OPENAI_MODEL_NAME")
695
- or ""
709
+ env.get("OPENCLAW_DEFAULT_MODEL") or env.get("OPENAI_MODEL_NAME") or ""
696
710
  ).strip()
697
711
  return [primary_model] if primary_model else []
698
712
 
@@ -705,9 +719,7 @@ def _filter_openclaw_provider_catalog(
705
719
  if not requested_models:
706
720
  return []
707
721
  raw_models = [
708
- item.get("_provider_raw_model") or item
709
- if isinstance(item, dict)
710
- else item
722
+ item.get("_provider_raw_model") or item if isinstance(item, dict) else item
711
723
  for item in provider_catalog
712
724
  ]
713
725
  selected: list[Any] = []
@@ -716,7 +728,9 @@ def _filter_openclaw_provider_catalog(
716
728
  match = find_model_in_catalog(raw_models, model_id)
717
729
  if match is None:
718
730
  continue
719
- identities = sorted(str(value).strip().lower() for value in (model_id, str(match)) if str(value).strip())
731
+ identities = sorted(
732
+ str(value).strip().lower() for value in (model_id, str(match)) if str(value).strip()
733
+ )
720
734
  dedupe_key = identities[0] if identities else str(model_id).lower()
721
735
  if dedupe_key in seen:
722
736
  continue
@@ -747,14 +761,12 @@ def _build_openclaw_env_vars(
747
761
  openclaw_explicit_model = default_model or _resolve_env("OPENCLAW_DEFAULT_MODEL")
748
762
  generic_model_preference = _resolve_env("OPENAI_MODEL_NAME", "MODEL_NAME", "LLM_MODEL")
749
763
  model_preference = openclaw_explicit_model or generic_model_preference
750
- explicit_base_url = (
751
- model_base_url
752
- or _resolve_env("OPENCLAW_MODEL_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE")
764
+ explicit_base_url = model_base_url or _resolve_env(
765
+ "OPENCLAW_MODEL_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"
753
766
  )
754
767
  base_url = _resolve_model_base_url(explicit_base_url)
755
- api_key = (
756
- model_api_key
757
- or _resolve_env("OPENCLAW_MODEL_API_KEY", "OPENAI_API_KEY", "LLM_API_KEY", "MODEL_API_KEY")
768
+ api_key = model_api_key or _resolve_env(
769
+ "OPENCLAW_MODEL_API_KEY", "OPENAI_API_KEY", "LLM_API_KEY", "MODEL_API_KEY"
758
770
  )
759
771
  model = model_preference or "glm-5.2"
760
772
  explicit_provider_id = model_provider_id or _resolve_env("OPENCLAW_MODEL_PROVIDER_ID")
@@ -762,34 +774,33 @@ def _build_openclaw_env_vars(
762
774
  if not inferred_provider_id and model and "/" in model:
763
775
  inferred_provider_id = model.split("/", 1)[0].strip()
764
776
  provider_id = inferred_provider_id or default_provider_id
765
- resolved_gateway_port = (
766
- gateway_port
767
- or _resolve_env("OPENCLAW_GATEWAY_PORT", "PORT")
768
- or "8080"
769
- )
770
- resolved_public_port = (
771
- public_port
772
- or _resolve_env("OPENCLAW_PUBLIC_PORT")
773
- or "80"
774
- )
777
+ resolved_gateway_port = gateway_port or _resolve_env("OPENCLAW_GATEWAY_PORT", "PORT") or "8080"
778
+ resolved_public_port = public_port or _resolve_env("OPENCLAW_PUBLIC_PORT") or "80"
775
779
  explicit_model_api = _resolve_env("OPENCLAW_MODEL_API")
776
780
  model_api = explicit_model_api or default_model_api
777
781
  trusted_proxy_user_header = (
778
- _resolve_env(
779
- "OPENCLAW_TRUSTED_PROXY_USER_HEADER",
780
- "OPENCLAW_GATEWAY_TRUSTED_PROXY_USER_HEADER",
782
+ (
783
+ _resolve_env(
784
+ "OPENCLAW_TRUSTED_PROXY_USER_HEADER",
785
+ "OPENCLAW_GATEWAY_TRUSTED_PROXY_USER_HEADER",
786
+ )
787
+ or DEFAULT_TRUSTED_PROXY_USER_HEADER
781
788
  )
782
- or DEFAULT_TRUSTED_PROXY_USER_HEADER
783
- ).strip().lower()
789
+ .strip()
790
+ .lower()
791
+ )
784
792
  internal_trusted_proxy_user = (
785
- _resolve_env("OPENCLAW_INTERNAL_TRUSTED_PROXY_USER")
786
- or "openclaw-backend"
793
+ _resolve_env("OPENCLAW_INTERNAL_TRUSTED_PROXY_USER") or "openclaw-backend"
787
794
  )
788
795
  internal_trusted_proxy_user_header = (
789
- _resolve_env("OPENCLAW_INTERNAL_TRUSTED_PROXY_USER_HEADER")
790
- or trusted_proxy_user_header
791
- or DEFAULT_TRUSTED_PROXY_USER_HEADER
792
- ).strip().lower()
796
+ (
797
+ _resolve_env("OPENCLAW_INTERNAL_TRUSTED_PROXY_USER_HEADER")
798
+ or trusted_proxy_user_header
799
+ or DEFAULT_TRUSTED_PROXY_USER_HEADER
800
+ )
801
+ .strip()
802
+ .lower()
803
+ )
793
804
  trusted_proxies = _normalize_csv_list(
794
805
  _resolve_env("OPENCLAW_TRUSTED_PROXIES") or "",
795
806
  default_items=DEFAULT_TRUSTED_PROXY_CIDRS,
@@ -797,7 +808,9 @@ def _build_openclaw_env_vars(
797
808
  browser_enabled = _resolve_env("OPENCLAW_BROWSER_ENABLED")
798
809
  browser_no_sandbox = _resolve_env("OPENCLAW_BROWSER_NO_SANDBOX") or "true"
799
810
  browser_headless = _resolve_env("OPENCLAW_BROWSER_HEADLESS") or "true"
800
- browser_executable = _resolve_env("OPENCLAW_BROWSER_EXECUTABLE_PATH", "OPENCLAW_BROWSER_EXECUTABLE")
811
+ browser_executable = _resolve_env(
812
+ "OPENCLAW_BROWSER_EXECUTABLE_PATH", "OPENCLAW_BROWSER_EXECUTABLE"
813
+ )
801
814
  ui_locale = _normalize_ui_locale(_resolve_env("OPENCLAW_UI_LOCALE", "LANG", "LC_ALL"))
802
815
  exec_strict_mode_raw = (
803
816
  exec_profile_overrides.get("OPENCLAW_EXEC_STRICT_MODE")
@@ -806,13 +819,21 @@ def _build_openclaw_env_vars(
806
819
  )
807
820
  exec_strict_mode = _is_truthy(exec_strict_mode_raw)
808
821
 
809
- exec_host = exec_profile_overrides.get("OPENCLAW_EXEC_HOST") or _resolve_env("OPENCLAW_EXEC_HOST") or "gateway"
822
+ exec_host = (
823
+ exec_profile_overrides.get("OPENCLAW_EXEC_HOST")
824
+ or _resolve_env("OPENCLAW_EXEC_HOST")
825
+ or "gateway"
826
+ )
810
827
  exec_security = (
811
828
  exec_profile_overrides.get("OPENCLAW_EXEC_SECURITY")
812
829
  or _resolve_env("OPENCLAW_EXEC_SECURITY")
813
830
  or ("allowlist" if exec_strict_mode else "full")
814
831
  )
815
- exec_ask = exec_profile_overrides.get("OPENCLAW_EXEC_ASK") or _resolve_env("OPENCLAW_EXEC_ASK") or "off"
832
+ exec_ask = (
833
+ exec_profile_overrides.get("OPENCLAW_EXEC_ASK")
834
+ or _resolve_env("OPENCLAW_EXEC_ASK")
835
+ or "off"
836
+ )
816
837
  exec_ask_fallback = (
817
838
  exec_profile_overrides.get("OPENCLAW_EXEC_ASK_FALLBACK")
818
839
  or _resolve_env("OPENCLAW_EXEC_ASK_FALLBACK")
@@ -831,9 +852,7 @@ def _build_openclaw_env_vars(
831
852
  exec_default_allowlist_enabled = (
832
853
  exec_profile_overrides.get("OPENCLAW_EXEC_DEFAULT_ALLOWLIST_ENABLED")
833
854
  or _resolve_env("OPENCLAW_EXEC_DEFAULT_ALLOWLIST_ENABLED")
834
- or (
835
- "true" if exec_strict_mode else "false"
836
- )
855
+ or ("true" if exec_strict_mode else "false")
837
856
  )
838
857
  exec_allowlist = _resolve_env("OPENCLAW_EXEC_ALLOWLIST")
839
858
  fs_workspace_only = (
@@ -850,10 +869,14 @@ def _build_openclaw_env_vars(
850
869
  env["OPENCLAW_GATEWAY_BIND"] = "lan"
851
870
  if gateway_auth_mode:
852
871
  env["OPENCLAW_GATEWAY_AUTH_MODE"] = gateway_auth_mode
853
- env["OPENCLAW_TRUSTED_PROXY_USER_HEADER"] = trusted_proxy_user_header or DEFAULT_TRUSTED_PROXY_USER_HEADER
872
+ env["OPENCLAW_TRUSTED_PROXY_USER_HEADER"] = (
873
+ trusted_proxy_user_header or DEFAULT_TRUSTED_PROXY_USER_HEADER
874
+ )
854
875
  env["OPENCLAW_INTERNAL_TRUSTED_PROXY_USER"] = internal_trusted_proxy_user
855
876
  env["OPENCLAW_INTERNAL_TRUSTED_PROXY_USER_HEADER"] = (
856
- internal_trusted_proxy_user_header or trusted_proxy_user_header or DEFAULT_TRUSTED_PROXY_USER_HEADER
877
+ internal_trusted_proxy_user_header
878
+ or trusted_proxy_user_header
879
+ or DEFAULT_TRUSTED_PROXY_USER_HEADER
857
880
  )
858
881
  env["OPENCLAW_TRUSTED_PROXIES"] = trusted_proxies
859
882
  env["OPENCLAW_GATEWAY_PORT"] = str(resolved_gateway_port)
@@ -899,8 +922,9 @@ def _build_openclaw_env_vars(
899
922
  _, catalog_model_id = normalized_model.split("/", 1)
900
923
  resolved_model = normalized_model
901
924
  else:
902
- catalog_model_id = normalized_model
903
- resolved_model = f"{provider_id}/{normalized_model}" if provider_id else normalized_model
925
+ resolved_model = (
926
+ f"{provider_id}/{normalized_model}" if provider_id else normalized_model
927
+ )
904
928
  if openclaw_explicit_model:
905
929
  env["OPENCLAW_DEFAULT_MODEL"] = resolved_model
906
930
  elif generic_model_preference:
@@ -1005,11 +1029,14 @@ def _normalize_openclaw_gateway_auth_env(env: dict[str, str]) -> dict[str, str]:
1005
1029
  auth_mode = raw_mode or ("token" if raw_token or raw_password else "trusted-proxy")
1006
1030
  if auth_mode == "token":
1007
1031
  if raw_token and raw_password and raw_token != raw_password:
1008
- raise ValueError("OPENCLAW_GATEWAY_TOKEN 与 OPENCLAW_GATEWAY_PASSWORD 同时提供时必须一致")
1032
+ raise ValueError(
1033
+ "OPENCLAW_GATEWAY_TOKEN 与 OPENCLAW_GATEWAY_PASSWORD 同时提供时必须一致"
1034
+ )
1009
1035
  shared_secret = raw_token or raw_password
1010
1036
  if not shared_secret:
1011
1037
  raise ValueError(
1012
- "OPENCLAW_GATEWAY_AUTH_MODE=token 时必须提供 OPENCLAW_GATEWAY_TOKEN 或 OPENCLAW_GATEWAY_PASSWORD"
1038
+ "OPENCLAW_GATEWAY_AUTH_MODE=token 时必须提供 "
1039
+ "OPENCLAW_GATEWAY_TOKEN 或 OPENCLAW_GATEWAY_PASSWORD"
1013
1040
  )
1014
1041
  normalized_env["OPENCLAW_GATEWAY_AUTH_MODE"] = "token"
1015
1042
  normalized_env["OPENCLAW_GATEWAY_TOKEN"] = shared_secret
@@ -1018,7 +1045,8 @@ def _normalize_openclaw_gateway_auth_env(env: dict[str, str]) -> dict[str, str]:
1018
1045
 
1019
1046
  if raw_token or raw_password:
1020
1047
  raise ValueError(
1021
- "仅在 OPENCLAW_GATEWAY_AUTH_MODE=token 时支持 OPENCLAW_GATEWAY_TOKEN 或 OPENCLAW_GATEWAY_PASSWORD"
1048
+ "仅在 OPENCLAW_GATEWAY_AUTH_MODE=token 时支持 "
1049
+ "OPENCLAW_GATEWAY_TOKEN 或 OPENCLAW_GATEWAY_PASSWORD"
1022
1050
  )
1023
1051
 
1024
1052
  normalized_env["OPENCLAW_GATEWAY_AUTH_MODE"] = auth_mode
@@ -1028,22 +1056,13 @@ def _normalize_openclaw_gateway_auth_env(env: dict[str, str]) -> dict[str, str]:
1028
1056
 
1029
1057
 
1030
1058
  def _parse_extra_openclaw_env_pairs(items: tuple[str, ...] | list[str] | None) -> dict[str, str]:
1031
- """解析 deploy --env 传入的自定义环境变量。"""
1032
- parsed: dict[str, str] = {}
1033
- for raw_item in items or ():
1034
- item = str(raw_item or "").strip()
1035
- if not item or "=" not in item:
1036
- raise ValueError(f"自定义环境变量格式错误: {raw_item!r},应为 KEY=VALUE")
1037
- key, value = item.split("=", 1)
1038
- key = key.strip()
1039
- if not OPENCLAW_ENV_KEY_PATTERN.fullmatch(key):
1040
- raise ValueError(f"自定义环境变量名不合法: {key!r},请使用合法的环境变量名")
1041
- if key == "OPENCLAW_GATEWAY_AUTH_MODE":
1042
- normalized = value.strip().lower()
1043
- if normalized not in {"trusted-proxy", "token", "none"}:
1044
- raise ValueError("OPENCLAW_GATEWAY_AUTH_MODE 仅支持 trusted-proxy、token 或 none")
1045
- value = normalized
1046
- parsed[key] = value
1059
+ """解析 deploy --env 传入的自定义环境变量,并对 gateway 鉴权模式做早期归一化。"""
1060
+ parsed = parse_env_pairs(items)
1061
+ if "OPENCLAW_GATEWAY_AUTH_MODE" in parsed:
1062
+ normalized = parsed["OPENCLAW_GATEWAY_AUTH_MODE"].strip().lower()
1063
+ if normalized not in {"trusted-proxy", "token", "none"}:
1064
+ raise ValueError("OPENCLAW_GATEWAY_AUTH_MODE 仅支持 trusted-proxy、token 或 none")
1065
+ parsed["OPENCLAW_GATEWAY_AUTH_MODE"] = normalized
1047
1066
  return parsed
1048
1067
 
1049
1068
 
@@ -1174,7 +1193,7 @@ async def _fetch_bootstrap_config(region: str) -> Optional[Dict[str, Any]]:
1174
1193
 
1175
1194
  try:
1176
1195
  async with AgentEngineClient(region=region) as client:
1177
- return await client.get_client_bootstrap_config(
1196
+ result = await client.get_client_bootstrap_config(
1178
1197
  product="openclaw",
1179
1198
  framework="openclaw",
1180
1199
  region=region,
@@ -1183,6 +1202,7 @@ async def _fetch_bootstrap_config(region: str) -> Optional[Dict[str, Any]]:
1183
1202
  locale=_resolve_env("OPENCLAW_UI_LOCALE", "LANG", "LC_ALL"),
1184
1203
  ignore_dry_run=True,
1185
1204
  )
1205
+ return dict(result) if isinstance(result, dict) else None
1186
1206
  except Exception as e:
1187
1207
  print_warn(f"拉取服务端默认配置失败,回退本地默认镜像: {e}")
1188
1208
  return None
@@ -1253,14 +1273,22 @@ def _flatten_agent_detail(agent: dict) -> dict:
1253
1273
  "agent_id": basic.get("agent_id") or agent.get("agent_id") or "",
1254
1274
  "name": basic.get("name") or agent.get("name") or "",
1255
1275
  "status": (basic.get("status") or agent.get("status") or "UNKNOWN").upper(),
1256
- "framework": basic.get("framework") or deploy.get("framework") or agent.get("framework") or "",
1276
+ "framework": basic.get("framework")
1277
+ or deploy.get("framework")
1278
+ or agent.get("framework")
1279
+ or "",
1257
1280
  "region": basic.get("region") or deploy.get("region") or agent.get("region") or "",
1258
- "endpoint": quick.get("public_endpoint") or quick.get("private_endpoint") or agent.get("endpoint") or "",
1281
+ "endpoint": quick.get("public_endpoint")
1282
+ or quick.get("private_endpoint")
1283
+ or agent.get("endpoint")
1284
+ or "",
1259
1285
  "artifact_path": deploy.get("artifact_path") or agent.get("artifact_path") or "",
1260
1286
  "created_at": basic.get("created_at") or agent.get("created_at") or "",
1261
1287
  "updated_at": basic.get("updated_at") or agent.get("updated_at") or "",
1262
1288
  "api_key": quick.get("api_key") or agent.get("api_key"),
1263
- "langfuse_url": (agent.get("advanced") or {}).get("observability_url") or agent.get("langfuse_trace_url") or "",
1289
+ "langfuse_url": (agent.get("advanced") or {}).get("observability_url")
1290
+ or agent.get("langfuse_trace_url")
1291
+ or "",
1264
1292
  }
1265
1293
 
1266
1294
 
@@ -1277,7 +1305,9 @@ async def _get_openclaw_detail_with_client(
1277
1305
  detail = _flatten_agent_detail(agent)
1278
1306
  framework = str(detail.get("framework") or "").strip().lower()
1279
1307
  if framework and framework != "openclaw":
1280
- raise resolution_error(f"目标 Agent 不是 OpenClaw: {agent_ref}", hints=["agentengine openclaw list"])
1308
+ raise resolution_error(
1309
+ f"目标 Agent 不是 OpenClaw: {agent_ref}", hints=["agentengine openclaw list"]
1310
+ )
1281
1311
  return detail
1282
1312
 
1283
1313
 
@@ -1303,10 +1333,7 @@ def _resolve_region(
1303
1333
  ) -> str:
1304
1334
  """解析 region: 显式参数 > state > 环境变量 > 默认值。"""
1305
1335
  return (
1306
- cli_region
1307
- or (state or {}).get("region")
1308
- or _resolve_env("KSYUN_REGION")
1309
- or "cn-beijing-6"
1336
+ cli_region or (state or {}).get("region") or _resolve_env("KSYUN_REGION") or "cn-beijing-6"
1310
1337
  )
1311
1338
 
1312
1339
 
@@ -1331,7 +1358,9 @@ async def _resolve_openclaw_detail_or_raise(
1331
1358
  detail = await _get_openclaw_detail_with_client(client, resolved.value)
1332
1359
 
1333
1360
  if not detail:
1334
- raise resolution_error(f"未找到 OpenClaw: {resolved.value}", hints=["agentengine openclaw list"])
1361
+ raise resolution_error(
1362
+ f"未找到 OpenClaw: {resolved.value}", hints=["agentengine openclaw list"]
1363
+ )
1335
1364
  return resolved_region, detail
1336
1365
 
1337
1366
 
@@ -1378,7 +1407,9 @@ async def _ensure_openclaw_gateway_available(
1378
1407
  await _wait_for_gateway_ready(region, detail, timeout_seconds=timeout_seconds)
1379
1408
 
1380
1409
 
1381
- def _emit_data_payload(title: str, payload: dict[str, Any], *, subtitle: Optional[str] = None) -> None:
1410
+ def _emit_data_payload(
1411
+ title: str, payload: dict[str, Any], *, subtitle: Optional[str] = None
1412
+ ) -> None:
1382
1413
  if is_json_output():
1383
1414
  emit_json(payload)
1384
1415
  return
@@ -1386,7 +1417,9 @@ def _emit_data_payload(title: str, payload: dict[str, Any], *, subtitle: Optiona
1386
1417
  console.print(json_dumps(payload), markup=False)
1387
1418
 
1388
1419
 
1389
- def _render_openclaw_dry_run(action: str, request: dict[str, Any], hints: tuple[str, ...] = ()) -> None:
1420
+ def _render_openclaw_dry_run(
1421
+ action: str, request: dict[str, Any], hints: tuple[str, ...] = ()
1422
+ ) -> None:
1390
1423
  if is_json_output():
1391
1424
  emit_json(
1392
1425
  build_dry_run_envelope(
@@ -1456,7 +1489,9 @@ def _select_openclaw_gateway_secret(
1456
1489
  or ""
1457
1490
  ).strip()
1458
1491
  if token and password and token != password:
1459
- raise click.ClickException("OPENCLAW_GATEWAY_TOKEN 与 OPENCLAW_GATEWAY_PASSWORD 同时提供时必须一致")
1492
+ raise click.ClickException(
1493
+ "OPENCLAW_GATEWAY_TOKEN 与 OPENCLAW_GATEWAY_PASSWORD 同时提供时必须一致"
1494
+ )
1460
1495
  if token:
1461
1496
  return "token", token
1462
1497
  if password:
@@ -1473,14 +1508,20 @@ def _mask_openclaw_secret(secret: str | None) -> str:
1473
1508
  return f"{text[:4]}****"
1474
1509
 
1475
1510
 
1476
- def _openclaw_auth_mode_from_sources(detail: dict[str, Any], state: dict[str, Any] | None = None) -> str:
1477
- return str(
1478
- detail.get("openclaw_auth_mode")
1479
- or detail.get("gateway_auth_mode")
1480
- or (state or {}).get("openclaw_auth_mode")
1481
- or (state or {}).get("gateway_auth_mode")
1482
- or ""
1483
- ).strip().lower()
1511
+ def _openclaw_auth_mode_from_sources(
1512
+ detail: dict[str, Any], state: dict[str, Any] | None = None
1513
+ ) -> str:
1514
+ return (
1515
+ str(
1516
+ detail.get("openclaw_auth_mode")
1517
+ or detail.get("gateway_auth_mode")
1518
+ or (state or {}).get("openclaw_auth_mode")
1519
+ or (state or {}).get("gateway_auth_mode")
1520
+ or ""
1521
+ )
1522
+ .strip()
1523
+ .lower()
1524
+ )
1484
1525
 
1485
1526
 
1486
1527
  def _openclaw_state_gateway_token(env_vars: dict[str, str]) -> str | None:
@@ -1488,9 +1529,7 @@ def _openclaw_state_gateway_token(env_vars: dict[str, str]) -> str | None:
1488
1529
  if str(env_vars.get("OPENCLAW_GATEWAY_AUTH_MODE") or "").strip().lower() != "token":
1489
1530
  return None
1490
1531
  token = str(
1491
- env_vars.get("OPENCLAW_GATEWAY_TOKEN")
1492
- or env_vars.get("OPENCLAW_GATEWAY_PASSWORD")
1493
- or ""
1532
+ env_vars.get("OPENCLAW_GATEWAY_TOKEN") or env_vars.get("OPENCLAW_GATEWAY_PASSWORD") or ""
1494
1533
  ).strip()
1495
1534
  return token or None
1496
1535
 
@@ -1523,7 +1562,9 @@ async def _run_weixin_remote_cli_login(
1523
1562
  if not endpoint:
1524
1563
  raise OpenClawGatewayError("OpenClaw runtime endpoint 为空,无法通过远端 CLI 执行微信登录")
1525
1564
 
1526
- print_info(f"当前 OpenClaw 未暴露微信 web login RPC,改用远端 OpenClaw CLI 登录流程({reason})")
1565
+ print_info(
1566
+ f"当前 OpenClaw 未暴露微信 web login RPC,改用远端 OpenClaw CLI 登录流程({reason})"
1567
+ )
1527
1568
  exit_code = await run_terminal_session(
1528
1569
  endpoint=endpoint,
1529
1570
  api_key=_openclaw_terminal_api_key(detail),
@@ -1592,9 +1633,8 @@ async def _wait_for_gateway_reload_after_config_apply(
1592
1633
 
1593
1634
  def _is_gateway_reload_disconnect_error(exc: Exception) -> bool:
1594
1635
  message = str(exc).lower()
1595
- return (
1596
- "websocket receive failed" in message
1597
- and ("1011" in message or "bad gateway" in message or "going away" in message)
1636
+ return "websocket receive failed" in message and (
1637
+ "1011" in message or "bad gateway" in message or "going away" in message
1598
1638
  )
1599
1639
 
1600
1640
 
@@ -1684,9 +1724,15 @@ def _is_channel_configured(
1684
1724
  accounts = channel_cfg.get("accounts")
1685
1725
  return isinstance(accounts, dict) and any(str(key).strip() for key in accounts.keys())
1686
1726
  if channel == "feishu":
1687
- return bool(str(channel_cfg.get("appId") or "").strip() and str(channel_cfg.get("appSecret") or "").strip())
1727
+ return bool(
1728
+ str(channel_cfg.get("appId") or "").strip()
1729
+ and str(channel_cfg.get("appSecret") or "").strip()
1730
+ )
1688
1731
  if channel == "wps-xiezuo":
1689
- return bool(str(channel_cfg.get("appId") or "").strip() and str(channel_cfg.get("appSecret") or "").strip())
1732
+ return bool(
1733
+ str(channel_cfg.get("appId") or "").strip()
1734
+ and str(channel_cfg.get("appSecret") or "").strip()
1735
+ )
1690
1736
  return bool(channel_cfg)
1691
1737
 
1692
1738
 
@@ -1791,7 +1837,9 @@ def _ensure_local_node_tools() -> dict[str, str]:
1791
1837
  if not node_path or not npx_path:
1792
1838
  raise resolution_error(
1793
1839
  "本地缺少 `node` 或 `npx`,飞书接入依赖官方 onboarding 工具",
1794
- hints=["先安装 Node.js,然后重试 `agentengine openclaw channel connect --channel feishu`"],
1840
+ hints=[
1841
+ "先安装 Node.js,然后重试 `agentengine openclaw channel connect --channel feishu`"
1842
+ ],
1795
1843
  )
1796
1844
  return {"node": node_path, "npx": npx_path}
1797
1845
 
@@ -1822,14 +1870,11 @@ async def _resolve_npx_cached_package_file(package_spec: str, relative_path: str
1822
1870
  text=True,
1823
1871
  )
1824
1872
  if completed.returncode != 0:
1825
- raise OpenClawGatewayError(
1826
- f"无法准备官方 npm 包缓存: {completed.stderr.strip() or completed.stdout.strip() or package_spec}"
1827
- )
1873
+ npm_error = completed.stderr.strip() or completed.stdout.strip() or package_spec
1874
+ raise OpenClawGatewayError(f"无法准备官方 npm 包缓存: {npm_error}")
1828
1875
 
1829
1876
  npm_cache_root = Path(
1830
- os.getenv("NPM_CONFIG_CACHE")
1831
- or os.getenv("npm_config_cache")
1832
- or str(Path.home() / ".npm")
1877
+ os.getenv("NPM_CONFIG_CACHE") or os.getenv("npm_config_cache") or str(Path.home() / ".npm")
1833
1878
  )
1834
1879
  pattern = f"_npx/**/node_modules/{package_name}/{relative_path}"
1835
1880
  matches = list(npm_cache_root.glob(pattern))
@@ -1847,8 +1892,7 @@ async def _run_feishu_onboarding(existing_app_id: Optional[str]) -> dict[str, An
1847
1892
  "@larksuite/openclaw-lark-tools@latest",
1848
1893
  "dist/utils/install-prompts.js",
1849
1894
  )
1850
- script_body = textwrap.dedent(
1851
- """
1895
+ script_body = textwrap.dedent("""
1852
1896
  const fs = require("fs");
1853
1897
  const { runInstallAuthFlow } = require(__INSTALL_PROMPTS_PATH__);
1854
1898
 
@@ -1860,19 +1904,26 @@ async def _run_feishu_onboarding(existing_app_id: Optional[str]) -> dict[str, An
1860
1904
  {},
1861
1905
  false,
1862
1906
  );
1863
- fs.writeFileSync(process.env.KSADK_FEISHU_RESULT_PATH, JSON.stringify(result), "utf8");
1907
+ fs.writeFileSync(
1908
+ process.env.KSADK_FEISHU_RESULT_PATH,
1909
+ JSON.stringify(result),
1910
+ "utf8",
1911
+ );
1864
1912
  } catch (error) {
1865
1913
  console.error(error);
1866
1914
  process.exit(1);
1867
1915
  }
1868
1916
  })();
1869
- """
1870
- ).replace("__INSTALL_PROMPTS_PATH__", json.dumps(str(install_prompts_path)))
1871
- with tempfile.NamedTemporaryFile("w", suffix=".cjs", delete=False, encoding="utf-8") as script_file:
1917
+ """).replace("__INSTALL_PROMPTS_PATH__", json.dumps(str(install_prompts_path)))
1918
+ with tempfile.NamedTemporaryFile(
1919
+ "w", suffix=".cjs", delete=False, encoding="utf-8"
1920
+ ) as script_file:
1872
1921
  script_file.write(script_body.strip())
1873
1922
  script_path = script_file.name
1874
1923
 
1875
- with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as result_file:
1924
+ with tempfile.NamedTemporaryFile(
1925
+ "w", suffix=".json", delete=False, encoding="utf-8"
1926
+ ) as result_file:
1876
1927
  result_path = result_file.name
1877
1928
 
1878
1929
  env = os.environ.copy()
@@ -1945,7 +1996,9 @@ def _resolve_weixin_account_id(
1945
1996
  raise click.ClickException("检测到多个微信账号,请显式传入 --account-id")
1946
1997
  if create_if_missing:
1947
1998
  return "default"
1948
- raise click.ClickException("尚未检测到微信账号,请先执行 `agentengine openclaw channel connect --channel weixin`")
1999
+ raise click.ClickException(
2000
+ "尚未检测到微信账号,请先执行 `agentengine openclaw channel connect --channel weixin`"
2001
+ )
1949
2002
 
1950
2003
 
1951
2004
  def _mutate_weixin_account_enabled(
@@ -2011,7 +2064,8 @@ def _mutate_feishu_connect_config(config: dict[str, Any], onboarding: dict[str,
2011
2064
  feishu_cfg[key] = value
2012
2065
  changed = True
2013
2066
 
2014
- user_info = onboarding.get("userInfo") if isinstance(onboarding.get("userInfo"), dict) else {}
2067
+ user_info_value = onboarding.get("userInfo")
2068
+ user_info: dict[str, Any] = dict(user_info_value) if isinstance(user_info_value, dict) else {}
2015
2069
  open_id = str(user_info.get("openId") or "").strip()
2016
2070
  if open_id:
2017
2071
  if feishu_cfg.get("dmPolicy") != "allowlist":
@@ -2105,8 +2159,12 @@ def _mutate_wps_xiezuo_connect_config(
2105
2159
  bindings = config.setdefault("bindings", [])
2106
2160
  if isinstance(bindings, list):
2107
2161
  next_bindings = [
2108
- item for item in bindings
2109
- if not (isinstance(item, dict) and item.get("match", {}).get("channel") == WPS_XIEZUO_CHANNEL_KEY)
2162
+ item
2163
+ for item in bindings
2164
+ if not (
2165
+ isinstance(item, dict)
2166
+ and item.get("match", {}).get("channel") == WPS_XIEZUO_CHANNEL_KEY
2167
+ )
2110
2168
  ]
2111
2169
  desired_binding = {
2112
2170
  "type": "route",
@@ -2130,7 +2188,9 @@ def _mutate_wps_xiezuo_enabled(
2130
2188
  ) -> bool:
2131
2189
  normalized_account = str(account_id or WPS_XIEZUO_DEFAULT_ACCOUNT_ID).strip()
2132
2190
  if normalized_account and normalized_account != WPS_XIEZUO_DEFAULT_ACCOUNT_ID:
2133
- raise click.ClickException("WPS 协作插件使用扁平 channel 配置,`--account-id` 仅支持 default")
2191
+ raise click.ClickException(
2192
+ "WPS 协作插件使用扁平 channel 配置,`--account-id` 仅支持 default"
2193
+ )
2134
2194
  changed = _ensure_plugin_enabled(config, WPS_XIEZUO_PLUGIN_ID) if enabled else False
2135
2195
  channels = config.setdefault("channels", {})
2136
2196
  channel_cfg = channels.setdefault(WPS_XIEZUO_CHANNEL_KEY, {})
@@ -2152,7 +2212,9 @@ def _check_wps_xiezuo_local_deps() -> dict[str, Any]:
2152
2212
  }
2153
2213
 
2154
2214
 
2155
- @click.group("openclaw", context_settings=CONTEXT_SETTINGS, help=build_resource_group_help(OPENCLAW_RESOURCE))
2215
+ @click.group(
2216
+ "openclaw", context_settings=CONTEXT_SETTINGS, help=build_resource_group_help(OPENCLAW_RESOURCE)
2217
+ )
2156
2218
  def openclaw():
2157
2219
  pass
2158
2220
 
@@ -2211,8 +2273,15 @@ async def _run_openclaw_repair_action(
2211
2273
  @click.option("--session", "-s", default=None, help="OpenClaw Session key")
2212
2274
  @click.option("--message", "-m", default=None, help="连接后发送的初始消息")
2213
2275
  @click.option("--thinking", default=None, help="Thinking level override")
2214
- @click.option("--history-limit", type=click.IntRange(1, 10000), default=None, help="历史条数 (默认使用 OpenClaw 默认值)")
2215
- @click.option("--timeout-ms", type=click.IntRange(1, 86400000), default=None, help="Agent timeout ms")
2276
+ @click.option(
2277
+ "--history-limit",
2278
+ type=click.IntRange(1, 10000),
2279
+ default=None,
2280
+ help="历史条数 (默认使用 OpenClaw 默认值)",
2281
+ )
2282
+ @click.option(
2283
+ "--timeout-ms", type=click.IntRange(1, 86400000), default=None, help="Agent timeout ms"
2284
+ )
2216
2285
  @click.option("--deliver", is_flag=True, help="Deliver assistant replies")
2217
2286
  @click.option("--insecure", "-k", is_flag=True, help="跳过 SSL 证书验证")
2218
2287
  @dry_run_option()
@@ -2279,10 +2348,14 @@ def tui_openclaw(
2279
2348
  elif not agent_ref and str(state.get("endpoint") or "").strip():
2280
2349
  resolved_endpoint = str(state.get("endpoint") or "").strip()
2281
2350
  else:
2282
- resolved_region, detail = asyncio.run(_resolve_openclaw_detail_or_raise(agent_ref, region=region))
2351
+ resolved_region, detail = asyncio.run(
2352
+ _resolve_openclaw_detail_or_raise(agent_ref, region=region)
2353
+ )
2283
2354
  resolved_endpoint = str(detail.get("endpoint") or "").strip()
2284
2355
  if not resolved_endpoint:
2285
- raise click.ClickException("未解析到 OpenClaw Endpoint,请传入 --endpoint 或指定 OpenClaw ID/名称")
2356
+ raise click.ClickException(
2357
+ "未解析到 OpenClaw Endpoint,请传入 --endpoint 或指定 OpenClaw ID/名称"
2358
+ )
2286
2359
 
2287
2360
  secret_kind, gateway_secret = _select_openclaw_gateway_secret(
2288
2361
  gateway_token=gateway_token,
@@ -2293,18 +2366,26 @@ def tui_openclaw(
2293
2366
  auth_mode = _openclaw_auth_mode_from_sources(detail, state)
2294
2367
  if auth_mode in {"token", "password"} and not gateway_secret:
2295
2368
  raise click.ClickException(
2296
- "当前 OpenClaw Gateway 为 token/password 模式,agentengine openclaw tui 需要 OpenClaw Gateway token/password。\n"
2369
+ "当前 OpenClaw Gateway 为 token/password 模式,"
2370
+ "agentengine openclaw tui 需要 OpenClaw Gateway token/password。\n"
2297
2371
  "请使用: agentengine openclaw tui --gateway-token <token>\n"
2298
2372
  "或设置: OPENCLAW_GATEWAY_TOKEN=<token> agentengine openclaw tui\n"
2299
- "如果部署时传过 OPENCLAW_GATEWAY_TOKEN,请重新运行部署让本地 .agentengine.state 记录该 token。\n"
2373
+ "如果部署时传过 OPENCLAW_GATEWAY_TOKEN,请重新运行部署让本地 "
2374
+ ".agentengine.state 记录该 token。\n"
2300
2375
  "注意:这里不是 AgentEngine API Key(ak-*)。"
2301
2376
  )
2302
2377
 
2303
- terminal_api_key = gateway_secret or api_key or str(detail.get("api_key") or state.get("api_key") or "").strip() or None
2378
+ terminal_api_key = (
2379
+ gateway_secret
2380
+ or api_key
2381
+ or str(detail.get("api_key") or state.get("api_key") or "").strip()
2382
+ or None
2383
+ )
2304
2384
  click.secho("🖥️ OpenClaw Native Remote TUI", fg="blue", bold=True)
2305
2385
  click.echo(f" Endpoint: {resolved_endpoint}")
2306
2386
  if gateway_secret:
2307
- click.echo(f" Runtime Auth: OpenClaw Gateway {secret_kind} {_mask_openclaw_secret(gateway_secret)}")
2387
+ masked_gateway_secret = _mask_openclaw_secret(gateway_secret)
2388
+ click.echo(f" Runtime Auth: OpenClaw Gateway {secret_kind} {masked_gateway_secret}")
2308
2389
  elif terminal_api_key:
2309
2390
  click.echo(f" Runtime Auth: Bearer {_mask_openclaw_secret(terminal_api_key)}")
2310
2391
  else:
@@ -2337,7 +2418,9 @@ def tui_openclaw(
2337
2418
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2338
2419
  @click.option("--no-open", is_flag=True, help="仅打印 URL,不自动打开浏览器")
2339
2420
  @cli_output_option()
2340
- def gateway_open(agent_ref: Optional[str], region: Optional[str], no_open: bool, output_mode: str | None):
2421
+ def gateway_open(
2422
+ agent_ref: Optional[str], region: Optional[str], no_open: bool, output_mode: str | None
2423
+ ):
2341
2424
  """打开 OpenClaw gateway Dashboard。"""
2342
2425
  _ = output_mode
2343
2426
  no_open = no_open or is_json_output()
@@ -2408,19 +2491,29 @@ def gateway_ws_url(agent_ref: Optional[str], region: Optional[str], output_mode:
2408
2491
  "auth_mode": "cookie-session",
2409
2492
  "note": "该 ws-url 依赖短链 cookie session,不承诺长期复用。",
2410
2493
  }
2411
- _emit_data_payload("OpenClaw Gateway 连接信息", payload, subtitle=str(detail.get("name") or "-"))
2494
+ _emit_data_payload(
2495
+ "OpenClaw Gateway 连接信息", payload, subtitle=str(detail.get("name") or "-")
2496
+ )
2412
2497
 
2413
2498
  try:
2414
2499
  asyncio.run(_run())
2415
2500
  except Exception as e:
2416
- _abort_openclaw_error(e, context="获取 gateway ws-url 失败", argv=["openclaw", "gateway", "ws-url"])
2501
+ _abort_openclaw_error(
2502
+ e, context="获取 gateway ws-url 失败", argv=["openclaw", "gateway", "ws-url"]
2503
+ )
2417
2504
 
2418
2505
 
2419
2506
  @openclaw_gateway.command("logs", context_settings=CONTEXT_SETTINGS)
2420
2507
  @click.argument("agent_ref", required=False)
2421
2508
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2422
2509
  @click.option("--instance", default=None, help="实例名;不填则查询全部实例")
2423
- @click.option("--log-type", type=click.Choice(["stdout", "log"], case_sensitive=False), default="stdout", show_default=True, help="日志类型")
2510
+ @click.option(
2511
+ "--log-type",
2512
+ type=click.Choice(["stdout", "log"], case_sensitive=False),
2513
+ default="stdout",
2514
+ show_default=True,
2515
+ help="日志类型",
2516
+ )
2424
2517
  @click.option("--start-time", default=None, help="开始时间,支持 Unix 毫秒或 ISO-8601")
2425
2518
  @click.option("--end-time", default=None, help="结束时间,支持 Unix 毫秒或 ISO-8601")
2426
2519
  @cli_output_option()
@@ -2465,7 +2558,9 @@ def gateway_logs(
2465
2558
  emit_json(payload)
2466
2559
  return
2467
2560
 
2468
- print_title("OpenClaw Gateway 日志", str(detail.get("name") or detail.get("agent_id") or "-"))
2561
+ print_title(
2562
+ "OpenClaw Gateway 日志", str(detail.get("name") or detail.get("agent_id") or "-")
2563
+ )
2469
2564
  print_kv("实例", str(resp.get("instance") or "all"))
2470
2565
  print_kv("日志类型", str(resp.get("log_type") or log_type))
2471
2566
  print_kv("日志条数", str(resp.get("total") or 0))
@@ -2479,13 +2574,20 @@ def gateway_logs(
2479
2574
  try:
2480
2575
  asyncio.run(_run())
2481
2576
  except Exception as e:
2482
- _abort_openclaw_error(e, context="获取 gateway 日志失败", argv=["openclaw", "gateway", "logs"])
2577
+ _abort_openclaw_error(
2578
+ e, context="获取 gateway 日志失败", argv=["openclaw", "gateway", "logs"]
2579
+ )
2483
2580
 
2484
2581
 
2485
2582
  @openclaw_gateway.command("doctor", context_settings=CONTEXT_SETTINGS)
2486
2583
  @click.argument("agent_ref", required=False)
2487
2584
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2488
- @click.option("--fix", "do_fix", is_flag=True, help="不走 gateway 内诊断,改为通过控制面执行 openclaw doctor --fix")
2585
+ @click.option(
2586
+ "--fix",
2587
+ "do_fix",
2588
+ is_flag=True,
2589
+ help="不走 gateway 内诊断,改为通过控制面执行 openclaw doctor --fix",
2590
+ )
2489
2591
  @cli_output_option()
2490
2592
  def gateway_doctor(
2491
2593
  agent_ref: Optional[str],
@@ -2519,7 +2621,7 @@ def gateway_doctor(
2519
2621
  "ws_url": info.ws_url,
2520
2622
  }
2521
2623
  )
2522
- hello = await gateway.connect()
2624
+ await gateway.connect()
2523
2625
  checks.append(
2524
2626
  {
2525
2627
  "name": "cookie_ws_handshake",
@@ -2606,7 +2708,12 @@ def openclaw_channel():
2606
2708
  @openclaw_channel.command("status", context_settings=CONTEXT_SETTINGS)
2607
2709
  @click.argument("agent_ref", required=False)
2608
2710
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2609
- @click.option("--channel", type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False), default=None, help="指定 channel")
2711
+ @click.option(
2712
+ "--channel",
2713
+ type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False),
2714
+ default=None,
2715
+ help="指定 channel",
2716
+ )
2610
2717
  @click.option("--probe", is_flag=True, help="触发远端 probe 刷新 channel 快照")
2611
2718
  @cli_output_option()
2612
2719
  def channel_status(
@@ -2626,7 +2733,9 @@ def channel_status(
2626
2733
  gateway = _build_gateway_client(resolved_region, detail)
2627
2734
  try:
2628
2735
  await gateway.connect()
2629
- snapshot = await gateway.channels_status(probe=probe, timeout_ms=8_000 if probe else None)
2736
+ snapshot = await gateway.channels_status(
2737
+ probe=probe, timeout_ms=8_000 if probe else None
2738
+ )
2630
2739
  finally:
2631
2740
  await gateway.close()
2632
2741
 
@@ -2640,26 +2749,67 @@ def channel_status(
2640
2749
  "snapshot": snapshot,
2641
2750
  "selected": _extract_channel_snapshot(snapshot, normalized_channel),
2642
2751
  }
2643
- _emit_data_payload("OpenClaw Channel 状态", payload, subtitle=str(detail.get("name") or "-"))
2752
+ _emit_data_payload(
2753
+ "OpenClaw Channel 状态", payload, subtitle=str(detail.get("name") or "-")
2754
+ )
2644
2755
 
2645
2756
  try:
2646
2757
  asyncio.run(_run())
2647
2758
  except Exception as e:
2648
- _abort_openclaw_error(e, context="获取 channel 状态失败", argv=["openclaw", "channel", "status"])
2759
+ _abort_openclaw_error(
2760
+ e, context="获取 channel 状态失败", argv=["openclaw", "channel", "status"]
2761
+ )
2649
2762
 
2650
2763
 
2651
- @openclaw_channel.command("connect", context_settings=CONTEXT_SETTINGS, help=OPENCLAW_CHANNEL_CONNECT_HELP)
2764
+ @openclaw_channel.command(
2765
+ "connect", context_settings=CONTEXT_SETTINGS, help=OPENCLAW_CHANNEL_CONNECT_HELP
2766
+ )
2652
2767
  @click.argument("agent_ref", required=False)
2653
2768
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2654
- @click.option("--channel", "channel_name", type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False), required=True, help="目标 channel")
2769
+ @click.option(
2770
+ "--channel",
2771
+ "channel_name",
2772
+ type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False),
2773
+ required=True,
2774
+ help="目标 channel",
2775
+ )
2655
2776
  @click.option("--open-qr", is_flag=True, help="仅微信:在本地浏览器额外打开二维码链接")
2656
2777
  @click.option("--app-id", "wps_xiezuo_app_id", default=None, help="仅 WPS 协作:开放平台应用 ID")
2657
- @click.option("--app-secret", "wps_xiezuo_app_secret", default=None, help="仅 WPS 协作:开放平台应用密钥")
2658
- @click.option("--account-id", "wps_xiezuo_account_id", default="default", help="仅 WPS 协作:账号 ID;当前只支持 default")
2659
- @click.option("--agent-id", "wps_xiezuo_agent_id", default="main", help="仅 WPS 协作:消息路由到的 OpenClaw agentId")
2660
- @click.option("--dm-policy", "wps_xiezuo_dm_policy", type=click.Choice(("disabled", "open", "pairing", "allowlist")), default="pairing", help="仅 WPS 协作:私聊策略,默认 pairing")
2661
- @click.option("--group-policy", "wps_xiezuo_group_policy", type=click.Choice(("open", "allowlist")), default="open", help="仅 WPS 协作:群聊策略,默认 open")
2662
- @click.option("--base-url", "wps_xiezuo_base_url", default="https://openapi.wps.cn", help="仅 WPS 协作:WPS OpenAPI 基础地址")
2778
+ @click.option(
2779
+ "--app-secret", "wps_xiezuo_app_secret", default=None, help="仅 WPS 协作:开放平台应用密钥"
2780
+ )
2781
+ @click.option(
2782
+ "--account-id",
2783
+ "wps_xiezuo_account_id",
2784
+ default="default",
2785
+ help="仅 WPS 协作:账号 ID;当前只支持 default",
2786
+ )
2787
+ @click.option(
2788
+ "--agent-id",
2789
+ "wps_xiezuo_agent_id",
2790
+ default="main",
2791
+ help="仅 WPS 协作:消息路由到的 OpenClaw agentId",
2792
+ )
2793
+ @click.option(
2794
+ "--dm-policy",
2795
+ "wps_xiezuo_dm_policy",
2796
+ type=click.Choice(("disabled", "open", "pairing", "allowlist")),
2797
+ default="pairing",
2798
+ help="仅 WPS 协作:私聊策略,默认 pairing",
2799
+ )
2800
+ @click.option(
2801
+ "--group-policy",
2802
+ "wps_xiezuo_group_policy",
2803
+ type=click.Choice(("open", "allowlist")),
2804
+ default="open",
2805
+ help="仅 WPS 协作:群聊策略,默认 open",
2806
+ )
2807
+ @click.option(
2808
+ "--base-url",
2809
+ "wps_xiezuo_base_url",
2810
+ default="https://openapi.wps.cn",
2811
+ help="仅 WPS 协作:WPS OpenAPI 基础地址",
2812
+ )
2663
2813
  def channel_connect(
2664
2814
  agent_ref: Optional[str],
2665
2815
  region: Optional[str],
@@ -2779,13 +2929,18 @@ def channel_connect(
2779
2929
  if normalized_channel == "wps-xiezuo":
2780
2930
  app_id = str(wps_xiezuo_app_id or "").strip()
2781
2931
  app_secret = str(wps_xiezuo_app_secret or "").strip()
2782
- account_id = str(wps_xiezuo_account_id or WPS_XIEZUO_DEFAULT_ACCOUNT_ID).strip() or WPS_XIEZUO_DEFAULT_ACCOUNT_ID
2932
+ account_id = (
2933
+ str(wps_xiezuo_account_id or WPS_XIEZUO_DEFAULT_ACCOUNT_ID).strip()
2934
+ or WPS_XIEZUO_DEFAULT_ACCOUNT_ID
2935
+ )
2783
2936
  if not app_id:
2784
2937
  raise click.ClickException("连接 WPS 协作 channel 必须提供 --app-id")
2785
2938
  if not app_secret:
2786
2939
  raise click.ClickException("连接 WPS 协作 channel 必须提供 --app-secret")
2787
2940
  if account_id != WPS_XIEZUO_DEFAULT_ACCOUNT_ID:
2788
- raise click.ClickException("WPS 协作插件使用扁平 channel 配置,`--account-id` 仅支持 default")
2941
+ raise click.ClickException(
2942
+ "WPS 协作插件使用扁平 channel 配置,`--account-id` 仅支持 default"
2943
+ )
2789
2944
  apply_gateway = _build_gateway_client(resolved_region, detail)
2790
2945
  changed = False
2791
2946
  try:
@@ -2800,7 +2955,8 @@ def channel_connect(
2800
2955
  agent_id=str(wps_xiezuo_agent_id or "main").strip() or "main",
2801
2956
  dm_policy=wps_xiezuo_dm_policy,
2802
2957
  group_policy=wps_xiezuo_group_policy,
2803
- base_url=str(wps_xiezuo_base_url or "https://openapi.wps.cn").strip() or "https://openapi.wps.cn",
2958
+ base_url=str(wps_xiezuo_base_url or "https://openapi.wps.cn").strip()
2959
+ or "https://openapi.wps.cn",
2804
2960
  )
2805
2961
  if changed:
2806
2962
  await _config_apply_and_wait_for_reload(
@@ -2842,9 +2998,12 @@ def channel_connect(
2842
2998
  await bootstrap_gateway.close()
2843
2999
 
2844
3000
  config, _ = _extract_config_state(cfg_snapshot)
2845
- existing_app_id = str(
2846
- ((config.get("channels") or {}).get(FEISHU_CHANNEL_KEY) or {}).get("appId") or ""
2847
- ).strip() or None
3001
+ existing_app_id = (
3002
+ str(
3003
+ ((config.get("channels") or {}).get(FEISHU_CHANNEL_KEY) or {}).get("appId") or ""
3004
+ ).strip()
3005
+ or None
3006
+ )
2848
3007
  onboarding = await _run_feishu_onboarding(existing_app_id)
2849
3008
  changed_config = copy.deepcopy(config)
2850
3009
  changed = _mutate_feishu_connect_config(changed_config, onboarding)
@@ -2885,7 +3044,9 @@ def channel_connect(
2885
3044
  try:
2886
3045
  asyncio.run(_run())
2887
3046
  except Exception as e:
2888
- _abort_openclaw_error(e, context="channel connect 失败", argv=["openclaw", "channel", "connect"])
3047
+ _abort_openclaw_error(
3048
+ e, context="channel connect 失败", argv=["openclaw", "channel", "connect"]
3049
+ )
2889
3050
 
2890
3051
 
2891
3052
  def _run_channel_toggle_command(
@@ -2954,18 +3115,28 @@ def _run_channel_toggle_command(
2954
3115
  "enabled": enabled,
2955
3116
  "status": _extract_channel_snapshot(snapshot, normalized_channel),
2956
3117
  }
2957
- _emit_data_payload(f"OpenClaw Channel {action.title()}", payload, subtitle=normalized_channel)
3118
+ _emit_data_payload(
3119
+ f"OpenClaw Channel {action.title()}", payload, subtitle=normalized_channel
3120
+ )
2958
3121
 
2959
3122
  try:
2960
3123
  asyncio.run(_run())
2961
3124
  except Exception as e:
2962
- _abort_openclaw_error(e, context=f"channel {action} 失败", argv=["openclaw", "channel", action])
3125
+ _abort_openclaw_error(
3126
+ e, context=f"channel {action} 失败", argv=["openclaw", "channel", action]
3127
+ )
2963
3128
 
2964
3129
 
2965
3130
  @openclaw_channel.command("enable", context_settings=CONTEXT_SETTINGS)
2966
3131
  @click.argument("agent_ref", required=False)
2967
3132
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2968
- @click.option("--channel", "channel_name", type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False), required=True, help="目标 channel")
3133
+ @click.option(
3134
+ "--channel",
3135
+ "channel_name",
3136
+ type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False),
3137
+ required=True,
3138
+ help="目标 channel",
3139
+ )
2969
3140
  @click.option("--account-id", default=None, help="账号 ID;飞书 V1 仅支持 default")
2970
3141
  def channel_enable(
2971
3142
  agent_ref: Optional[str],
@@ -2987,7 +3158,13 @@ def channel_enable(
2987
3158
  @openclaw_channel.command("disable", context_settings=CONTEXT_SETTINGS)
2988
3159
  @click.argument("agent_ref", required=False)
2989
3160
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
2990
- @click.option("--channel", "channel_name", type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False), required=True, help="目标 channel")
3161
+ @click.option(
3162
+ "--channel",
3163
+ "channel_name",
3164
+ type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False),
3165
+ required=True,
3166
+ help="目标 channel",
3167
+ )
2991
3168
  @click.option("--account-id", default=None, help="账号 ID;飞书 V1 仅支持 default")
2992
3169
  def channel_disable(
2993
3170
  agent_ref: Optional[str],
@@ -3009,9 +3186,16 @@ def channel_disable(
3009
3186
  @openclaw_channel.command("doctor", context_settings=CONTEXT_SETTINGS)
3010
3187
  @click.argument("agent_ref", required=False)
3011
3188
  @region_option(default=None, envvar=None, help_text="区域 (默认优先读取 .agentengine.state)")
3012
- @click.option("--channel", type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False), default=None, help="指定 channel")
3189
+ @click.option(
3190
+ "--channel",
3191
+ type=click.Choice(OPENCLAW_CHANNELS, case_sensitive=False),
3192
+ default=None,
3193
+ help="指定 channel",
3194
+ )
3013
3195
  @cli_output_option()
3014
- def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Optional[str], output_mode: str | None):
3196
+ def channel_doctor(
3197
+ agent_ref: Optional[str], region: Optional[str], channel: Optional[str], output_mode: str | None
3198
+ ):
3015
3199
  """检查 channel 接入前置条件。"""
3016
3200
  _ = output_mode
3017
3201
  normalized_channel = str(channel).lower() if channel else None
@@ -3028,20 +3212,28 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3028
3212
  gateway = _build_gateway_client(resolved_region, detail)
3029
3213
  try:
3030
3214
  info = await gateway.build_access_info()
3031
- checks.append({"name": "dashboard_short_link", "ok": True, "dashboard_url": info.access_url})
3215
+ checks.append(
3216
+ {"name": "dashboard_short_link", "ok": True, "dashboard_url": info.access_url}
3217
+ )
3032
3218
  await gateway.connect()
3033
3219
  methods = gateway.methods
3034
3220
  checks.append({"name": "cookie_ws_handshake", "ok": True, "methods": len(methods)})
3035
3221
  snapshot = await gateway.channels_status(probe=False)
3036
3222
  config_snapshot = await gateway.config_get()
3037
- config = config_snapshot.get("config") if isinstance(config_snapshot.get("config"), dict) else {}
3223
+ config = (
3224
+ config_snapshot.get("config")
3225
+ if isinstance(config_snapshot.get("config"), dict)
3226
+ else {}
3227
+ )
3038
3228
  except Exception as exc:
3039
3229
  checks.append({"name": "gateway_connectivity", "ok": False, "error": str(exc)})
3040
3230
  finally:
3041
3231
  await gateway.close()
3042
3232
 
3043
3233
  channels_to_check = [normalized_channel] if normalized_channel else list(OPENCLAW_CHANNELS)
3044
- plugin_entries = ((config.get("plugins") or {}).get("entries") or {}) if isinstance(config, dict) else {}
3234
+ plugin_entries = (
3235
+ ((config.get("plugins") or {}).get("entries") or {}) if isinstance(config, dict) else {}
3236
+ )
3045
3237
  for item in channels_to_check:
3046
3238
  selected_snapshot = _extract_channel_snapshot(snapshot, item)
3047
3239
  configured = _is_channel_configured(item, config=config, snapshot=selected_snapshot)
@@ -3059,7 +3251,9 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3059
3251
  name="weixin_status_snapshot",
3060
3252
  available=selected_snapshot is not None,
3061
3253
  configured=configured,
3062
- connect_required_message="首次连接前微信状态快照可能为空,执行 channel connect 后会自动补齐",
3254
+ connect_required_message=(
3255
+ "首次连接前微信状态快照可能为空," "执行 channel connect 后会自动补齐"
3256
+ ),
3063
3257
  )
3064
3258
  )
3065
3259
  checks.append(
@@ -3067,7 +3261,9 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3067
3261
  name="weixin_qr_rpc",
3068
3262
  available="web.login.start" in methods and "web.login.wait" in methods,
3069
3263
  configured=configured,
3070
- connect_required_message="首次连接会先自动启用 bundled weixin plugin,然后再暴露扫码 RPC",
3264
+ connect_required_message=(
3265
+ "首次连接会先自动启用 bundled weixin plugin," "然后再暴露扫码 RPC"
3266
+ ),
3071
3267
  connect_required_ok=False,
3072
3268
  required_methods=["web.login.start", "web.login.wait"],
3073
3269
  )
@@ -3085,7 +3281,10 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3085
3281
  name="feishu_status_snapshot",
3086
3282
  available=selected_snapshot is not None,
3087
3283
  configured=configured,
3088
- connect_required_message="飞书尚未完成 connect/onboarding,首次接入前不会出现在 channel snapshot 中",
3284
+ connect_required_message=(
3285
+ "飞书尚未完成 connect/onboarding,"
3286
+ "首次接入前不会出现在 channel snapshot 中"
3287
+ ),
3089
3288
  )
3090
3289
  )
3091
3290
  node_path = shutil.which("node")
@@ -3111,7 +3310,9 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3111
3310
  name="wps_xiezuo_status_snapshot",
3112
3311
  available=selected_snapshot is not None,
3113
3312
  configured=configured,
3114
- connect_required_message="WPS 协作尚未完成配置,首次 connect 前不会出现在 channel snapshot 中",
3313
+ connect_required_message=(
3314
+ "WPS 协作尚未完成配置," "首次 connect 前不会出现在 channel snapshot 中"
3315
+ ),
3115
3316
  )
3116
3317
  )
3117
3318
  dep_check = _check_wps_xiezuo_local_deps()
@@ -3134,15 +3335,20 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3134
3335
 
3135
3336
  try:
3136
3337
  payload = asyncio.run(_run())
3137
- _emit_data_payload("OpenClaw Channel Doctor", payload, subtitle=str(payload.get("name") or "-"))
3338
+ _emit_data_payload(
3339
+ "OpenClaw Channel Doctor", payload, subtitle=str(payload.get("name") or "-")
3340
+ )
3138
3341
  except Exception as e:
3139
- _abort_openclaw_error(e, context="channel doctor 执行失败", argv=["openclaw", "channel", "doctor"])
3342
+ _abort_openclaw_error(
3343
+ e, context="channel doctor 执行失败", argv=["openclaw", "channel", "doctor"]
3344
+ )
3140
3345
 
3141
3346
 
3142
3347
  @openclaw.command("deploy", context_settings=CONTEXT_SETTINGS)
3143
3348
  @click.option("--name", "-n", default=None, help="OpenClaw 名称 (默认: openclaw-gateway)")
3144
3349
  @click.option(
3145
- "--region", "-r",
3350
+ "--region",
3351
+ "-r",
3146
3352
  default="cn-beijing-6",
3147
3353
  envvar="KSYUN_REGION",
3148
3354
  help="部署区域 (默认: cn-beijing-6)",
@@ -3154,7 +3360,9 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3154
3360
  help="安全预设: relaxed | strict | strictest (安全测试建议 strictest)",
3155
3361
  )
3156
3362
  @click.option("--strict-mode", "security_profile", flag_value="strict", help="快捷开启严格模式")
3157
- @click.option("--strictest", "security_profile", flag_value="strictest", help="快捷开启最严格安全模式")
3363
+ @click.option(
3364
+ "--strictest", "security_profile", flag_value="strictest", help="快捷开启最严格安全模式"
3365
+ )
3158
3366
  @click.option(
3159
3367
  "--image",
3160
3368
  default=None,
@@ -3172,15 +3380,21 @@ def channel_doctor(agent_ref: Optional[str], region: Optional[str], channel: Opt
3172
3380
  @click.option("--mem0-instance-id", default=None, help="mem0 实例 ID")
3173
3381
  @click.option("--mem0-instance-name", default=None, help="mem0 实例名称(可选)")
3174
3382
  @click.option("--mem0-region", default=None, help="mem0 实例区域(可选)")
3383
+ @env_options
3384
+ @click.option("--storage-size-gi", type=int, default=20, show_default=True, help="PVC 容量(Gi)")
3175
3385
  @click.option(
3176
- "--env",
3177
- "extra_env",
3178
- multiple=True,
3179
- help="额外透传自定义环境变量,格式 KEY=VALUE,可重复传入",
3386
+ "--storage-mount-path", default=None, help="PVC 挂载目录(默认: /home/node/.openclaw)"
3180
3387
  )
3181
- @click.option("--storage-size-gi", type=int, default=20, show_default=True, help="PVC 容量(Gi)")
3182
- @click.option("--storage-mount-path", default=None, help="PVC 挂载目录(默认: /home/node/.openclaw)")
3183
3388
  @click.option("--no-storage", is_flag=True, help="禁用默认 PVC 挂载")
3389
+ @click.option(
3390
+ "--agent-id",
3391
+ "agent_id_opt",
3392
+ default=None,
3393
+ help=(
3394
+ "指定要更新的已有 Agent ID;当前凭证有权限时会自动回填 "
3395
+ ".agentengine.state 并走热更新(用于本地状态丢失后重新关联)"
3396
+ ),
3397
+ )
3184
3398
  @network_options
3185
3399
  @dry_run_option("仅显示请求,不实际部署")
3186
3400
  def deploy(
@@ -3196,9 +3410,11 @@ def deploy(
3196
3410
  mem0_instance_name: Optional[str],
3197
3411
  mem0_region: Optional[str],
3198
3412
  extra_env: tuple[str, ...],
3413
+ env_file: Optional[str],
3199
3414
  storage_size_gi: int,
3200
3415
  storage_mount_path: Optional[str],
3201
3416
  no_storage: bool,
3417
+ agent_id_opt: Optional[str],
3202
3418
  enable_public_access: Optional[bool],
3203
3419
  enable_vpc_access: bool,
3204
3420
  vpc_id: Optional[str],
@@ -3222,7 +3438,8 @@ def deploy(
3222
3438
  # 一键创建最严格实例(适合安全测试)
3223
3439
  agentengine openclaw deploy --security-profile strictest
3224
3440
  # 显式指定模型
3225
- agentengine openclaw deploy --model-base-url https://api.example.com/v1 --model-api-key sk-xxx
3441
+ agentengine openclaw deploy --model-base-url https://api.example.com/v1 \
3442
+ --model-api-key sk-xxx
3226
3443
  # 使用自定义镜像
3227
3444
  agentengine openclaw deploy --image ghcr.io/my-org/openclaw:v2
3228
3445
  # 透传业务自定义环境变量
@@ -3237,6 +3454,7 @@ def deploy(
3237
3454
  _option_was_explicit(ctx, "model_api_key"),
3238
3455
  _option_was_explicit(ctx, "default_model"),
3239
3456
  bool(extra_env),
3457
+ _option_was_explicit(ctx, "env_file"),
3240
3458
  )
3241
3459
  )
3242
3460
  include_storage_on_update = any(
@@ -3267,9 +3485,11 @@ def deploy(
3267
3485
  mem0_instance_name=mem0_instance_name,
3268
3486
  mem0_region=mem0_region,
3269
3487
  extra_env=extra_env,
3488
+ env_file=env_file,
3270
3489
  storage_size_gi=storage_size_gi,
3271
3490
  storage_mount_path=storage_mount_path,
3272
3491
  no_storage=no_storage,
3492
+ agent_id_opt=agent_id_opt,
3273
3493
  include_env_on_update=include_env_on_update,
3274
3494
  include_storage_on_update=include_storage_on_update,
3275
3495
  **network_cli_kwargs(
@@ -3302,9 +3522,11 @@ async def _deploy_openclaw(
3302
3522
  mem0_instance_name: Optional[str],
3303
3523
  mem0_region: Optional[str],
3304
3524
  extra_env: tuple[str, ...] = (),
3525
+ env_file: Optional[str] = None,
3305
3526
  storage_size_gi: int = 20,
3306
3527
  storage_mount_path: Optional[str] = None,
3307
3528
  no_storage: bool = False,
3529
+ agent_id_opt: Optional[str] = None,
3308
3530
  include_env_on_update: bool = False,
3309
3531
  include_storage_on_update: bool = False,
3310
3532
  enable_public_access: Optional[bool] = None,
@@ -3317,25 +3539,25 @@ async def _deploy_openclaw(
3317
3539
  ):
3318
3540
  """异步部署 OpenClaw"""
3319
3541
  from ksadk.api import AgentEngineClient
3320
- from ksadk.deployment.state import load_state, save_state, clear_state
3321
- from dotenv import dotenv_values
3542
+ from ksadk.cli.env_options import (
3543
+ apply_explicit_env_with_shell_priority,
3544
+ inject_env_to_environ,
3545
+ resolve_runtime_env_overrides,
3546
+ )
3547
+ from ksadk.deployment.state import clear_state, load_state, save_state
3322
3548
 
3323
- # 自动加载当前目录 .env(仅补充未导出的变量,不覆盖已导出的 shell 环境)
3549
+ # 自动加载当前目录 .env 或 --env-file 指定文件(优先级: --env > --env-file > shell > .env)
3324
3550
  project_dir = Path(".").resolve()
3325
- env_file = project_dir / ".env"
3326
- if env_file.exists():
3327
- try:
3328
- loaded = 0
3329
- for k, v in dotenv_values(env_file).items():
3330
- if not k or v is None:
3331
- continue
3332
- if os.getenv(k) is None:
3333
- os.environ[k] = str(v)
3334
- loaded += 1
3335
- if loaded:
3336
- print_info(f"已从 .env 注入环境变量: {loaded} 项")
3337
- except Exception as e:
3338
- print_warn(f"读取 .env 失败,将继续使用当前 shell 环境: {e}")
3551
+ cli_env, auto_dotenv, shell_keys, env_source = resolve_runtime_env_overrides(
3552
+ env_file=env_file,
3553
+ extra_env=extra_env,
3554
+ base_dir=project_dir,
3555
+ )
3556
+ loaded = inject_env_to_environ(cli_env, auto_dotenv, shell_keys)
3557
+ if loaded:
3558
+ print_info(f"已从 {env_source or '--env'} 注入环境变量: {loaded} 项")
3559
+ if cli_env or auto_dotenv:
3560
+ include_env_on_update = True
3339
3561
 
3340
3562
  # 读取本地状态 (判断创建 vs 更新)
3341
3563
  state = load_state(project_dir)
@@ -3345,6 +3567,14 @@ async def _deploy_openclaw(
3345
3567
  if state_kind == "openclaw":
3346
3568
  existing_agent_id = state.get("agent_id")
3347
3569
  state_name = str(state.get("name") or "").strip() or None
3570
+ explicit_agent_id = (agent_id_opt or "").strip() or None
3571
+ if explicit_agent_id:
3572
+ if existing_agent_id and existing_agent_id != explicit_agent_id:
3573
+ print_info(
3574
+ f"--agent-id ({explicit_agent_id}) 与本地状态 "
3575
+ f"({existing_agent_id}) 不一致,以 --agent-id 为准"
3576
+ )
3577
+ existing_agent_id = explicit_agent_id
3348
3578
 
3349
3579
  if name:
3350
3580
  openclaw_name = name
@@ -3372,18 +3602,19 @@ async def _deploy_openclaw(
3372
3602
  default_model=default_model,
3373
3603
  security_profile=security_profile,
3374
3604
  )
3375
- custom_env_vars = _parse_extra_openclaw_env_pairs(extra_env)
3376
- if custom_env_vars:
3377
- env_vars.update(custom_env_vars)
3605
+ if cli_env or auto_dotenv:
3606
+ apply_explicit_env_with_shell_priority(env_vars, cli_env, auto_dotenv, shell_keys)
3378
3607
  env_vars = _normalize_openclaw_gateway_auth_env(env_vars)
3379
3608
  if not str(env_vars.get("OPENCLAW_MODEL_CATALOG_JSON") or "").strip():
3380
- catalog_api_base = (
3381
- model_base_url
3382
- or _resolve_env("OPENCLAW_MODEL_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE", "LLM_API_BASE", "MODEL_API_BASE")
3609
+ catalog_api_base = model_base_url or _resolve_env(
3610
+ "OPENCLAW_MODEL_BASE_URL",
3611
+ "OPENAI_BASE_URL",
3612
+ "OPENAI_API_BASE",
3613
+ "LLM_API_BASE",
3614
+ "MODEL_API_BASE",
3383
3615
  )
3384
- catalog_api_key = (
3385
- model_api_key
3386
- or _resolve_env("OPENCLAW_MODEL_API_KEY", "OPENAI_API_KEY", "LLM_API_KEY", "MODEL_API_KEY")
3616
+ catalog_api_key = model_api_key or _resolve_env(
3617
+ "OPENCLAW_MODEL_API_KEY", "OPENAI_API_KEY", "LLM_API_KEY", "MODEL_API_KEY"
3387
3618
  )
3388
3619
  provider_catalog = await fetch_provider_model_catalog(
3389
3620
  api_base=catalog_api_base,
@@ -3391,7 +3622,9 @@ async def _deploy_openclaw(
3391
3622
  )
3392
3623
  selected_catalog = _filter_openclaw_provider_catalog(env_vars, provider_catalog)
3393
3624
  if _apply_openclaw_provider_model_catalog(env_vars, selected_catalog):
3394
- print_info(f"已从模型服务 /v1/models 同步 OpenClaw 模型元数据: {len(selected_catalog)} 个")
3625
+ print_info(
3626
+ f"已从模型服务 /v1/models 同步 OpenClaw 模型元数据: {len(selected_catalog)} 个"
3627
+ )
3395
3628
  memory_config = _build_openclaw_memory_config(
3396
3629
  memory_system=memory_system,
3397
3630
  mem0_instance_id=mem0_instance_id,
@@ -3412,7 +3645,7 @@ async def _deploy_openclaw(
3412
3645
 
3413
3646
  # 构建环境变量列表
3414
3647
  env_list = [
3415
- {"Key": k, "Value": v, "IsSensitive": "KEY" in k or "TOKEN" in k or "SECRET" in k}
3648
+ {"Key": k, "Value": v, "IsSensitive": is_sensitive_env_var(k)}
3416
3649
  for k, v in env_vars.items()
3417
3650
  ]
3418
3651
  # 资源规格(支持通过环境变量覆盖)
@@ -3445,7 +3678,9 @@ async def _deploy_openclaw(
3445
3678
  if storage_config:
3446
3679
  request_data["storage"] = storage_config
3447
3680
  if existing_agent_id and include_storage_on_update and no_storage:
3448
- print_warn("更新已有 OpenClaw 时 `--no-storage` 不会删除服务端既有挂盘配置;默认保留已有配置。")
3681
+ print_warn(
3682
+ "更新已有 OpenClaw 时 `--no-storage` 不会删除服务端既有挂盘配置;默认保留已有配置。"
3683
+ )
3449
3684
  network_payload = build_network_payload(
3450
3685
  enable_public_access=enable_public_access,
3451
3686
  enable_vpc_access=enable_vpc_access,
@@ -3456,7 +3691,7 @@ async def _deploy_openclaw(
3456
3691
  region=region,
3457
3692
  dry_run=dry_run,
3458
3693
  )
3459
- # create 默认开公网(network_payload 未显式 enable_public_access 时补 True);update 分支用原始 network_payload(None=保留现有配置)
3694
+ # Create 默认开公网;update 使用原始 payload,None 表示保留现有配置。
3460
3695
  create_network_payload = dict(network_payload) if network_payload is not None else {}
3461
3696
  if "enable_public_access" not in create_network_payload:
3462
3697
  create_network_payload["enable_public_access"] = True
@@ -3477,12 +3712,15 @@ async def _deploy_openclaw(
3477
3712
  request_data["image_credential"] = image_credential
3478
3713
  elif kcr_password and not kcr_username and registry_kind != "personal_kcr":
3479
3714
  print_warn(
3480
- f"检测到 KCR_PASSWORD 但缺少 KCR_USERNAME,已忽略{registry_kind_label(registry_kind)}镜像凭证;"
3715
+ "检测到 KCR_PASSWORD 但缺少 KCR_USERNAME,已忽略"
3716
+ f"{registry_kind_label(registry_kind)}镜像凭证;"
3481
3717
  "企业版 KCR 和第三方镜像仓库必须配置 KCR_USERNAME + KCR_PASSWORD"
3482
3718
  )
3483
3719
  elif "/agentengine-public/" not in image_ref:
3484
3720
  if registry_kind == "personal_kcr":
3485
- print_warn("未配置个人版 KCR 镜像凭证 (KSYUN_ACCOUNT_ID/KCR_PASSWORD),私有镜像可能无法拉取")
3721
+ print_warn(
3722
+ "未配置个人版 KCR 镜像凭证 (KSYUN_ACCOUNT_ID/KCR_PASSWORD),私有镜像可能无法拉取"
3723
+ )
3486
3724
  else:
3487
3725
  print_warn(
3488
3726
  f"未配置{registry_kind_label(registry_kind)}镜像凭证 "
@@ -3515,6 +3753,37 @@ async def _deploy_openclaw(
3515
3753
  latest_status = None
3516
3754
  updated_existing_agent = False
3517
3755
  async with AgentEngineClient(region=region) as client:
3756
+ if explicit_agent_id:
3757
+ try:
3758
+ detail = await client.get_agent(
3759
+ explicit_agent_id, include_api_key=True
3760
+ )
3761
+ except Exception as e:
3762
+ raise click.ClickException(
3763
+ f"指定的 Agent ID '{explicit_agent_id}' 不存在,或当前凭证无权限访问。\n"
3764
+ f" 详情: {e}\n"
3765
+ " 👉 请确认 agent_id 正确,且当前 AK/SK / 账号有该 Agent 的权限。"
3766
+ ) from e
3767
+ qa = detail.get("quick_access", {}) or {}
3768
+ basic = detail.get("basic", {}) or {}
3769
+ recovered_state = state.copy()
3770
+ recovered_state.update(
3771
+ {
3772
+ "agent_id": explicit_agent_id,
3773
+ "name": basic.get("name") or openclaw_name,
3774
+ "type": "openclaw",
3775
+ "region": region,
3776
+ "endpoint": qa.get("public_endpoint"),
3777
+ }
3778
+ )
3779
+ if qa.get("api_key"):
3780
+ recovered_state["api_key"] = qa["api_key"]
3781
+ recovered_state = {
3782
+ k: v for k, v in recovered_state.items() if v is not None
3783
+ }
3784
+ save_state(project_dir, recovered_state)
3785
+ state = recovered_state
3786
+ print_info(f"已通过 --agent-id 关联已有 Agent: {explicit_agent_id}")
3518
3787
  if existing_agent_id:
3519
3788
  print_info(f"检测到本地状态: {existing_agent_id},执行更新...")
3520
3789
  try:
@@ -3538,12 +3807,13 @@ async def _deploy_openclaw(
3538
3807
  except Exception as update_err:
3539
3808
  err_msg = str(update_err)
3540
3809
  not_found = (
3541
- "code: 404" in err_msg.lower()
3542
- or "agent not found" in err_msg.lower()
3810
+ "code: 404" in err_msg.lower() or "agent not found" in err_msg.lower()
3543
3811
  )
3544
3812
  if not not_found:
3545
3813
  raise
3546
- print_warn(f"本地状态失效 ({existing_agent_id}),将自动回退为新建: {update_err}")
3814
+ print_warn(
3815
+ f"本地状态失效 ({existing_agent_id}),将自动回退为新建: {update_err}"
3816
+ )
3547
3817
  cleared = clear_state(project_dir, key=existing_agent_id)
3548
3818
  if cleared:
3549
3819
  print_info("已清理失效的 .agentengine.state")
@@ -3568,10 +3838,12 @@ async def _deploy_openclaw(
3568
3838
  attempts=12,
3569
3839
  interval_seconds=5,
3570
3840
  include_api_key=True,
3571
- detail_fetcher=lambda agent_ref, include_api_key: _get_openclaw_detail_with_client(
3572
- client,
3573
- agent_ref,
3574
- include_api_key=include_api_key,
3841
+ detail_fetcher=lambda agent_ref, include_api_key: (
3842
+ _get_openclaw_detail_with_client(
3843
+ client,
3844
+ agent_ref,
3845
+ include_api_key=include_api_key,
3846
+ )
3575
3847
  ),
3576
3848
  suppress_transient_not_found_log=True,
3577
3849
  )
@@ -3583,8 +3855,7 @@ async def _deploy_openclaw(
3583
3855
  else:
3584
3856
  print_warn("实例创建中,稍后使用 'agentengine openclaw list' 查看")
3585
3857
  elif agent_id and (
3586
- not str(endpoint or "").strip()
3587
- or not str(api_key or "").strip()
3858
+ not str(endpoint or "").strip() or not str(api_key or "").strip()
3588
3859
  ):
3589
3860
  latest = await get_latest_agent_access(
3590
3861
  client,
@@ -3594,10 +3865,12 @@ async def _deploy_openclaw(
3594
3865
  initial_delay_seconds=2,
3595
3866
  require_complete_access=True,
3596
3867
  include_api_key=True,
3597
- detail_fetcher=lambda agent_ref, include_api_key: _get_openclaw_detail_with_client(
3598
- client,
3599
- agent_ref,
3600
- include_api_key=include_api_key,
3868
+ detail_fetcher=lambda agent_ref, include_api_key: (
3869
+ _get_openclaw_detail_with_client(
3870
+ client,
3871
+ agent_ref,
3872
+ include_api_key=include_api_key,
3873
+ )
3601
3874
  ),
3602
3875
  suppress_transient_not_found_log=True,
3603
3876
  )
@@ -3627,11 +3900,13 @@ async def _deploy_openclaw(
3627
3900
  state_payload["openclaw_gateway_token"] = state_gateway_token
3628
3901
  save_state(project_dir, state_payload)
3629
3902
 
3630
- # 仅在更新已有实例时回读一次状态;新建时底层可能尚未落库,立即按 ID 查询会产生误导性报错。
3903
+ # 更新已有实例才回读;新建后立即查询可能早于底层落库。
3631
3904
  if updated_existing_agent and agent_id:
3632
3905
  try:
3633
3906
  latest = await client.get_agent(agent_id=agent_id, include_api_key=False)
3634
- latest_status = str(((latest.get("basic") or {}).get("status") or "")).upper() or None
3907
+ latest_status = (
3908
+ str(((latest.get("basic") or {}).get("status") or "")).upper() or None
3909
+ )
3635
3910
  except Exception:
3636
3911
  latest_status = None
3637
3912
 
@@ -3672,7 +3947,9 @@ def list_openclaws(region: str, page: int, size: int, dry_run: bool, output_mode
3672
3947
 
3673
3948
  async def _list():
3674
3949
  async with AgentEngineClient(region=region, dry_run=dry_run) as client:
3675
- resp = await client.list_agents(region=region, framework="openclaw", page=page, page_size=size)
3950
+ resp = await client.list_agents(
3951
+ region=region, framework="openclaw", page=page, page_size=size
3952
+ )
3676
3953
  agents = resp.get("agents", []) or []
3677
3954
  total = int(resp.get("total") or len(agents))
3678
3955
  rows = []
@@ -3765,7 +4042,9 @@ def status(agent_ref: Optional[str], region: Optional[str], dry_run: bool, outpu
3765
4042
  agent = await client.get_agent(name=agent_ref)
3766
4043
 
3767
4044
  if not agent:
3768
- raise resolution_error(f"未找到 OpenClaw: {agent_ref}", hints=["agentengine openclaw list"])
4045
+ raise resolution_error(
4046
+ f"未找到 OpenClaw: {agent_ref}", hints=["agentengine openclaw list"]
4047
+ )
3769
4048
 
3770
4049
  detail = _flatten_agent_detail(agent)
3771
4050
  status_val = detail.get("status", "UNKNOWN")
@@ -3781,7 +4060,11 @@ def status(agent_ref: Optional[str], region: Optional[str], dry_run: bool, outpu
3781
4060
  ("框架", str(detail.get("framework") or "-"), None),
3782
4061
  ("区域", str(detail.get("region") or region), None),
3783
4062
  ("Endpoint", str(detail.get("endpoint") or "N/A"), "#58a6ff"),
3784
- ("Langfuse", str(detail.get("langfuse_url") or "-"), "#58a6ff" if detail.get("langfuse_url") else None),
4063
+ (
4064
+ "Langfuse",
4065
+ str(detail.get("langfuse_url") or "-"),
4066
+ "#58a6ff" if detail.get("langfuse_url") else None,
4067
+ ),
3785
4068
  ("镜像", str(detail.get("artifact_path") or "-"), None),
3786
4069
  ("创建时间", created_at_display, None),
3787
4070
  ("更新时间", updated_at_display, None),
@@ -3844,6 +4127,7 @@ def _delete_impl(agent_refs: tuple[str, ...], region: str, assume_yes: bool, dry
3844
4127
 
3845
4128
  # 清理本地状态
3846
4129
  from ksadk.deployment.state import clear_state
4130
+
3847
4131
  try:
3848
4132
  removed = clear_state(Path("."), key=agent_ref)
3849
4133
  if removed:
@@ -3866,7 +4150,7 @@ def _delete_impl(agent_refs: tuple[str, ...], region: str, assume_yes: bool, dry
3866
4150
  "failed": failed_refs,
3867
4151
  }
3868
4152
 
3869
- dry_run_kwargs = {"dry_run": dry_run}
4153
+ dry_run_kwargs: dict[str, Any] = {"dry_run": dry_run}
3870
4154
  if is_json_output():
3871
4155
  dry_run_kwargs.update(
3872
4156
  dry_run_resource="openclaw",
@@ -3904,7 +4188,13 @@ def _delete_impl(agent_refs: tuple[str, ...], region: str, assume_yes: bool, dry
3904
4188
  @confirm_options()
3905
4189
  @dry_run_option()
3906
4190
  @cli_output_option()
3907
- def delete(agent_refs: tuple[str, ...], region: str, assume_yes: bool, dry_run: bool, output_mode: str | None):
4191
+ def delete(
4192
+ agent_refs: tuple[str, ...],
4193
+ region: str,
4194
+ assume_yes: bool,
4195
+ dry_run: bool,
4196
+ output_mode: str | None,
4197
+ ):
3908
4198
  """删除 OpenClaw 实例。"""
3909
4199
  _ = output_mode
3910
4200
  _delete_impl(agent_refs=agent_refs, region=region, assume_yes=assume_yes, dry_run=dry_run)
@@ -3916,7 +4206,13 @@ def delete(agent_refs: tuple[str, ...], region: str, assume_yes: bool, dry_run:
3916
4206
  @confirm_options()
3917
4207
  @dry_run_option()
3918
4208
  @cli_output_option()
3919
- def destroy(agent_refs: tuple[str, ...], region: str, assume_yes: bool, dry_run: bool, output_mode: str | None):
4209
+ def destroy(
4210
+ agent_refs: tuple[str, ...],
4211
+ region: str,
4212
+ assume_yes: bool,
4213
+ dry_run: bool,
4214
+ output_mode: str | None,
4215
+ ):
3920
4216
  """删除 OpenClaw 实例。"""
3921
4217
  _ = output_mode
3922
4218
  _delete_impl(agent_refs=agent_refs, region=region, assume_yes=assume_yes, dry_run=dry_run)