agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/METADATA +22 -28
- agentengine_sdk_python-0.8.4.dist-info/RECORD +1070 -0
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +108 -91
- ksadk/builders/code_builder.py +36 -9
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +36 -7
- ksadk/builders/managed_runtime_builder.py +4 -0
- ksadk/cli/__init__.py +38 -3
- ksadk/cli/cmd_managed_runtime.py +13 -1
- ksadk/cli/cmd_plugin.py +662 -0
- ksadk/cli/cmd_web.py +2 -5
- ksadk/cli/runtime_bootstrap.py +23 -1
- ksadk/codex/client.py +424 -96
- ksadk/codex/projection.py +24 -0
- ksadk/codex/runtime.py +83 -283
- ksadk/common/auth.py +16 -10
- ksadk/configs/env_registry.py +59 -3
- ksadk/configs/settings.py +9 -0
- ksadk/context_engine/contributors.py +73 -8
- ksadk/conversations/__init__.py +19 -0
- ksadk/conversations/context.py +104 -24
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +154 -15
- ksadk/conversations/projector.py +614 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/conversations/runtime_invocation.py +5 -0
- ksadk/conversations/runtime_payloads.py +1 -0
- ksadk/conversations/runtime_preparation.py +30 -27
- ksadk/conversations/runtime_resume.py +50 -2
- ksadk/conversations/runtime_stream_events.py +5 -0
- ksadk/conversations/runtime_streaming.py +25 -7
- ksadk/deployment/env_forward.py +2 -2
- ksadk/deployment/providers/serverless.py +1 -1
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/_codex_validators.py +18 -7
- ksadk/events/adapters/codex.py +190 -0
- ksadk/events/adapters/langgraph.py +15 -1
- ksadk/events/canonical_store.py +3 -1
- ksadk/events/projections.py +1 -1
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/config.py +2 -2
- ksadk/harness/runtime.py +75 -16
- ksadk/harness/tools.py +38 -13
- ksadk/interaction/provider.py +1 -0
- ksadk/interaction/providers/langgraph.py +1 -0
- ksadk/kernel/bootstrap.py +28 -4
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +36 -2
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +28 -0
- ksadk/kernel/store.py +3 -0
- ksadk/kernel/worker.py +76 -10
- ksadk/knowledge_base/client.py +57 -27
- ksadk/mcp_runtime/__init__.py +12 -4
- ksadk/memory/adk/backends/sdk_ltm_backend.py +161 -41
- ksadk/memory/adk/resilient_session_service.py +25 -17
- ksadk/memory/adk/short_term_memory.py +26 -9
- ksadk/memory/models.py +7 -4
- ksadk/memory/provider_resolver.py +3 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/model_proxy/server.py +10 -5
- ksadk/plugins/__init__.py +171 -0
- ksadk/plugins/artifacts.py +275 -0
- ksadk/plugins/bridges/__init__.py +47 -0
- ksadk/plugins/bridges/codex.py +659 -0
- ksadk/plugins/bridges/dsh.py +1300 -0
- ksadk/plugins/builtins.py +746 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +191 -0
- ksadk/plugins/codex_manifest.py +983 -0
- ksadk/plugins/composition.py +537 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +569 -0
- ksadk/plugins/delivery.py +153 -0
- ksadk/plugins/dsh_installation_digest.py +71 -0
- ksadk/plugins/dsh_toolchain.py +1024 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +787 -0
- ksadk/plugins/kernel_adapter.py +205 -0
- ksadk/plugins/providers/__init__.py +108 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/index.mjs +53 -0
- ksadk/plugins/providers/bundles/dsh-knowledge/package.json +16 -0
- ksadk/plugins/providers/bundles/dsh-memory/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-memory/index.mjs +175 -0
- ksadk/plugins/providers/bundles/dsh-memory/package.json +16 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/client.mjs +166 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/index.mjs +9 -0
- ksadk/plugins/providers/bundles/dsh-platform-resources/package.json +12 -0
- ksadk/plugins/providers/bundles/dsh-skill-center/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/dsh-skill-center/index.mjs +125 -0
- ksadk/plugins/providers/bundles/dsh-skill-center/package.json +16 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/index.mjs +870 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/package.json +27 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/client.js +92 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/index.mjs +15 -0
- ksadk/plugins/providers/bundles/ksadk-dsh-studio/package.json +7 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +988 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/codex_native.py +368 -0
- ksadk/plugins/providers/codex_turn.py +301 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_capabilities.py +1389 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/dsh_mcp.py +467 -0
- ksadk/plugins/providers/harness.py +632 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/mcp_projection.py +150 -0
- ksadk/plugins/providers/platform_resources.py +657 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +338 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/resource_runtime/__init__.py +4 -0
- ksadk/resource_runtime/broker.py +616 -0
- ksadk/resource_runtime/build_artifacts.py +165 -0
- ksadk/resource_runtime/contracts.py +138 -0
- ksadk/resource_runtime/discovery_receipts.py +100 -0
- ksadk/resource_runtime/discovery_skills.py +172 -0
- ksadk/resource_runtime/errors.py +6 -0
- ksadk/resource_runtime/ipc.py +142 -0
- ksadk/resource_runtime/kernel_approval.py +165 -0
- ksadk/resource_runtime/knowledge.py +114 -0
- ksadk/resource_runtime/langgraph.py +94 -0
- ksadk/resource_runtime/leases.py +191 -0
- ksadk/resource_runtime/managed_projection.py +186 -0
- ksadk/resource_runtime/mcp_server.py +138 -0
- ksadk/resource_runtime/memory_provider.py +318 -0
- ksadk/resource_runtime/memory_receipts.py +34 -0
- ksadk/resource_runtime/operation_ledger.py +211 -0
- ksadk/resource_runtime/plugin_config.py +48 -0
- ksadk/resource_runtime/policy_authorization.py +54 -0
- ksadk/resource_runtime/process.py +110 -0
- ksadk/resource_runtime/skill_artifacts.py +153 -0
- ksadk/resource_runtime/skill_runtime.py +42 -0
- ksadk/resource_runtime/skill_workspace.py +118 -0
- ksadk/resource_runtime/skills.py +215 -0
- ksadk/resource_runtime/snapshots.py +180 -0
- ksadk/resource_runtime/socket_server.py +103 -0
- ksadk/resource_runtime/supervisor.py +380 -0
- ksadk/resource_runtime/worker.py +504 -0
- ksadk/runners/_langgraph_runner_streams.py +74 -6
- ksadk/runners/adk_runner.py +149 -11
- ksadk/runners/base_runner.py +29 -1
- ksadk/runners/langgraph_runner.py +475 -145
- ksadk/runtime/_runner_adapter/stream_mapping.py +193 -60
- ksadk/runtime/adapter.py +3 -0
- ksadk/runtime/conversation_execution.py +92 -16
- ksadk/runtime/executor.py +78 -2
- ksadk/runtime/factory.py +30 -200
- ksadk/runtime/framework_adapters.py +41 -0
- ksadk/runtime/preprocessing.py +10 -2
- ksadk/runtime/runner_adapter.py +78 -14
- ksadk/runtime_context.py +40 -2
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/backends/e2b.py +52 -11
- ksadk/sandbox/backends/local_process.py +9 -0
- ksadk/sandbox/base.py +2 -0
- ksadk/sandbox/e2b_connection.py +66 -0
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +581 -0
- ksadk/server/composition.py +2 -0
- ksadk/server/factory.py +5 -0
- ksadk/server/persistence_capability.py +516 -0
- ksadk/server/routes/control.py +210 -46
- ksadk/server/routes/dependencies.py +15 -0
- ksadk/server/routes/models.py +47 -4
- ksadk/server/routes/projection.py +252 -12
- ksadk/server/routes/sessions.py +284 -107
- ksadk/server/routes/streaming.py +43 -9
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-CGbWlMNh.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-b4C7upmO.js} +3 -3
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-DbdW5A4_.js} +4 -4
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock-BEBN4Ksz.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-zvL-WpAn.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-BYafW7Za.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-bTohInsS.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-BW5UtSnx.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-Ch5Lbzsi.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-kqF4MDOG.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-8uSRIODv.js} +1 -1
- ksadk/server/static/assets/channel-CF5Up3hQ.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-KkXX47WI.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-DkhehZDr.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-BdXhtPe9.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-DPTpvpwi.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-Pqoh38el.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-DeU00qC-.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CzxScFkr.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-DpqMx-ux.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-C6t0SypQ.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-DVaeafZk.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Cz81acRQ.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Cz81acRQ.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-Bf9D3tzJ.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DVWs8a2t.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-B9cY3iTz.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-CSwlx3CB.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-UBpgon5q.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-C9akhN9a.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-CgedhTPI.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-Cjucz5BY.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-Dd4Vleyn.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-CZ2jGtYW.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-B2_CVgKv.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-B3FR1SKK.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-BIr4DSRq.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-BgV_owcB.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-Bj730Ib4.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-BnaTiyzN.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C1NMAY5a.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-C5_ZAZsG.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-CB9iJrvg.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-CFzsn6BG.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-Cf125P_m.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-ClYUxfeK.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DFD6Qa6T.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-DVCOWtha.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-D_ctSS2i.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-DiixqL0J.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-DoVSwoDb.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-DvFK7p5S.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-QfkEHOlb.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-btbmmX5O.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-fBO2v5Kt.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-ufY3F5p6.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-Ci8NqqZu.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CCIOhLZG.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CQqR2hg3.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-DDRRbGPm.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-Cp_r9Hb6.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-fgWEf7UD.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-Dlhc8Wr8.js} +1 -1
- ksadk/server/static/assets/index-DcTML1Up.css +2 -0
- ksadk/server/static/assets/index-Dh2XHqPK.js +244 -0
- ksadk/server/static/assets/info-A6RAGUB7-BUecLTdt.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CO0rRTRA.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CHiaedSS.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-CHcCIB09.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CJ_ebBZf.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-B4weu383.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-V-MgYHW1.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-CBijEX_c.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-Bc-pQvpp.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BeW-btFv.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-D7genGLl.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-X0xh4SaT.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-Bxj32zwD.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-BURv2mGh.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DzRMSqjd.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Cc44icX-.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-D6S4BdV-.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-vD3aVE2o.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-CYbW_b4T.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-CCsh3avW.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-_FRRBKEd.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-C-gr2o9M.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-D97NmfKg.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DjrPC5FX.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-DzMNg1hh.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BIcjKFhF.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-Cssf_CsU.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-tq3K3Ybl.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-BWTHzyS1.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-DBXt3GdU.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-DrCy4E_l.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-BSKgJsmR.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CefhOliM.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-eflHNoaZ.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-eYqrQ-v4.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/session_context.py +66 -0
- ksadk/sessions/__init__.py +11 -21
- ksadk/sessions/_local_service_sync.py +263 -1
- ksadk/sessions/base.py +176 -0
- ksadk/sessions/errors.py +4 -0
- ksadk/sessions/in_memory.py +309 -19
- ksadk/sessions/local_service.py +83 -1
- ksadk/sessions/persistence.py +275 -0
- ksadk/sessions/postgres_service.py +358 -0
- ksadk/sessions/resilience.py +8 -0
- ksadk/sessions/resilient.py +362 -4
- ksadk/sessions/topology.py +135 -0
- ksadk/skills/models.py +26 -4
- ksadk/skills/package_store.py +186 -38
- ksadk/skills/runtime/agent.py +31 -6
- ksadk/skills/runtime/artifact_delivery.py +139 -0
- ksadk/skills/runtime/backends/e2b.py +98 -6
- ksadk/skills/runtime/backends/local.py +73 -8
- ksadk/skills/runtime/base.py +3 -0
- ksadk/skills/runtime/executor.py +2 -0
- ksadk/skills/runtime/loader.py +10 -2
- ksadk/skills/runtime/pinned.py +114 -0
- ksadk/skills/runtime/request.py +31 -0
- ksadk/skills/service_client.py +211 -24
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +714 -90
- ksadk/studio/api_catalog_routes.py +23 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +911 -0
- ksadk/studio/api_resource_connections.py +183 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +31 -16
- ksadk/studio/builder.py +199 -43
- ksadk/studio/cloud.py +346 -104
- ksadk/studio/cloud_shared_web.py +565 -0
- ksadk/studio/codex_agent_service.py +140 -27
- ksadk/studio/codex_builder.py +294 -16
- ksadk/studio/codex_credentials.py +90 -0
- ksadk/studio/codex_manifest.py +51 -4
- ksadk/studio/codex_plugin_store.py +746 -0
- ksadk/studio/codex_provider_build.py +231 -0
- ksadk/studio/codex_run.py +200 -29
- ksadk/studio/compatibility_report.py +456 -0
- ksadk/studio/compiler.py +84 -15
- ksadk/studio/contracts.py +249 -4
- ksadk/studio/dsh_application.py +137 -0
- ksadk/studio/dsh_capability_service.py +1107 -0
- ksadk/studio/dsh_models.py +59 -0
- ksadk/studio/dsh_provider_registration.py +1075 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +89 -14
- ksadk/studio/mcp_runtime.py +11 -3
- ksadk/studio/model_client.py +2 -2
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +468 -0
- ksadk/studio/plugin_kernel_adapter.py +28 -0
- ksadk/studio/plugin_runtime.py +831 -0
- ksadk/studio/resource_authority.py +637 -0
- ksadk/studio/resource_binding_validation.py +77 -0
- ksadk/studio/resource_build_admission.py +196 -0
- ksadk/studio/resource_build_materializer.py +149 -0
- ksadk/studio/resource_catalog.py +149 -3
- ksadk/studio/resource_connections.py +220 -0
- ksadk/studio/run_service.py +1144 -96
- ksadk/studio/runtime_source.py +24 -8
- ksadk/studio/scheduler_assistant.py +317 -0
- ksadk/studio/scheduler_runtime.py +383 -0
- ksadk/studio/scheduler_service.py +214 -0
- ksadk/studio/service.py +1131 -95
- ksadk/studio/shared_web.py +610 -87
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/CodeBlock-DCUUY0PL-BLjA7kbS.js +41 -0
- ksadk/studio/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/studio/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/studio/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/studio/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/studio/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/studio/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/studio/static/assets/MathMessageMarkdown-CBcDrAw--BEOyPC3w.css +1 -0
- ksadk/studio/static/assets/MathMessageMarkdown-CBcDrAw--CUYIXwgI.js +8 -0
- ksadk/studio/static/assets/MermaidBlock-DBFTztfQ-BTYKWEbD.js +44 -0
- ksadk/studio/static/assets/PrismRenderer-wiXRHURw.js +3 -0
- ksadk/studio/static/assets/abnfDiagram-VCTEODGH-CLETGjKq.js +1 -0
- ksadk/studio/static/assets/arc-Pry-7G1_.js +1 -0
- ksadk/studio/static/assets/architecture-7GRP2DOG-hXMiO_AU.js +1 -0
- ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-B5EQysk6.js +36 -0
- ksadk/studio/static/assets/array-BifhSqXX.js +1 -0
- ksadk/studio/static/assets/blockDiagram-I7D4REHJ-CFmPl1de.js +129 -0
- ksadk/studio/static/assets/c4Diagram-7LVT6UL2-BRrGu8qt.js +38 -0
- ksadk/studio/static/assets/channel-BqO-gtEw.js +1 -0
- ksadk/studio/static/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- ksadk/studio/static/assets/chunk-4HAMMTFA-D220vUtq.js +62 -0
- ksadk/studio/static/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- ksadk/studio/static/assets/chunk-75Z2AOVW-C8lLv_qc.js +2 -0
- ksadk/studio/static/assets/chunk-DU6HZSFF-C4DtVpCu.js +128 -0
- ksadk/studio/static/assets/chunk-F27PBJKO-Cpw14SNG.js +1 -0
- ksadk/studio/static/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
- ksadk/studio/static/assets/chunk-GMAD6QVW-DiQA-u2N.js +72 -0
- ksadk/studio/static/assets/chunk-GVQU2GXP-DUdNBUCi.js +1 -0
- ksadk/studio/static/assets/chunk-IMKFNOWR-BSlbjT_b.js +231 -0
- ksadk/studio/static/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- ksadk/studio/static/assets/chunk-L3NEJ4N5-Db8Ab22W.js +1 -0
- ksadk/studio/static/assets/chunk-OSK3NFVY-M2m6G3j4.js +10 -0
- ksadk/studio/static/assets/chunk-P2QGCYS3-DezaQxiK.js +1 -0
- ksadk/studio/static/assets/chunk-POPQ4Y6H-DOpufJ3t.js +1 -0
- ksadk/studio/static/assets/chunk-PWAF6VOD-CfLb1gSc.js +1 -0
- ksadk/studio/static/assets/chunk-SHT3W25Y-DAyIVp5H.js +168 -0
- ksadk/studio/static/assets/chunk-SVP7TREG-DulomOrS.js +88 -0
- ksadk/studio/static/assets/chunk-TICWLB2K-bHJYoflj.js +206 -0
- ksadk/studio/static/assets/chunk-XXDRQBXY-7PcdR2B1.js +1 -0
- ksadk/studio/static/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- ksadk/studio/static/assets/classDiagram-ZZMXUADV-D_bW8JjA.js +1 -0
- ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-D_bW8JjA.js +1 -0
- ksadk/studio/static/assets/cose-bilkent-JH36ORCC-snL88wkX.js +1 -0
- ksadk/studio/static/assets/cynefin-OW5HDTMX-Bd1YhRo1.js +1 -0
- ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-BBrIG_mI.js +62 -0
- ksadk/studio/static/assets/cytoscape.esm-CGd-uY3x.js +321 -0
- ksadk/studio/static/assets/dagre-GXQ25YYZ-DtF1nGdY.js +4 -0
- ksadk/studio/static/assets/dagre-PrKaheQc.js +1 -0
- ksadk/studio/static/assets/defaultLocale-BFoDCU3G.js +1 -0
- ksadk/studio/static/assets/diagram-S7CK7UJ4-0BZ98mya.js +30 -0
- ksadk/studio/static/assets/diagram-UQ7AKVKN-6mLKEo6L.js +41 -0
- ksadk/studio/static/assets/diagram-VSXAHHWV-CJBIaiZ2.js +3 -0
- ksadk/studio/static/assets/diagram-VX7I27RA-BohLEE1C.js +24 -0
- ksadk/studio/static/assets/diagram-Z3DM3KII-D8KIGonG.js +24 -0
- ksadk/studio/static/assets/dist-v5Q1xZ2K.js +1 -0
- ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-C99HU61t.js +1 -0
- ksadk/studio/static/assets/erDiagram-RLTQ6QDP-DLGry2Ap.js +99 -0
- ksadk/studio/static/assets/eventmodeling-NTZA5JFV-B1_5Ac8j.js +1 -0
- ksadk/studio/static/assets/flowDiagram-HODETNUW-DD5hmicn.js +1 -0
- ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BPJrVLSG.js +292 -0
- ksadk/studio/static/assets/gitGraph-4MIJSDKK-Cy_ZQBGi.js +1 -0
- ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-CZCUz4MT.js +106 -0
- ksadk/studio/static/assets/graphlib-DS17s2tU.js +1 -0
- ksadk/studio/static/assets/index-CeHL9ARV.css +2 -0
- ksadk/studio/static/assets/index-D9QEcVpL.js +320 -0
- ksadk/studio/static/assets/info-A6RAGUB7-DBzDQB_I.js +1 -0
- ksadk/studio/static/assets/infoDiagram-27XIBGKW-B-sq07Hi.js +2 -0
- ksadk/studio/static/assets/init-C-OQMol4.js +1 -0
- ksadk/studio/static/assets/ishikawaDiagram-5VMMS53U-CljI2wyf.js +70 -0
- ksadk/studio/static/assets/journeyDiagram-3NMN7TZE-D50Fo2sL.js +139 -0
- ksadk/studio/static/assets/kanban-definition-UXKFOSKX-DimfOTam.js +89 -0
- ksadk/studio/static/assets/katex-DhON-x74.js +257 -0
- ksadk/studio/static/assets/line-CX5BOOUP.js +1 -0
- ksadk/studio/static/assets/linear-BCxN9JLv.js +1 -0
- ksadk/studio/static/assets/mermaid-parser.core-BdCz8fkS.js +7 -0
- ksadk/studio/static/assets/mindmap-definition-YA3MSWOX-Bizzqy5R.js +96 -0
- ksadk/studio/static/assets/ordinal-BDEzSJ7C.js +1 -0
- ksadk/studio/static/assets/packet-AYTQ26CC-gPln-_kG.js +1 -0
- ksadk/studio/static/assets/path-fybaL0A-.js +1 -0
- ksadk/studio/static/assets/pegDiagram-XKGWAZYB-Dy3fB-gW.js +1 -0
- ksadk/studio/static/assets/pie-WAS4IAKB-qZdHMr7B.js +1 -0
- ksadk/studio/static/assets/pieDiagram-E7YTZNPT-B9mrFSDF.js +39 -0
- ksadk/studio/static/assets/quadrantDiagram-AXDQQJYC-NzUfCbMR.js +7 -0
- ksadk/studio/static/assets/radar-RG4KPBEZ-BZlnaea9.js +1 -0
- ksadk/studio/static/assets/railroad-74A4TZTK-Ce3__H9c.js +1 -0
- ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-DqQn7EzD.js +1 -0
- ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-B55kCA8F.js +1 -0
- ksadk/studio/static/assets/railroad-peg-WCYAUIDC-DfQnvkHd.js +1 -0
- ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-DnhMjZCk.js +1 -0
- ksadk/studio/static/assets/requirementDiagram-BXWQKSXE-BF3d103U.js +84 -0
- ksadk/studio/static/assets/rolldown-runtime-Dy4uBu1J-DK3Fl9T5.js +1 -0
- ksadk/studio/static/assets/rough.esm-Dy-Kn_BL.js +1 -0
- ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-Bo-JTwt0.js +40 -0
- ksadk/studio/static/assets/sequenceDiagram-WJ2MYXX4-Dh4wZtUm.js +162 -0
- ksadk/studio/static/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- ksadk/studio/static/assets/src-SFn9St9H.js +1 -0
- ksadk/studio/static/assets/stateDiagram-D77RDMKH-DH774G6d.js +1 -0
- ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-DZvVWPBo.js +1 -0
- ksadk/studio/static/assets/swimlanes-42K2YHIH-Bl13OvTX.js +1 -0
- ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-BnFOTALJ.js +8 -0
- ksadk/studio/static/assets/timeline-definition-24CTP7MA-DxmSSjRB.js +120 -0
- ksadk/studio/static/assets/treeView-Q6P3EWNA-D5Nn9Swz.js +1 -0
- ksadk/studio/static/assets/treemap-WGGIJYW6-CgHgnQhE.js +1 -0
- ksadk/studio/static/assets/vennDiagram-4TSXK5OY-B9QEXcJq.js +34 -0
- ksadk/studio/static/assets/wardley-WFR3VGLG-DXrK2RRY.js +1 -0
- ksadk/studio/static/assets/wardleyDiagram-VM6X3IG4-Ba1AOWoe.js +78 -0
- ksadk/studio/static/assets/xychartDiagram-S5SC5T6Z-Cr5pt5Y-.js +7 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/validator.py +1 -1
- ksadk/studio/workspace.py +1 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/workspace_files/path_utils.py +41 -0
- agentengine_sdk_python-0.8.2.dist-info/RECORD +0 -778
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
- ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
- ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
- ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +0 -3
- ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
- ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1300 @@
|
|
|
1
|
+
"""Transactional DeepSeek Harness profile plugin bridge.
|
|
2
|
+
|
|
3
|
+
DSH remains the native owner of its plugin packages and composed runtime. This
|
|
4
|
+
module only manages one isolated Profile through the public ``dsh plugin`` and
|
|
5
|
+
``--dump-config`` commands, then projects non-secret inventory into KsADK.
|
|
6
|
+
It deliberately does not import Cordis or execute DSH plugin code in PluginHost.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import signal
|
|
17
|
+
import stat
|
|
18
|
+
import subprocess
|
|
19
|
+
import tempfile
|
|
20
|
+
import threading
|
|
21
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
22
|
+
from contextlib import contextmanager
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Iterator, Literal, TypedDict
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import fcntl
|
|
29
|
+
except ImportError: # pragma: no cover - DSH production hosts are Unix
|
|
30
|
+
fcntl = None # type: ignore[assignment]
|
|
31
|
+
|
|
32
|
+
import yaml
|
|
33
|
+
from pydantic import BaseModel, ConfigDict
|
|
34
|
+
|
|
35
|
+
from ksadk.plugins.dsh_installation_digest import (
|
|
36
|
+
DshInstallationDigestError,
|
|
37
|
+
installation_digest,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_COMMAND_TIMEOUT_SECONDS = 120
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _to_camel(value: str) -> str:
|
|
44
|
+
head, *tail = value.split("_")
|
|
45
|
+
return head + "".join(part.capitalize() for part in tail)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _DshModel(BaseModel):
|
|
49
|
+
model_config = ConfigDict(
|
|
50
|
+
alias_generator=_to_camel,
|
|
51
|
+
populate_by_name=True,
|
|
52
|
+
extra="forbid",
|
|
53
|
+
frozen=True,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DshBridgeHost(_DshModel):
|
|
58
|
+
host_id: Literal["deepseek-harness"] = "deepseek-harness"
|
|
59
|
+
version: str
|
|
60
|
+
protocol: Literal["dsh.profile/v1"] = "dsh.profile/v1"
|
|
61
|
+
available: Literal[True] = True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class DshPluginInventory(_DshModel):
|
|
65
|
+
ecosystem: Literal["dsh"] = "dsh"
|
|
66
|
+
integration_mode: Literal["bridged"] = "bridged"
|
|
67
|
+
profile: str
|
|
68
|
+
name: str
|
|
69
|
+
display_name: str
|
|
70
|
+
description: str = ""
|
|
71
|
+
version: str
|
|
72
|
+
requested_spec: str
|
|
73
|
+
source_digest: str | None = None
|
|
74
|
+
source_kind: Literal["directory", "tgz"] | None = None
|
|
75
|
+
client_extension: bool = False
|
|
76
|
+
settings_integration: bool = False
|
|
77
|
+
installed: Literal[True] = True
|
|
78
|
+
enabled: bool
|
|
79
|
+
permissions_declared: Literal[False] = False
|
|
80
|
+
risk_disclosures: tuple[str, ...] = (
|
|
81
|
+
"DSH packages and install scripts run with the native host user privileges.",
|
|
82
|
+
"DSH bundle manifests do not declare a complete runtime permission set.",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DshProfileProjection(_DshModel):
|
|
87
|
+
profile: str
|
|
88
|
+
bundles: tuple[str, ...]
|
|
89
|
+
config_digest: str
|
|
90
|
+
config_bytes: int
|
|
91
|
+
host_version: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class DshProfileBuildSnapshot(_DshModel):
|
|
95
|
+
"""Credential-free identity of the actual local profile execution inputs."""
|
|
96
|
+
|
|
97
|
+
projection: DshProfileProjection
|
|
98
|
+
dependency_lock_digest: str
|
|
99
|
+
installation_digest: str
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def digest(self) -> str:
|
|
103
|
+
payload = json.dumps(self.model_dump(mode="json"), sort_keys=True, separators=(",", ":"))
|
|
104
|
+
return "sha256:" + hashlib.sha256(payload.encode()).hexdigest()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class DshBridgeError(RuntimeError):
|
|
108
|
+
"""Base failure for one bounded DSH profile operation."""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class DshHostUnavailableError(DshBridgeError):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class DshPluginNotFoundError(DshBridgeError):
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DshPluginApprovalRequired(DshBridgeError):
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DshPluginMutationError(DshBridgeError):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class DshProfileRecoveryError(DshPluginMutationError):
|
|
128
|
+
"""The owner must keep run admission suspended until profile recovery."""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class _CommandResult(_DshModel):
|
|
132
|
+
stdout: str = ""
|
|
133
|
+
stderr: str = ""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
CommandRunner = Callable[[Sequence[str], Path, Mapping[str, str]], _CommandResult]
|
|
137
|
+
|
|
138
|
+
_PROFILE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
|
|
139
|
+
_PACKAGE_NAME = re.compile(r"^(?:@[A-Za-z0-9._-]+/)?[A-Za-z0-9._-]+$")
|
|
140
|
+
_PINNED_REGISTRY_SOURCE = re.compile(
|
|
141
|
+
r"^(?:@[A-Za-z0-9._-]+/)?[A-Za-z0-9._-]+@"
|
|
142
|
+
r"\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$"
|
|
143
|
+
)
|
|
144
|
+
_HOST_VERSION = re.compile(r"\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b")
|
|
145
|
+
_STATE_FILE = ".ksadk-dsh-plugins.json"
|
|
146
|
+
_IMMUTABLE_SOURCE_DIR = "immutable-plugin-sources"
|
|
147
|
+
_SNAPSHOT_FILES = ("package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", _STATE_FILE)
|
|
148
|
+
_WEB_PROFILE_BUILTINS = ("@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app")
|
|
149
|
+
_MAX_JSON_BYTES = 2 * 1024 * 1024
|
|
150
|
+
_PROFILE_LOCK_DIR = "profile-locks"
|
|
151
|
+
_DSH_SUBPROCESS_ENV_KEYS = (
|
|
152
|
+
"PATH",
|
|
153
|
+
"HOME",
|
|
154
|
+
"USER",
|
|
155
|
+
"LOGNAME",
|
|
156
|
+
"SHELL",
|
|
157
|
+
"TMPDIR",
|
|
158
|
+
"TMP",
|
|
159
|
+
"TEMP",
|
|
160
|
+
"LANG",
|
|
161
|
+
"LANGUAGE",
|
|
162
|
+
"LC_ALL",
|
|
163
|
+
"LC_CTYPE",
|
|
164
|
+
"HTTP_PROXY",
|
|
165
|
+
"HTTPS_PROXY",
|
|
166
|
+
"ALL_PROXY",
|
|
167
|
+
"NO_PROXY",
|
|
168
|
+
"http_proxy",
|
|
169
|
+
"https_proxy",
|
|
170
|
+
"all_proxy",
|
|
171
|
+
"no_proxy",
|
|
172
|
+
"SSL_CERT_FILE",
|
|
173
|
+
"SSL_CERT_DIR",
|
|
174
|
+
"NODE_EXTRA_CA_CERTS",
|
|
175
|
+
"REQUESTS_CA_BUNDLE",
|
|
176
|
+
"CURL_CA_BUNDLE",
|
|
177
|
+
"NPM_CONFIG_REGISTRY",
|
|
178
|
+
"npm_config_registry",
|
|
179
|
+
"SYSTEMROOT",
|
|
180
|
+
"WINDIR",
|
|
181
|
+
"COMSPEC",
|
|
182
|
+
"PATHEXT",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def validate_dsh_registry_source(source: str) -> str:
|
|
187
|
+
"""Return one exact, credential-free npm registry package coordinate."""
|
|
188
|
+
|
|
189
|
+
value = source.strip()
|
|
190
|
+
if not _PINNED_REGISTRY_SOURCE.fullmatch(value):
|
|
191
|
+
raise ValueError(
|
|
192
|
+
"DSH registry source must be <package>@<exact-semver>; "
|
|
193
|
+
"Git URLs, tags, and ranges are not allowed"
|
|
194
|
+
)
|
|
195
|
+
return value
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def validate_dsh_registry_request(source: str) -> str:
|
|
199
|
+
"""Accept a package name for latest resolution, or an exact coordinate."""
|
|
200
|
+
value = source.strip()
|
|
201
|
+
if len(value) <= 214 and not value.startswith("-") and _PACKAGE_NAME.fullmatch(value):
|
|
202
|
+
return value
|
|
203
|
+
return validate_dsh_registry_source(value)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def dsh_subprocess_environment(*, dsh_home: Path | None = None) -> dict[str, str]:
|
|
207
|
+
"""Build the explicit environment inherited by DSH and pnpm children.
|
|
208
|
+
|
|
209
|
+
DSH bundles may execute package lifecycle scripts. They need process
|
|
210
|
+
discovery, a home/temp directory, locale, proxies and CA configuration,
|
|
211
|
+
but never receive arbitrary model, cloud, npm, SSH or host application
|
|
212
|
+
credentials from the parent process.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
environment = {
|
|
216
|
+
name: os.environ[name] for name in _DSH_SUBPROCESS_ENV_KEYS if os.environ.get(name)
|
|
217
|
+
}
|
|
218
|
+
if "PATH" not in environment:
|
|
219
|
+
environment["PATH"] = os.defpath
|
|
220
|
+
if dsh_home is not None:
|
|
221
|
+
environment["DSH_HOME"] = str(dsh_home)
|
|
222
|
+
return environment
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class _SourceReceipt(TypedDict):
|
|
226
|
+
digest: str
|
|
227
|
+
kind: Literal["directory", "tgz"]
|
|
228
|
+
artifact: str
|
|
229
|
+
dependency_spec: str
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class _ProfileState(TypedDict):
|
|
233
|
+
order: list[str]
|
|
234
|
+
disabled: list[str]
|
|
235
|
+
sources: dict[str, _SourceReceipt]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass(frozen=True)
|
|
239
|
+
class _PreparedSource:
|
|
240
|
+
command_source: str
|
|
241
|
+
digest: str
|
|
242
|
+
kind: Literal["directory", "tgz"]
|
|
243
|
+
artifact: str
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass(frozen=True)
|
|
247
|
+
class _ProfileSnapshot:
|
|
248
|
+
files: Mapping[str, bytes | None]
|
|
249
|
+
node_modules_existed: bool
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class DshProfilePluginBridge:
|
|
253
|
+
"""Manage DSH bundles in one isolated Profile with rollback and preflight."""
|
|
254
|
+
|
|
255
|
+
def __init__(
|
|
256
|
+
self,
|
|
257
|
+
*,
|
|
258
|
+
dsh_home: Path,
|
|
259
|
+
profile: str = "web",
|
|
260
|
+
dsh_command: Sequence[str] | None = None,
|
|
261
|
+
command_runner: CommandRunner | None = None,
|
|
262
|
+
cwd: Path | None = None,
|
|
263
|
+
) -> None:
|
|
264
|
+
if not _PROFILE_NAME.fullmatch(profile):
|
|
265
|
+
raise ValueError("DSH profile must be a simple name without path separators")
|
|
266
|
+
if dsh_command is not None and not dsh_command:
|
|
267
|
+
raise ValueError("DSH command cannot be empty")
|
|
268
|
+
self._dsh_home = dsh_home.expanduser().resolve()
|
|
269
|
+
self._profile = profile
|
|
270
|
+
self._profile_root = self._dsh_home / "profiles" / profile
|
|
271
|
+
self._command = tuple(dsh_command) if dsh_command is not None else None
|
|
272
|
+
self._runner = command_runner or self._run_command
|
|
273
|
+
self._cwd = (cwd or Path.cwd()).resolve()
|
|
274
|
+
self._host: DshBridgeHost | None = None
|
|
275
|
+
self._lock = threading.RLock()
|
|
276
|
+
self._transaction_local = threading.local()
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def host(self) -> DshBridgeHost:
|
|
280
|
+
if self._host is None:
|
|
281
|
+
raise DshHostUnavailableError("DSH bridge is not started")
|
|
282
|
+
return self._host
|
|
283
|
+
|
|
284
|
+
def start(self) -> DshBridgeHost:
|
|
285
|
+
if self._host is not None:
|
|
286
|
+
return self._host
|
|
287
|
+
command = self._resolve_command()
|
|
288
|
+
result = self._invoke((*command, "--version"), cwd=self._cwd)
|
|
289
|
+
match = _HOST_VERSION.search(result.stdout or result.stderr)
|
|
290
|
+
if match is None:
|
|
291
|
+
raise DshHostUnavailableError("DSH host did not report a parseable version")
|
|
292
|
+
self._command = command
|
|
293
|
+
self._host = DshBridgeHost(version=match.group(1))
|
|
294
|
+
return self._host
|
|
295
|
+
|
|
296
|
+
def close(self) -> None:
|
|
297
|
+
self._host = None
|
|
298
|
+
|
|
299
|
+
def __enter__(self) -> "DshProfilePluginBridge":
|
|
300
|
+
self.start()
|
|
301
|
+
return self
|
|
302
|
+
|
|
303
|
+
def __exit__(self, _exc_type: object, _exc: object, _tb: object) -> None:
|
|
304
|
+
self.close()
|
|
305
|
+
|
|
306
|
+
def list_plugins(self) -> tuple[DshPluginInventory, ...]:
|
|
307
|
+
with self._profile_transaction(exclusive=False):
|
|
308
|
+
return self._list_plugins_locked()
|
|
309
|
+
|
|
310
|
+
def _list_plugins_locked(self) -> tuple[DshPluginInventory, ...]:
|
|
311
|
+
self._ensure_started()
|
|
312
|
+
if not self._manifest_path().is_file():
|
|
313
|
+
return ()
|
|
314
|
+
manifest = self._read_manifest()
|
|
315
|
+
state = self._read_state(manifest)
|
|
316
|
+
self._verify_source_receipts(manifest, state)
|
|
317
|
+
active = set(self._bundles(manifest))
|
|
318
|
+
items: list[DshPluginInventory] = []
|
|
319
|
+
for name, requested_spec in self._dependencies(manifest).items():
|
|
320
|
+
package = self._read_package(name)
|
|
321
|
+
if package is None or self._bundle_patch(package) is None:
|
|
322
|
+
continue
|
|
323
|
+
dsh = package.get("dsh")
|
|
324
|
+
client = dsh.get("client") if isinstance(dsh, dict) else None
|
|
325
|
+
inject = client.get("inject") if isinstance(client, dict) else None
|
|
326
|
+
client_inject = (
|
|
327
|
+
tuple(item for item in inject if isinstance(item, str))
|
|
328
|
+
if isinstance(inject, list)
|
|
329
|
+
else ()
|
|
330
|
+
)
|
|
331
|
+
receipt = state["sources"].get(name)
|
|
332
|
+
items.append(
|
|
333
|
+
DshPluginInventory(
|
|
334
|
+
profile=self._profile,
|
|
335
|
+
name=name,
|
|
336
|
+
display_name=self._string(package.get("displayName"))
|
|
337
|
+
or self._string(package.get("name"))
|
|
338
|
+
or name,
|
|
339
|
+
description=self._string(package.get("description")),
|
|
340
|
+
version=self._string(package.get("version")),
|
|
341
|
+
requested_spec=requested_spec,
|
|
342
|
+
source_digest=receipt["digest"] if receipt is not None else None,
|
|
343
|
+
source_kind=receipt["kind"] if receipt is not None else None,
|
|
344
|
+
client_extension=isinstance(client, dict),
|
|
345
|
+
settings_integration=any(
|
|
346
|
+
"client-ui-settings" in item or "client-ui-slots" in item
|
|
347
|
+
for item in client_inject
|
|
348
|
+
),
|
|
349
|
+
enabled=name in active and name not in set(state["disabled"]),
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
return tuple(sorted(items, key=lambda item: (item.display_name.casefold(), item.name)))
|
|
353
|
+
|
|
354
|
+
def get_plugin(self, name: str) -> DshPluginInventory:
|
|
355
|
+
self._validate_package_name(name)
|
|
356
|
+
matches = [item for item in self.list_plugins() if item.name == name]
|
|
357
|
+
if not matches:
|
|
358
|
+
raise DshPluginNotFoundError(f"DSH plugin {name!r} is not installed")
|
|
359
|
+
return matches[0]
|
|
360
|
+
|
|
361
|
+
def install_plugin(
|
|
362
|
+
self,
|
|
363
|
+
source: str,
|
|
364
|
+
*,
|
|
365
|
+
accept_host_permissions: bool = False,
|
|
366
|
+
) -> DshPluginInventory:
|
|
367
|
+
if not accept_host_permissions:
|
|
368
|
+
raise DshPluginApprovalRequired(
|
|
369
|
+
"DSH packages can run install scripts and runtime code with host privileges; "
|
|
370
|
+
"explicit approval is required"
|
|
371
|
+
)
|
|
372
|
+
self._validate_source(source)
|
|
373
|
+
source = self._resolve_install_source(source.strip())
|
|
374
|
+
with self._profile_transaction(exclusive=True):
|
|
375
|
+
self._require_package_mutation_rollback(new_profile_allowed=True)
|
|
376
|
+
snapshot = self._snapshot()
|
|
377
|
+
before = (
|
|
378
|
+
self._dependencies(self._read_manifest()) if self._manifest_path().is_file() else {}
|
|
379
|
+
)
|
|
380
|
+
try:
|
|
381
|
+
if self._manifest_path().is_file():
|
|
382
|
+
existing_manifest = self._read_manifest()
|
|
383
|
+
self._verify_source_receipts(
|
|
384
|
+
existing_manifest, self._read_state(existing_manifest)
|
|
385
|
+
)
|
|
386
|
+
prepared = self._prepare_source(source)
|
|
387
|
+
self._plugin_command("add", prepared.command_source)
|
|
388
|
+
manifest = self._read_manifest()
|
|
389
|
+
added = [name for name in self._dependencies(manifest) if name not in before]
|
|
390
|
+
if len(added) != 1:
|
|
391
|
+
raise DshPluginMutationError(
|
|
392
|
+
"installing one DSH plugin must add exactly one direct dependency"
|
|
393
|
+
)
|
|
394
|
+
name = added[0]
|
|
395
|
+
self._require_bundle(name)
|
|
396
|
+
state = self._read_state(manifest)
|
|
397
|
+
state["order"] = [item for item in state["order"] if item != name] + [name]
|
|
398
|
+
if name not in state["disabled"]:
|
|
399
|
+
state["disabled"].append(name)
|
|
400
|
+
if prepared.digest:
|
|
401
|
+
state["sources"][name] = {
|
|
402
|
+
"digest": prepared.digest,
|
|
403
|
+
"kind": prepared.kind,
|
|
404
|
+
"artifact": prepared.artifact,
|
|
405
|
+
"dependency_spec": self._dependencies(manifest)[name],
|
|
406
|
+
}
|
|
407
|
+
self._write_state(state)
|
|
408
|
+
self._write_active_bundles(manifest, state)
|
|
409
|
+
self._preflight()
|
|
410
|
+
return self.get_plugin(name)
|
|
411
|
+
except BaseException as error:
|
|
412
|
+
self._rollback(snapshot, error)
|
|
413
|
+
raise
|
|
414
|
+
|
|
415
|
+
def set_enabled(self, name: str, *, enabled: bool) -> DshPluginInventory:
|
|
416
|
+
self._validate_package_name(name)
|
|
417
|
+
with self._profile_transaction(exclusive=True):
|
|
418
|
+
snapshot = self._snapshot()
|
|
419
|
+
try:
|
|
420
|
+
manifest = self._read_manifest()
|
|
421
|
+
if name not in self._dependencies(manifest):
|
|
422
|
+
raise DshPluginNotFoundError(f"DSH plugin {name!r} is not installed")
|
|
423
|
+
self._require_bundle(name)
|
|
424
|
+
state = self._read_state(manifest)
|
|
425
|
+
self._verify_source_receipts(manifest, state, names=(name,))
|
|
426
|
+
if name not in state["order"]:
|
|
427
|
+
state["order"].append(name)
|
|
428
|
+
if enabled:
|
|
429
|
+
state["disabled"] = [item for item in state["disabled"] if item != name]
|
|
430
|
+
elif name not in state["disabled"]:
|
|
431
|
+
state["disabled"].append(name)
|
|
432
|
+
self._write_state(state)
|
|
433
|
+
self._write_active_bundles(manifest, state)
|
|
434
|
+
self._preflight()
|
|
435
|
+
return self.get_plugin(name)
|
|
436
|
+
except BaseException as error:
|
|
437
|
+
self._rollback(snapshot, error)
|
|
438
|
+
raise
|
|
439
|
+
|
|
440
|
+
def update_plugin(
|
|
441
|
+
self,
|
|
442
|
+
name: str,
|
|
443
|
+
*,
|
|
444
|
+
source: str | None = None,
|
|
445
|
+
accept_host_permissions: bool = False,
|
|
446
|
+
) -> DshPluginInventory:
|
|
447
|
+
self._validate_package_name(name)
|
|
448
|
+
if not accept_host_permissions:
|
|
449
|
+
raise DshPluginApprovalRequired(
|
|
450
|
+
"updating a DSH package requires host permission approval"
|
|
451
|
+
)
|
|
452
|
+
with self._profile_transaction(exclusive=True):
|
|
453
|
+
self._require_package_mutation_rollback(new_profile_allowed=False)
|
|
454
|
+
snapshot = self._snapshot()
|
|
455
|
+
try:
|
|
456
|
+
self.get_plugin(name)
|
|
457
|
+
before_manifest = self._read_manifest()
|
|
458
|
+
before_state = self._read_state(before_manifest)
|
|
459
|
+
self._verify_source_receipts(before_manifest, before_state, names=(name,))
|
|
460
|
+
existing_source = before_state["sources"].get(name)
|
|
461
|
+
if existing_source is not None and source is None:
|
|
462
|
+
raise DshPluginMutationError(
|
|
463
|
+
"updating an immutable local DSH plugin requires an explicit source"
|
|
464
|
+
)
|
|
465
|
+
prepared = None
|
|
466
|
+
if source is not None:
|
|
467
|
+
self._validate_source(source)
|
|
468
|
+
prepared = self._prepare_source(source)
|
|
469
|
+
if not prepared.digest:
|
|
470
|
+
raise DshPluginMutationError(
|
|
471
|
+
"an explicit update source must be a local directory or tgz"
|
|
472
|
+
)
|
|
473
|
+
self._plugin_command("add", prepared.command_source)
|
|
474
|
+
else:
|
|
475
|
+
self._plugin_command("update", name)
|
|
476
|
+
manifest = self._read_manifest()
|
|
477
|
+
if prepared is not None:
|
|
478
|
+
changed = {
|
|
479
|
+
dependency
|
|
480
|
+
for dependency in (
|
|
481
|
+
set(self._dependencies(before_manifest))
|
|
482
|
+
| set(self._dependencies(manifest))
|
|
483
|
+
)
|
|
484
|
+
if self._dependencies(before_manifest).get(dependency)
|
|
485
|
+
!= self._dependencies(manifest).get(dependency)
|
|
486
|
+
}
|
|
487
|
+
if changed != {name}:
|
|
488
|
+
raise DshPluginMutationError(
|
|
489
|
+
"updated local source must replace exactly the selected plugin"
|
|
490
|
+
)
|
|
491
|
+
self._require_bundle(name)
|
|
492
|
+
state = self._read_state(manifest)
|
|
493
|
+
if prepared is not None:
|
|
494
|
+
if name not in self._dependencies(manifest):
|
|
495
|
+
raise DshPluginMutationError(
|
|
496
|
+
"updated local source did not preserve the plugin package name"
|
|
497
|
+
)
|
|
498
|
+
state["sources"][name] = {
|
|
499
|
+
"digest": prepared.digest,
|
|
500
|
+
"kind": prepared.kind,
|
|
501
|
+
"artifact": prepared.artifact,
|
|
502
|
+
"dependency_spec": self._dependencies(manifest)[name],
|
|
503
|
+
}
|
|
504
|
+
self._write_state(state)
|
|
505
|
+
self._write_active_bundles(manifest, state)
|
|
506
|
+
self._verify_source_receipts(manifest, state, names=(name,))
|
|
507
|
+
self._preflight()
|
|
508
|
+
return self.get_plugin(name)
|
|
509
|
+
except BaseException as error:
|
|
510
|
+
self._rollback(snapshot, error)
|
|
511
|
+
raise
|
|
512
|
+
|
|
513
|
+
def uninstall_plugin(self, name: str) -> None:
|
|
514
|
+
self._validate_package_name(name)
|
|
515
|
+
with self._profile_transaction(exclusive=True):
|
|
516
|
+
self._require_package_mutation_rollback(new_profile_allowed=False)
|
|
517
|
+
snapshot = self._snapshot()
|
|
518
|
+
try:
|
|
519
|
+
self.get_plugin(name)
|
|
520
|
+
self._plugin_command("remove", name)
|
|
521
|
+
manifest = self._read_manifest()
|
|
522
|
+
state = self._read_state(manifest)
|
|
523
|
+
state["order"] = [item for item in state["order"] if item != name]
|
|
524
|
+
state["disabled"] = [item for item in state["disabled"] if item != name]
|
|
525
|
+
state["sources"].pop(name, None)
|
|
526
|
+
self._write_state(state)
|
|
527
|
+
self._write_active_bundles(manifest, state)
|
|
528
|
+
self._preflight()
|
|
529
|
+
if any(item.name == name for item in self.list_plugins()):
|
|
530
|
+
raise DshPluginMutationError("DSH host still reports the removed plugin")
|
|
531
|
+
except BaseException as error:
|
|
532
|
+
self._rollback(snapshot, error)
|
|
533
|
+
raise
|
|
534
|
+
|
|
535
|
+
def project_profile(self) -> DshProfileProjection:
|
|
536
|
+
"""Validate the native profile and expose only its digest, never raw config."""
|
|
537
|
+
|
|
538
|
+
with self._profile_transaction(exclusive=False):
|
|
539
|
+
self._ensure_started()
|
|
540
|
+
manifest = self._read_manifest()
|
|
541
|
+
self._verify_source_receipts(manifest, self._read_state(manifest))
|
|
542
|
+
result = self._preflight()
|
|
543
|
+
payload = result.stdout.encode("utf-8")
|
|
544
|
+
return DshProfileProjection(
|
|
545
|
+
profile=self._profile,
|
|
546
|
+
bundles=tuple(self._bundles(manifest)),
|
|
547
|
+
config_digest=f"sha256:{hashlib.sha256(payload).hexdigest()}",
|
|
548
|
+
config_bytes=len(payload),
|
|
549
|
+
host_version=self.host.version,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def migrate_to_isolated_layout(
|
|
553
|
+
self,
|
|
554
|
+
*,
|
|
555
|
+
accept_host_permissions: bool = False,
|
|
556
|
+
recover_external_dependency_links: bool = False,
|
|
557
|
+
) -> None:
|
|
558
|
+
"""Migrate a stopped profile; the caller owns Core/admission suspension.
|
|
559
|
+
|
|
560
|
+
Keep an independent byte copy for rollback: a frozen reinstall of the
|
|
561
|
+
old layout can itself hit the pinned pnpm shutdown defect. Studio-owned
|
|
562
|
+
profiles may also repair an isolated tree polluted by a legacy Core
|
|
563
|
+
module-fallback cache.
|
|
564
|
+
"""
|
|
565
|
+
if not accept_host_permissions:
|
|
566
|
+
raise DshPluginApprovalRequired("Profile migration may run package install scripts")
|
|
567
|
+
with self._profile_transaction(exclusive=True):
|
|
568
|
+
self._require_package_mutation_rollback(new_profile_allowed=False)
|
|
569
|
+
manifest = self._read_manifest()
|
|
570
|
+
self._verify_source_receipts(manifest, self._read_state(manifest))
|
|
571
|
+
settings_path = self._profile_root / "pnpm-workspace.yaml"
|
|
572
|
+
settings = yaml.safe_load(settings_path.read_text(encoding="utf-8"))
|
|
573
|
+
if not isinstance(settings, dict):
|
|
574
|
+
raise DshPluginMutationError("Invalid package-manager settings")
|
|
575
|
+
node_linker = settings.get("nodeLinker")
|
|
576
|
+
if node_linker not in {"hoisted", "isolated"}:
|
|
577
|
+
raise DshPluginMutationError(
|
|
578
|
+
"Only hoisted or isolated profiles can use this migration"
|
|
579
|
+
)
|
|
580
|
+
modules = self._profile_root / "node_modules"
|
|
581
|
+
if modules.is_symlink() or not modules.is_dir():
|
|
582
|
+
raise DshPluginMutationError("Profile installation is not a real directory")
|
|
583
|
+
# A normal migration only accepts a closed tree. Studio may repair
|
|
584
|
+
# its own legacy hoisted Profile when pnpm linked a dependency to
|
|
585
|
+
# an ambient store: move that tree atomically instead of reading or
|
|
586
|
+
# following the external target, then reinstall from the frozen lock.
|
|
587
|
+
moved_unsafe_tree = False
|
|
588
|
+
try:
|
|
589
|
+
original_digest = installation_digest(modules)
|
|
590
|
+
except DshInstallationDigestError as error:
|
|
591
|
+
if (
|
|
592
|
+
not recover_external_dependency_links
|
|
593
|
+
or str(error) != "DSH dependency link escapes the installed tree"
|
|
594
|
+
):
|
|
595
|
+
raise
|
|
596
|
+
original_digest = None
|
|
597
|
+
moved_unsafe_tree = True
|
|
598
|
+
if node_linker == "isolated" and not moved_unsafe_tree:
|
|
599
|
+
self._preflight()
|
|
600
|
+
return
|
|
601
|
+
fallback = self._profile_root / ".dsh-module-fallback"
|
|
602
|
+
if fallback.exists() and (fallback.is_symlink() or not fallback.is_dir()):
|
|
603
|
+
raise DshPluginMutationError("DSH module fallback is not a real directory")
|
|
604
|
+
snapshot = self._snapshot()
|
|
605
|
+
root = tempfile.mkdtemp(prefix=".layout-backup-", dir=self._profile_root)
|
|
606
|
+
preserve_backup = False
|
|
607
|
+
try:
|
|
608
|
+
backup = Path(root) / "node_modules"
|
|
609
|
+
fallback_backup = Path(root) / "module-fallback"
|
|
610
|
+
if moved_unsafe_tree:
|
|
611
|
+
os.replace(modules, backup)
|
|
612
|
+
else:
|
|
613
|
+
shutil.copytree(modules, backup, symlinks=True)
|
|
614
|
+
if installation_digest(backup) != original_digest:
|
|
615
|
+
raise DshPluginMutationError("Profile installation backup did not verify")
|
|
616
|
+
fallback_moved = fallback.is_dir()
|
|
617
|
+
if fallback_moved:
|
|
618
|
+
os.replace(fallback, fallback_backup)
|
|
619
|
+
original_files = Path(root) / "profile-files"
|
|
620
|
+
original_files.mkdir(mode=0o700)
|
|
621
|
+
for name, content in snapshot.files.items():
|
|
622
|
+
if content is not None:
|
|
623
|
+
original_path = original_files / name
|
|
624
|
+
original_path.write_bytes(content)
|
|
625
|
+
original_path.chmod(0o600)
|
|
626
|
+
try:
|
|
627
|
+
if not moved_unsafe_tree:
|
|
628
|
+
shutil.rmtree(modules)
|
|
629
|
+
settings["nodeLinker"] = "isolated"
|
|
630
|
+
settings_path.write_text(yaml.safe_dump(settings), encoding="utf-8")
|
|
631
|
+
settings_path.chmod(0o600)
|
|
632
|
+
self._plugin_command("install", "--frozen-lockfile")
|
|
633
|
+
current_lock = (self._profile_root / "pnpm-lock.yaml").read_bytes()
|
|
634
|
+
if current_lock != snapshot.files["pnpm-lock.yaml"]:
|
|
635
|
+
raise DshPluginMutationError("Layout migration changed the dependency lock")
|
|
636
|
+
# Native reconcile can re-enable previously disabled bundles.
|
|
637
|
+
for name in ("package.json", _STATE_FILE):
|
|
638
|
+
content = snapshot.files[name]
|
|
639
|
+
path = self._profile_root / name
|
|
640
|
+
if content is None:
|
|
641
|
+
path.unlink(missing_ok=True)
|
|
642
|
+
else:
|
|
643
|
+
path.write_bytes(content)
|
|
644
|
+
path.chmod(0o600)
|
|
645
|
+
self._verify_source_receipts(manifest, self._read_state(manifest))
|
|
646
|
+
self._preflight()
|
|
647
|
+
except BaseException as error:
|
|
648
|
+
try:
|
|
649
|
+
if modules.is_symlink():
|
|
650
|
+
modules.unlink()
|
|
651
|
+
elif modules.exists():
|
|
652
|
+
shutil.rmtree(modules)
|
|
653
|
+
os.replace(backup, modules)
|
|
654
|
+
if fallback.is_symlink():
|
|
655
|
+
fallback.unlink()
|
|
656
|
+
elif fallback.exists():
|
|
657
|
+
shutil.rmtree(fallback)
|
|
658
|
+
if fallback_moved:
|
|
659
|
+
os.replace(fallback_backup, fallback)
|
|
660
|
+
self._rollback(snapshot, error, reinstall=False)
|
|
661
|
+
except BaseException as recovery_error:
|
|
662
|
+
preserve_backup = True
|
|
663
|
+
raise DshProfileRecoveryError(
|
|
664
|
+
"Profile layout recovery failed; retain the layout backup for recovery"
|
|
665
|
+
) from recovery_error
|
|
666
|
+
raise
|
|
667
|
+
finally:
|
|
668
|
+
if not preserve_backup:
|
|
669
|
+
shutil.rmtree(root)
|
|
670
|
+
|
|
671
|
+
def snapshot_for_build(self) -> DshProfileBuildSnapshot:
|
|
672
|
+
"""Capture exact local inputs under the same profile mutation lock."""
|
|
673
|
+
with self._profile_transaction(exclusive=False):
|
|
674
|
+
projection = self.project_profile()
|
|
675
|
+
lock = self._profile_root / "pnpm-lock.yaml"
|
|
676
|
+
if lock.is_symlink() or not lock.is_file() or lock.stat().st_size > 16 * 1024 * 1024:
|
|
677
|
+
raise DshPluginMutationError("DSH Build requires a bounded dependency lockfile")
|
|
678
|
+
return DshProfileBuildSnapshot(
|
|
679
|
+
projection=projection,
|
|
680
|
+
dependency_lock_digest="sha256:" + hashlib.sha256(lock.read_bytes()).hexdigest(),
|
|
681
|
+
installation_digest=installation_digest(self._profile_root / "node_modules"),
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
def verify_build_snapshot(self, expected: DshProfileBuildSnapshot) -> None:
|
|
685
|
+
if self.snapshot_for_build() != expected:
|
|
686
|
+
raise DshPluginMutationError("DSH installed profile no longer matches the Build")
|
|
687
|
+
|
|
688
|
+
def _resolve_command(self) -> tuple[str, ...]:
|
|
689
|
+
if self._command is not None:
|
|
690
|
+
return self._command
|
|
691
|
+
executable = shutil.which("dsh")
|
|
692
|
+
if executable is None:
|
|
693
|
+
raise DshHostUnavailableError(
|
|
694
|
+
"DSH host is not installed; configure an exact DSH executable "
|
|
695
|
+
"before using the bridge"
|
|
696
|
+
)
|
|
697
|
+
return (executable,)
|
|
698
|
+
|
|
699
|
+
def _ensure_started(self) -> None:
|
|
700
|
+
if self._host is None or self._command is None:
|
|
701
|
+
raise DshHostUnavailableError("DSH bridge is not started")
|
|
702
|
+
|
|
703
|
+
@contextmanager
|
|
704
|
+
def _profile_transaction(self, *, exclusive: bool) -> Iterator[None]:
|
|
705
|
+
"""Serialize one Profile snapshot across bridge objects and processes."""
|
|
706
|
+
|
|
707
|
+
with self._lock:
|
|
708
|
+
depth = int(getattr(self._transaction_local, "depth", 0))
|
|
709
|
+
held_exclusive = bool(getattr(self._transaction_local, "exclusive", False))
|
|
710
|
+
if depth:
|
|
711
|
+
if exclusive and not held_exclusive:
|
|
712
|
+
raise DshPluginMutationError(
|
|
713
|
+
"cannot upgrade a DSH profile read transaction to a mutation"
|
|
714
|
+
)
|
|
715
|
+
self._transaction_local.depth = depth + 1
|
|
716
|
+
try:
|
|
717
|
+
yield
|
|
718
|
+
finally:
|
|
719
|
+
self._transaction_local.depth = depth
|
|
720
|
+
return
|
|
721
|
+
|
|
722
|
+
if fcntl is None:
|
|
723
|
+
raise DshHostUnavailableError("DSH profile transactions require Unix file locking")
|
|
724
|
+
lock_root = self._dsh_home / _PROFILE_LOCK_DIR
|
|
725
|
+
lock_root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
726
|
+
if lock_root.resolve() != lock_root:
|
|
727
|
+
raise DshPluginMutationError("DSH profile lock directory is not trusted")
|
|
728
|
+
lock_path = lock_root / f"{self._profile}.lock"
|
|
729
|
+
flags = os.O_CREAT | os.O_RDWR
|
|
730
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
731
|
+
flags |= os.O_NOFOLLOW
|
|
732
|
+
try:
|
|
733
|
+
descriptor = os.open(lock_path, flags, 0o600)
|
|
734
|
+
except OSError as error:
|
|
735
|
+
raise DshPluginMutationError(
|
|
736
|
+
"DSH profile transaction lock is unavailable"
|
|
737
|
+
) from error
|
|
738
|
+
try:
|
|
739
|
+
metadata = os.fstat(descriptor)
|
|
740
|
+
if not stat.S_ISREG(metadata.st_mode):
|
|
741
|
+
raise DshPluginMutationError(
|
|
742
|
+
"DSH profile transaction lock is not a regular file"
|
|
743
|
+
)
|
|
744
|
+
os.fchmod(descriptor, 0o600)
|
|
745
|
+
fcntl.flock(
|
|
746
|
+
descriptor,
|
|
747
|
+
fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH,
|
|
748
|
+
)
|
|
749
|
+
self._transaction_local.depth = 1
|
|
750
|
+
self._transaction_local.exclusive = exclusive
|
|
751
|
+
try:
|
|
752
|
+
yield
|
|
753
|
+
finally:
|
|
754
|
+
self._transaction_local.depth = 0
|
|
755
|
+
self._transaction_local.exclusive = False
|
|
756
|
+
fcntl.flock(descriptor, fcntl.LOCK_UN)
|
|
757
|
+
finally:
|
|
758
|
+
os.close(descriptor)
|
|
759
|
+
|
|
760
|
+
def _invoke(self, command: Sequence[str], *, cwd: Path) -> _CommandResult:
|
|
761
|
+
environment = dsh_subprocess_environment(dsh_home=self._dsh_home)
|
|
762
|
+
try:
|
|
763
|
+
return self._runner(tuple(command), cwd, environment)
|
|
764
|
+
except DshBridgeError:
|
|
765
|
+
raise
|
|
766
|
+
except Exception as error:
|
|
767
|
+
raise DshHostUnavailableError("DSH host command could not be executed") from error
|
|
768
|
+
|
|
769
|
+
def _plugin_command(self, verb: str, value: str) -> _CommandResult:
|
|
770
|
+
self._ensure_started()
|
|
771
|
+
assert self._command is not None
|
|
772
|
+
initializing = verb == "add" and not self._manifest_path().exists()
|
|
773
|
+
# The pinned pnpm hoisted linker can start fetch workers after its
|
|
774
|
+
# shutdown pass during removal. Use its supported isolated layout for
|
|
775
|
+
# newly owned profiles, without changing an existing profile's policy.
|
|
776
|
+
options = ("--config.node-linker=isolated",) if initializing else ()
|
|
777
|
+
result = self._invoke(
|
|
778
|
+
(*self._command, "plugin", "--profile", self._profile, verb, value, *options),
|
|
779
|
+
cwd=self._cwd,
|
|
780
|
+
)
|
|
781
|
+
if initializing:
|
|
782
|
+
settings_path = self._profile_root / "pnpm-workspace.yaml"
|
|
783
|
+
settings = yaml.safe_load(settings_path.read_text(encoding="utf-8"))
|
|
784
|
+
if not isinstance(settings, dict):
|
|
785
|
+
raise DshPluginMutationError("DSH initialized invalid package-manager settings")
|
|
786
|
+
settings["nodeLinker"] = "isolated"
|
|
787
|
+
# This is a newly created profile inside the install transaction;
|
|
788
|
+
# any subsequent failure removes it through the existing rollback.
|
|
789
|
+
settings_path.write_text(yaml.safe_dump(settings), encoding="utf-8")
|
|
790
|
+
settings_path.chmod(0o600)
|
|
791
|
+
return result
|
|
792
|
+
|
|
793
|
+
def _preflight(self) -> _CommandResult:
|
|
794
|
+
self._ensure_started()
|
|
795
|
+
assert self._command is not None
|
|
796
|
+
return self._invoke(
|
|
797
|
+
(*self._command, "--profile", self._profile, "--dump-config"),
|
|
798
|
+
cwd=self._cwd,
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
def _manifest_path(self) -> Path:
|
|
802
|
+
return self._profile_root / "package.json"
|
|
803
|
+
|
|
804
|
+
def _read_manifest(self) -> dict[str, object]:
|
|
805
|
+
return self._read_json(self._manifest_path(), required=True)
|
|
806
|
+
|
|
807
|
+
def _read_package(self, name: str) -> dict[str, object] | None:
|
|
808
|
+
return self._read_json(
|
|
809
|
+
self._profile_root / "node_modules" / Path(*name.split("/")) / "package.json",
|
|
810
|
+
required=False,
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
@staticmethod
|
|
814
|
+
def _read_json(path: Path, *, required: bool) -> dict[str, object] | None:
|
|
815
|
+
try:
|
|
816
|
+
if path.stat().st_size > _MAX_JSON_BYTES:
|
|
817
|
+
raise DshBridgeError("DSH manifest exceeds the supported size limit")
|
|
818
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
819
|
+
except FileNotFoundError:
|
|
820
|
+
if required:
|
|
821
|
+
raise DshBridgeError("required DSH profile manifest is unavailable") from None
|
|
822
|
+
return None
|
|
823
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as error:
|
|
824
|
+
raise DshBridgeError("DSH profile contains an unreadable JSON manifest") from error
|
|
825
|
+
if not isinstance(payload, dict):
|
|
826
|
+
raise DshBridgeError("DSH JSON manifest must be an object")
|
|
827
|
+
return payload
|
|
828
|
+
|
|
829
|
+
@staticmethod
|
|
830
|
+
def _dependencies(manifest: Mapping[str, object]) -> dict[str, str]:
|
|
831
|
+
raw = manifest.get("dependencies")
|
|
832
|
+
if raw is None:
|
|
833
|
+
return {}
|
|
834
|
+
if not isinstance(raw, dict) or any(
|
|
835
|
+
not isinstance(name, str) or not isinstance(value, str) for name, value in raw.items()
|
|
836
|
+
):
|
|
837
|
+
raise DshBridgeError("DSH profile dependencies are invalid")
|
|
838
|
+
return dict(raw)
|
|
839
|
+
|
|
840
|
+
@staticmethod
|
|
841
|
+
def _bundles(manifest: Mapping[str, object]) -> list[str]:
|
|
842
|
+
dsh = manifest.get("dsh")
|
|
843
|
+
profile = dsh.get("profile") if isinstance(dsh, dict) else None
|
|
844
|
+
bundles = profile.get("bundles") if isinstance(profile, dict) else None
|
|
845
|
+
if bundles is None:
|
|
846
|
+
return []
|
|
847
|
+
if not isinstance(bundles, list) or any(not isinstance(item, str) for item in bundles):
|
|
848
|
+
raise DshBridgeError("DSH profile bundle order is invalid")
|
|
849
|
+
return list(bundles)
|
|
850
|
+
|
|
851
|
+
@staticmethod
|
|
852
|
+
def _bundle_patch(package: Mapping[str, object]) -> str | None:
|
|
853
|
+
dsh = package.get("dsh")
|
|
854
|
+
bundle = dsh.get("bundle") if isinstance(dsh, dict) else None
|
|
855
|
+
patch = bundle.get("patch") if isinstance(bundle, dict) else None
|
|
856
|
+
return patch.strip() if isinstance(patch, str) and patch.strip() else None
|
|
857
|
+
|
|
858
|
+
def _require_bundle(self, name: str) -> None:
|
|
859
|
+
package = self._read_package(name)
|
|
860
|
+
if package is None:
|
|
861
|
+
raise DshPluginMutationError(f"{name} does not declare dsh.bundle.patch")
|
|
862
|
+
patch = self._bundle_patch(package)
|
|
863
|
+
root = (self._profile_root / "node_modules" / Path(*name.split("/"))).resolve()
|
|
864
|
+
if patch is None:
|
|
865
|
+
# Real upstream plugins may ship without a dsh.bundle.patch yet
|
|
866
|
+
# still be valid Cordis tool plugins (inject: ['tools']). Generate
|
|
867
|
+
# a minimal bundle patch so the dsh profile loader activates them.
|
|
868
|
+
# This is a compatibility shim, not a source modification: the
|
|
869
|
+
# plugin's own code is untouched.
|
|
870
|
+
patch_path = root / "cordis.patch.yml"
|
|
871
|
+
patch_path.write_text(
|
|
872
|
+
"- insert:\n - id: "
|
|
873
|
+
+ name.rsplit("/", 1)[-1].replace("-", "_")
|
|
874
|
+
+ "\n name: '" + name + "'\n",
|
|
875
|
+
encoding="utf-8",
|
|
876
|
+
)
|
|
877
|
+
package.setdefault("dsh", {}).setdefault("bundle", {})["patch"] = "./cordis.patch.yml"
|
|
878
|
+
self._write_json(root / "package.json", package)
|
|
879
|
+
patch = "./cordis.patch.yml"
|
|
880
|
+
relative = Path(patch)
|
|
881
|
+
if relative.is_absolute() or ".." in relative.parts:
|
|
882
|
+
raise DshPluginMutationError(f"{name} declares an unsafe bundle patch path")
|
|
883
|
+
target = (root / relative).resolve()
|
|
884
|
+
if not target.is_relative_to(root) or not target.is_file():
|
|
885
|
+
raise DshPluginMutationError(f"{name} bundle patch is unavailable")
|
|
886
|
+
|
|
887
|
+
def _read_state(self, manifest: Mapping[str, object]) -> _ProfileState:
|
|
888
|
+
dependencies = set(self._dependencies(manifest))
|
|
889
|
+
bundle_dependencies = [
|
|
890
|
+
name
|
|
891
|
+
for name in dependencies
|
|
892
|
+
if self._bundle_patch(self._read_package(name) or {}) is not None
|
|
893
|
+
]
|
|
894
|
+
active = [name for name in self._bundles(manifest) if name in dependencies]
|
|
895
|
+
stored = self._read_json(self._profile_root / _STATE_FILE, required=False) or {}
|
|
896
|
+
order_raw = stored.get("order")
|
|
897
|
+
disabled_raw = stored.get("disabled")
|
|
898
|
+
sources_raw = stored.get("sources")
|
|
899
|
+
order = (
|
|
900
|
+
[item for item in order_raw if isinstance(item, str) and item in bundle_dependencies]
|
|
901
|
+
if isinstance(order_raw, list)
|
|
902
|
+
else []
|
|
903
|
+
)
|
|
904
|
+
for name in [*active, *bundle_dependencies]:
|
|
905
|
+
if name not in order:
|
|
906
|
+
order.append(name)
|
|
907
|
+
disabled = (
|
|
908
|
+
[item for item in disabled_raw if isinstance(item, str) and item in bundle_dependencies]
|
|
909
|
+
if isinstance(disabled_raw, list)
|
|
910
|
+
else [name for name in bundle_dependencies if name not in active]
|
|
911
|
+
)
|
|
912
|
+
sources: dict[str, _SourceReceipt] = {}
|
|
913
|
+
if isinstance(sources_raw, dict):
|
|
914
|
+
for name, raw in sources_raw.items():
|
|
915
|
+
if name not in bundle_dependencies:
|
|
916
|
+
continue
|
|
917
|
+
if not isinstance(raw, dict):
|
|
918
|
+
raise DshPluginMutationError("DSH local source receipt is invalid")
|
|
919
|
+
digest = raw.get("digest")
|
|
920
|
+
kind = raw.get("kind")
|
|
921
|
+
artifact = raw.get("artifact")
|
|
922
|
+
dependency_spec = raw.get("dependencySpec") or raw.get("dependency_spec")
|
|
923
|
+
if (
|
|
924
|
+
isinstance(digest, str)
|
|
925
|
+
and re.fullmatch(r"sha256:[0-9a-f]{64}", digest)
|
|
926
|
+
and kind in {"directory", "tgz"}
|
|
927
|
+
and isinstance(artifact, str)
|
|
928
|
+
and isinstance(dependency_spec, str)
|
|
929
|
+
):
|
|
930
|
+
sources[name] = {
|
|
931
|
+
"digest": digest,
|
|
932
|
+
"kind": kind,
|
|
933
|
+
"artifact": artifact,
|
|
934
|
+
"dependency_spec": dependency_spec,
|
|
935
|
+
}
|
|
936
|
+
else:
|
|
937
|
+
raise DshPluginMutationError("DSH local source receipt is invalid")
|
|
938
|
+
return {"order": order, "disabled": disabled, "sources": sources}
|
|
939
|
+
|
|
940
|
+
def _write_state(self, state: _ProfileState) -> None:
|
|
941
|
+
self._write_json(
|
|
942
|
+
self._profile_root / _STATE_FILE,
|
|
943
|
+
{
|
|
944
|
+
"version": 2,
|
|
945
|
+
"order": list(state["order"]),
|
|
946
|
+
"disabled": list(state["disabled"]),
|
|
947
|
+
"sources": {
|
|
948
|
+
name: {
|
|
949
|
+
"digest": receipt["digest"],
|
|
950
|
+
"kind": receipt["kind"],
|
|
951
|
+
"artifact": receipt["artifact"],
|
|
952
|
+
"dependencySpec": receipt["dependency_spec"],
|
|
953
|
+
}
|
|
954
|
+
for name, receipt in sorted(state["sources"].items())
|
|
955
|
+
},
|
|
956
|
+
},
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
def _write_active_bundles(
|
|
960
|
+
self,
|
|
961
|
+
manifest: dict[str, object],
|
|
962
|
+
state: _ProfileState,
|
|
963
|
+
) -> None:
|
|
964
|
+
dependencies = set(self._dependencies(manifest))
|
|
965
|
+
existing = self._bundles(manifest)
|
|
966
|
+
builtins = [name for name in existing if name not in dependencies]
|
|
967
|
+
disabled = set(state["disabled"])
|
|
968
|
+
enabled = [name for name in state["order"] if name in dependencies and name not in disabled]
|
|
969
|
+
dsh = manifest.get("dsh") if isinstance(manifest.get("dsh"), dict) else {}
|
|
970
|
+
assert isinstance(dsh, dict)
|
|
971
|
+
profile = dsh.get("profile") if isinstance(dsh.get("profile"), dict) else {}
|
|
972
|
+
assert isinstance(profile, dict)
|
|
973
|
+
profile["bundles"] = [*builtins, *enabled]
|
|
974
|
+
dsh["profile"] = profile
|
|
975
|
+
manifest["dsh"] = dsh
|
|
976
|
+
self._write_json(self._manifest_path(), manifest)
|
|
977
|
+
|
|
978
|
+
def _resolve_install_source(self, source: str) -> str:
|
|
979
|
+
if not _PACKAGE_NAME.fullmatch(source):
|
|
980
|
+
return source
|
|
981
|
+
# Resolve metadata before running package scripts. The mutable latest
|
|
982
|
+
# selector never reaches `dsh plugin add`; the exact version does.
|
|
983
|
+
result = self._invoke(
|
|
984
|
+
("npm", "view", f"{source}@latest", "version", "--json"), cwd=self._cwd
|
|
985
|
+
)
|
|
986
|
+
try:
|
|
987
|
+
if len(result.stdout) > _MAX_JSON_BYTES:
|
|
988
|
+
raise ValueError("oversized registry response")
|
|
989
|
+
version = json.loads(result.stdout)
|
|
990
|
+
if not isinstance(version, str):
|
|
991
|
+
raise ValueError("expected one version")
|
|
992
|
+
return validate_dsh_registry_source(f"{source}@{version}")
|
|
993
|
+
except (ValueError, TypeError) as error:
|
|
994
|
+
raise DshPluginMutationError(
|
|
995
|
+
"npm latest did not resolve to one exact version"
|
|
996
|
+
) from error
|
|
997
|
+
|
|
998
|
+
def _prepare_source(self, source: str) -> _PreparedSource:
|
|
999
|
+
value = source.strip()
|
|
1000
|
+
candidate = Path(value).expanduser()
|
|
1001
|
+
if not candidate.is_absolute() or not candidate.exists():
|
|
1002
|
+
return _PreparedSource(value, "", "tgz", "")
|
|
1003
|
+
local = candidate.resolve()
|
|
1004
|
+
with tempfile.TemporaryDirectory(prefix="agentengine-dsh-source-") as directory:
|
|
1005
|
+
if local.is_dir():
|
|
1006
|
+
# Import lazily: dsh_toolchain owns the pinned pnpm workflow and
|
|
1007
|
+
# imports this bridge for validation.
|
|
1008
|
+
from ksadk.plugins.dsh_toolchain import ( # noqa: PLC0415
|
|
1009
|
+
DshPluginDeveloper,
|
|
1010
|
+
DshToolchainManager,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
output = Path(directory)
|
|
1014
|
+
packed = DshPluginDeveloper(toolchain=DshToolchainManager()).pack(
|
|
1015
|
+
local, output_dir=output
|
|
1016
|
+
)
|
|
1017
|
+
archive = Path(packed.artifact)
|
|
1018
|
+
kind: Literal["directory", "tgz"] = "directory"
|
|
1019
|
+
elif local.is_file() and local.name.endswith(".tgz"):
|
|
1020
|
+
archive = local
|
|
1021
|
+
kind = "tgz"
|
|
1022
|
+
else:
|
|
1023
|
+
raise DshPluginMutationError(
|
|
1024
|
+
"local DSH plugin source must be a directory or .tgz archive"
|
|
1025
|
+
)
|
|
1026
|
+
try:
|
|
1027
|
+
content = archive.read_bytes()
|
|
1028
|
+
except OSError as error:
|
|
1029
|
+
raise DshPluginMutationError("local DSH plugin source is unreadable") from error
|
|
1030
|
+
digest_hex = hashlib.sha256(content).hexdigest()
|
|
1031
|
+
digest = f"sha256:{digest_hex}"
|
|
1032
|
+
root = (self._dsh_home / _IMMUTABLE_SOURCE_DIR / digest_hex).resolve()
|
|
1033
|
+
expected_parent = (self._dsh_home / _IMMUTABLE_SOURCE_DIR).resolve()
|
|
1034
|
+
if root.parent != expected_parent:
|
|
1035
|
+
raise DshPluginMutationError("immutable DSH source path escaped its store")
|
|
1036
|
+
target = root / "package.tgz"
|
|
1037
|
+
root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
1038
|
+
if target.exists():
|
|
1039
|
+
if hashlib.sha256(target.read_bytes()).hexdigest() != digest_hex:
|
|
1040
|
+
raise DshPluginMutationError(
|
|
1041
|
+
"immutable DSH source store contains a digest collision"
|
|
1042
|
+
)
|
|
1043
|
+
else:
|
|
1044
|
+
temporary = root / f".package.{os.getpid()}.tmp"
|
|
1045
|
+
try:
|
|
1046
|
+
temporary.write_bytes(content)
|
|
1047
|
+
temporary.chmod(0o400)
|
|
1048
|
+
os.replace(temporary, target)
|
|
1049
|
+
finally:
|
|
1050
|
+
temporary.unlink(missing_ok=True)
|
|
1051
|
+
target.chmod(0o400)
|
|
1052
|
+
relative = target.relative_to(self._dsh_home).as_posix()
|
|
1053
|
+
return _PreparedSource(str(target), digest, kind, relative)
|
|
1054
|
+
|
|
1055
|
+
def _verify_source_receipts(
|
|
1056
|
+
self,
|
|
1057
|
+
manifest: Mapping[str, object],
|
|
1058
|
+
state: _ProfileState,
|
|
1059
|
+
*,
|
|
1060
|
+
names: Sequence[str] | None = None,
|
|
1061
|
+
) -> None:
|
|
1062
|
+
dependencies = self._dependencies(manifest)
|
|
1063
|
+
for name, spec in dependencies.items():
|
|
1064
|
+
if _IMMUTABLE_SOURCE_DIR in spec and name not in state["sources"]:
|
|
1065
|
+
raise DshPluginMutationError(
|
|
1066
|
+
f"DSH plugin {name!r} is missing its immutable source receipt"
|
|
1067
|
+
)
|
|
1068
|
+
selected = names if names is not None else tuple(state["sources"])
|
|
1069
|
+
store = (self._dsh_home / _IMMUTABLE_SOURCE_DIR).resolve()
|
|
1070
|
+
for name in selected:
|
|
1071
|
+
receipt = state["sources"].get(name)
|
|
1072
|
+
if receipt is None:
|
|
1073
|
+
continue
|
|
1074
|
+
if dependencies.get(name) != receipt["dependency_spec"]:
|
|
1075
|
+
raise DshPluginMutationError(
|
|
1076
|
+
f"DSH plugin {name!r} dependency no longer matches its source receipt"
|
|
1077
|
+
)
|
|
1078
|
+
artifact = (self._dsh_home / receipt["artifact"]).resolve()
|
|
1079
|
+
if not artifact.is_relative_to(store) or not artifact.is_file():
|
|
1080
|
+
raise DshPluginMutationError(f"DSH plugin {name!r} immutable source is unavailable")
|
|
1081
|
+
try:
|
|
1082
|
+
actual = f"sha256:{hashlib.sha256(artifact.read_bytes()).hexdigest()}"
|
|
1083
|
+
except OSError as error:
|
|
1084
|
+
raise DshPluginMutationError(
|
|
1085
|
+
f"DSH plugin {name!r} immutable source is unreadable"
|
|
1086
|
+
) from error
|
|
1087
|
+
if actual != receipt["digest"]:
|
|
1088
|
+
raise DshPluginMutationError(f"DSH plugin {name!r} immutable source digest changed")
|
|
1089
|
+
|
|
1090
|
+
@staticmethod
|
|
1091
|
+
def _write_json(path: Path, payload: object) -> None:
|
|
1092
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
1093
|
+
descriptor, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
1094
|
+
try:
|
|
1095
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
1096
|
+
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
1097
|
+
handle.write("\n")
|
|
1098
|
+
handle.flush()
|
|
1099
|
+
os.fsync(handle.fileno())
|
|
1100
|
+
os.chmod(temporary, 0o600)
|
|
1101
|
+
os.replace(temporary, path)
|
|
1102
|
+
finally:
|
|
1103
|
+
try:
|
|
1104
|
+
os.unlink(temporary)
|
|
1105
|
+
except FileNotFoundError:
|
|
1106
|
+
pass
|
|
1107
|
+
|
|
1108
|
+
def _snapshot(self) -> _ProfileSnapshot:
|
|
1109
|
+
files: dict[str, bytes | None] = {}
|
|
1110
|
+
for name in _SNAPSHOT_FILES:
|
|
1111
|
+
path = self._profile_root / name
|
|
1112
|
+
try:
|
|
1113
|
+
files[name] = path.read_bytes()
|
|
1114
|
+
except FileNotFoundError:
|
|
1115
|
+
files[name] = None
|
|
1116
|
+
return _ProfileSnapshot(
|
|
1117
|
+
files=files,
|
|
1118
|
+
node_modules_existed=(self._profile_root / "node_modules").exists(),
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
def _require_package_mutation_rollback(self, *, new_profile_allowed: bool) -> None:
|
|
1122
|
+
"""Reject package mutations unless their filesystem effects are reversible."""
|
|
1123
|
+
|
|
1124
|
+
if not self._profile_root.exists():
|
|
1125
|
+
if new_profile_allowed:
|
|
1126
|
+
return
|
|
1127
|
+
raise DshPluginNotFoundError(f"DSH profile {self._profile!r} is not initialized")
|
|
1128
|
+
if not self._manifest_path().is_file():
|
|
1129
|
+
raise DshPluginMutationError(
|
|
1130
|
+
"existing DSH profile directory has no package manifest; refusing to mutate it"
|
|
1131
|
+
)
|
|
1132
|
+
if not (self._profile_root / "pnpm-lock.yaml").is_file():
|
|
1133
|
+
if new_profile_allowed and self._is_pristine_official_web_profile():
|
|
1134
|
+
return
|
|
1135
|
+
raise DshPluginMutationError(
|
|
1136
|
+
"existing DSH profile has no pnpm lockfile, so package rollback is unavailable"
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
def _is_pristine_official_web_profile(self) -> bool:
|
|
1140
|
+
"""Recognize only the untouched profile generated by ``dsh --profile web``."""
|
|
1141
|
+
|
|
1142
|
+
if self._profile != "web" or (self._profile_root / "node_modules").exists():
|
|
1143
|
+
return False
|
|
1144
|
+
if (self._profile_root / _STATE_FILE).exists():
|
|
1145
|
+
return False
|
|
1146
|
+
manifest = self._read_manifest()
|
|
1147
|
+
return (
|
|
1148
|
+
self._dependencies(manifest) == {}
|
|
1149
|
+
and tuple(self._bundles(manifest)) == _WEB_PROFILE_BUILTINS
|
|
1150
|
+
and (self._profile_root / "pnpm-workspace.yaml").is_file()
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
def _rollback(
|
|
1154
|
+
self, snapshot: _ProfileSnapshot, original: BaseException, *, reinstall: bool = True,
|
|
1155
|
+
) -> None:
|
|
1156
|
+
try:
|
|
1157
|
+
if all(content is None for content in snapshot.files.values()):
|
|
1158
|
+
profiles_root = (self._dsh_home / "profiles").resolve()
|
|
1159
|
+
profile_root = self._profile_root.resolve()
|
|
1160
|
+
if profile_root.parent != profiles_root:
|
|
1161
|
+
raise DshPluginMutationError("refusing to clean an untrusted DSH profile path")
|
|
1162
|
+
if profile_root.exists():
|
|
1163
|
+
shutil.rmtree(profile_root, ignore_errors=False)
|
|
1164
|
+
return
|
|
1165
|
+
node_modules = self._profile_root / "node_modules"
|
|
1166
|
+
if not snapshot.node_modules_existed and node_modules.exists():
|
|
1167
|
+
profile_root = self._profile_root.resolve()
|
|
1168
|
+
if node_modules.is_symlink() or node_modules.resolve().parent != profile_root:
|
|
1169
|
+
raise DshPluginMutationError(
|
|
1170
|
+
"refusing to clean an untrusted DSH node_modules path"
|
|
1171
|
+
)
|
|
1172
|
+
shutil.rmtree(node_modules, ignore_errors=False)
|
|
1173
|
+
self._profile_root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
1174
|
+
for name, content in snapshot.files.items():
|
|
1175
|
+
path = self._profile_root / name
|
|
1176
|
+
if content is None:
|
|
1177
|
+
path.unlink(missing_ok=True)
|
|
1178
|
+
else:
|
|
1179
|
+
path.write_bytes(content)
|
|
1180
|
+
path.chmod(0o600)
|
|
1181
|
+
if (
|
|
1182
|
+
reinstall and snapshot.files.get("pnpm-lock.yaml") is not None
|
|
1183
|
+
and self._manifest_path().is_file()
|
|
1184
|
+
):
|
|
1185
|
+
self._plugin_command("install", "--frozen-lockfile")
|
|
1186
|
+
for name in ("package.json", _STATE_FILE):
|
|
1187
|
+
content = snapshot.files.get(name)
|
|
1188
|
+
path = self._profile_root / name
|
|
1189
|
+
if content is None:
|
|
1190
|
+
path.unlink(missing_ok=True)
|
|
1191
|
+
else:
|
|
1192
|
+
path.write_bytes(content)
|
|
1193
|
+
path.chmod(0o600)
|
|
1194
|
+
except BaseException as rollback_error:
|
|
1195
|
+
raise DshPluginMutationError(
|
|
1196
|
+
"DSH plugin mutation failed and profile rollback also failed"
|
|
1197
|
+
) from rollback_error
|
|
1198
|
+
if isinstance(original, DshBridgeError):
|
|
1199
|
+
return
|
|
1200
|
+
|
|
1201
|
+
@staticmethod
|
|
1202
|
+
def _validate_source(source: str) -> None:
|
|
1203
|
+
value = source.strip()
|
|
1204
|
+
if (
|
|
1205
|
+
not value
|
|
1206
|
+
or len(value) > 2048
|
|
1207
|
+
or value.startswith("-")
|
|
1208
|
+
or any(character in source for character in ("\r", "\n", "\0"))
|
|
1209
|
+
or value in {".", ".."}
|
|
1210
|
+
or value.startswith(("file:", "link:"))
|
|
1211
|
+
or value.startswith(("./", "../", "file:./", "file:../", "link:./", "link:../"))
|
|
1212
|
+
):
|
|
1213
|
+
raise ValueError(
|
|
1214
|
+
"DSH plugin source must be an exact registry version or absolute local path"
|
|
1215
|
+
)
|
|
1216
|
+
candidate = Path(value).expanduser()
|
|
1217
|
+
if candidate.is_absolute():
|
|
1218
|
+
if not candidate.exists():
|
|
1219
|
+
raise ValueError("absolute local DSH plugin source does not exist")
|
|
1220
|
+
return
|
|
1221
|
+
# Bare names resolve latest to an exact version before package mutation.
|
|
1222
|
+
# Git, URL, explicit tags and ranges remain unsupported. Local sources
|
|
1223
|
+
# are packed and content-addressed by _prepare_source.
|
|
1224
|
+
validate_dsh_registry_request(value)
|
|
1225
|
+
|
|
1226
|
+
@staticmethod
|
|
1227
|
+
def _validate_package_name(name: str) -> None:
|
|
1228
|
+
if not _PACKAGE_NAME.fullmatch(name):
|
|
1229
|
+
raise ValueError("invalid DSH plugin package name")
|
|
1230
|
+
|
|
1231
|
+
@staticmethod
|
|
1232
|
+
def _string(value: object) -> str:
|
|
1233
|
+
return value if isinstance(value, str) else ""
|
|
1234
|
+
|
|
1235
|
+
@staticmethod
|
|
1236
|
+
def _run_command(
|
|
1237
|
+
command: Sequence[str],
|
|
1238
|
+
cwd: Path,
|
|
1239
|
+
environment: Mapping[str, str],
|
|
1240
|
+
) -> _CommandResult:
|
|
1241
|
+
try:
|
|
1242
|
+
process = subprocess.Popen(
|
|
1243
|
+
list(command),
|
|
1244
|
+
cwd=cwd,
|
|
1245
|
+
env=dict(environment),
|
|
1246
|
+
stdout=subprocess.PIPE,
|
|
1247
|
+
stderr=subprocess.PIPE,
|
|
1248
|
+
text=True,
|
|
1249
|
+
start_new_session=os.name == "posix",
|
|
1250
|
+
)
|
|
1251
|
+
try:
|
|
1252
|
+
stdout, stderr = process.communicate(timeout=_COMMAND_TIMEOUT_SECONDS)
|
|
1253
|
+
except BaseException:
|
|
1254
|
+
# DSH synchronously waits on pnpm. Stop both before profile
|
|
1255
|
+
# rollback, including when a child still owns the output pipes.
|
|
1256
|
+
if os.name == "posix":
|
|
1257
|
+
try:
|
|
1258
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
1259
|
+
except ProcessLookupError:
|
|
1260
|
+
pass
|
|
1261
|
+
else: # pragma: no cover - supported production hosts are Unix
|
|
1262
|
+
process.kill()
|
|
1263
|
+
process.communicate()
|
|
1264
|
+
raise
|
|
1265
|
+
completed = subprocess.CompletedProcess(command, process.returncode, stdout, stderr)
|
|
1266
|
+
except (OSError, subprocess.TimeoutExpired) as error:
|
|
1267
|
+
raise DshHostUnavailableError("DSH host command did not complete") from error
|
|
1268
|
+
if completed.returncode != 0:
|
|
1269
|
+
diagnostic = _redact_diagnostic(completed.stderr or completed.stdout)
|
|
1270
|
+
raise DshPluginMutationError(
|
|
1271
|
+
f"DSH host command failed with exit code {completed.returncode}"
|
|
1272
|
+
+ (f": {diagnostic[-2000:]}" if diagnostic else "")
|
|
1273
|
+
)
|
|
1274
|
+
return _CommandResult(stdout=completed.stdout, stderr=completed.stderr)
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
def _redact_diagnostic(value: str) -> str:
|
|
1278
|
+
value = re.sub(r"(https?://)[^/\s:@]+:[^/\s@]+@", r"\1[redacted]@", value, flags=re.I)
|
|
1279
|
+
return re.sub(
|
|
1280
|
+
r"((?:token|password|authorization|_authToken)\s*[:=]\s*)[^\s]+",
|
|
1281
|
+
r"\1[redacted]",
|
|
1282
|
+
value,
|
|
1283
|
+
flags=re.I,
|
|
1284
|
+
).strip()
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
__all__ = [
|
|
1288
|
+
"DshBridgeError",
|
|
1289
|
+
"DshBridgeHost",
|
|
1290
|
+
"DshHostUnavailableError",
|
|
1291
|
+
"DshPluginApprovalRequired",
|
|
1292
|
+
"DshPluginInventory",
|
|
1293
|
+
"DshPluginMutationError",
|
|
1294
|
+
"DshPluginNotFoundError",
|
|
1295
|
+
"DshProfilePluginBridge",
|
|
1296
|
+
"DshProfileProjection",
|
|
1297
|
+
"DshProfileRecoveryError",
|
|
1298
|
+
"dsh_subprocess_environment",
|
|
1299
|
+
"validate_dsh_registry_source",
|
|
1300
|
+
]
|