agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__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.3.dist-info}/METADATA +18 -25
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +3 -0
- ksadk/builders/managed_runtime_builder.py +3 -0
- ksadk/cli/__init__.py +6 -0
- ksadk/cli/cmd_plugin.py +657 -0
- ksadk/codex/runtime.py +50 -8
- ksadk/configs/env_registry.py +31 -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 +19 -1
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/codex.py +190 -0
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/runtime.py +60 -1
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +21 -0
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +1 -0
- ksadk/kernel/worker.py +11 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -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-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 +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -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/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
- ksadk/runtime/adapter.py +1 -0
- ksadk/runtime/factory.py +76 -2
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/runner_adapter.py +5 -0
- ksadk/sandbox/__init__.py +2 -1
- 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 +563 -0
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/skills/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +513 -71
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +12 -2
- ksadk/studio/builder.py +123 -43
- ksadk/studio/cloud.py +4 -0
- ksadk/studio/codex_agent_service.py +12 -1
- ksadk/studio/codex_builder.py +54 -15
- ksadk/studio/codex_manifest.py +21 -1
- ksadk/studio/codex_run.py +16 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +6 -1
- ksadk/studio/contracts.py +123 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/resource_catalog.py +6 -1
- ksadk/studio/run_service.py +713 -75
- ksadk/studio/runtime_source.py +9 -1
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +672 -82
- ksadk/studio/shared_web.py +167 -28
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -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
- 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/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.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
ksadk/studio/runtime_source.py
CHANGED
|
@@ -30,7 +30,15 @@ def materialize_generated_runtime_source(
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
runtime = draft.spec.runtime
|
|
33
|
-
|
|
33
|
+
# Only the two legacy Python-framework runtimes own generated source.
|
|
34
|
+
# Harness and externally installed AgentProviders are bundle compositions;
|
|
35
|
+
# treating either as the ``else`` branch here would silently generate a
|
|
36
|
+
# LangGraph project and change the selected provider's execution model.
|
|
37
|
+
if (
|
|
38
|
+
runtime is None
|
|
39
|
+
or runtime.type not in {"adk", "langgraph"}
|
|
40
|
+
or not runtime.project_path
|
|
41
|
+
):
|
|
34
42
|
return
|
|
35
43
|
root = workspace.resolve(runtime.project_path)
|
|
36
44
|
marker = root / _GENERATED_MARKER
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""Build-pinned AgentKernel runtimes owned by the local Studio scheduler.
|
|
2
|
+
|
|
3
|
+
The ordinary Studio conversation path may keep using its historical direct
|
|
4
|
+
RuntimeExecutor or an independently enabled HTTP Kernel. Scheduler Lite owns
|
|
5
|
+
neither of those lifecycles. Instead it lazily starts one in-process Kernel
|
|
6
|
+
runtime for each immutable Build referenced by a local scheduled task.
|
|
7
|
+
|
|
8
|
+
This is intentionally a registry rather than another process-global Kernel:
|
|
9
|
+
one AgentKernelWorker has one concrete RuntimeAdapter factory, so reusing a
|
|
10
|
+
single global worker for multiple Studio Builds can execute the wrong Agent.
|
|
11
|
+
The registry keeps ``Build -> instance -> Runtime`` exact and dispatches every
|
|
12
|
+
occurrence through that runtime's AgentControl Inbox and canonical
|
|
13
|
+
SessionEvent log.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import hashlib
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from uuid import uuid4
|
|
23
|
+
|
|
24
|
+
from ksadk.kernel.bootstrap import (
|
|
25
|
+
AgentKernelRuntime,
|
|
26
|
+
AgentKernelRuntimeConfig,
|
|
27
|
+
build_agent_kernel_runtime,
|
|
28
|
+
)
|
|
29
|
+
from ksadk.kernel.contracts import (
|
|
30
|
+
AgentControlCommand,
|
|
31
|
+
AgentControlPermit,
|
|
32
|
+
AgentControlReceipt,
|
|
33
|
+
SessionEventSubscription,
|
|
34
|
+
)
|
|
35
|
+
from ksadk.kernel.ingress import trusted_context
|
|
36
|
+
from ksadk.runtime import RuntimeAdapter
|
|
37
|
+
from ksadk.scheduler.contracts import ScheduledTaskTarget, ScheduleOccurrence
|
|
38
|
+
from ksadk.sessions.base import BaseSessionService
|
|
39
|
+
from ksadk.studio.run_service import StudioRunSpec
|
|
40
|
+
|
|
41
|
+
ResolveBuild = Callable[[str], StudioRunSpec]
|
|
42
|
+
ResolveAdapterProvider = Callable[
|
|
43
|
+
[StudioRunSpec], Callable[[], RuntimeAdapter]
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class StudioSchedulerRuntimeError(RuntimeError):
|
|
48
|
+
"""Stable local scheduling failure safe to persist on an occurrence."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, code: str, message: str) -> None:
|
|
51
|
+
super().__init__(message)
|
|
52
|
+
self.code = code
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class StudioScheduledRuntimeTarget:
|
|
57
|
+
"""Server-owned immutable routing facts persisted on ScheduledTask/v1."""
|
|
58
|
+
|
|
59
|
+
build_id: str
|
|
60
|
+
agent_id: str
|
|
61
|
+
tenant_id: str
|
|
62
|
+
agent_instance_id: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class _RuntimeEntry:
|
|
67
|
+
target: StudioScheduledRuntimeTarget
|
|
68
|
+
spec: StudioRunSpec
|
|
69
|
+
runtime: AgentKernelRuntime
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class StudioScheduledKernelRegistry:
|
|
73
|
+
"""Own exact per-Build Kernel runtimes for one Studio process.
|
|
74
|
+
|
|
75
|
+
The registry does not register anything in ``ksadk.kernel.ingress`` and
|
|
76
|
+
therefore cannot redirect normal conversations or hosted HTTP traffic.
|
|
77
|
+
Scheduler dispatch uses :meth:`submit` and :meth:`read_events` directly,
|
|
78
|
+
while still crossing the frozen AgentControl permit/admission boundary.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
resolve_build: ResolveBuild,
|
|
85
|
+
resolve_adapter_provider: ResolveAdapterProvider,
|
|
86
|
+
session_service: BaseSessionService,
|
|
87
|
+
runtime_executor: object | None = None,
|
|
88
|
+
tenant_id: str = "local-studio",
|
|
89
|
+
workspace_id: str = "studio-scheduler",
|
|
90
|
+
poll_interval: float = 0.05,
|
|
91
|
+
lease_ttl_seconds: float = 30.0,
|
|
92
|
+
) -> None:
|
|
93
|
+
self._resolve_build = resolve_build
|
|
94
|
+
self._resolve_adapter_provider = resolve_adapter_provider
|
|
95
|
+
self._session_service = session_service
|
|
96
|
+
self._runtime_executor = runtime_executor
|
|
97
|
+
self._tenant_id = tenant_id
|
|
98
|
+
self._workspace_id = workspace_id
|
|
99
|
+
self._poll_interval = poll_interval
|
|
100
|
+
self._lease_ttl_seconds = lease_ttl_seconds
|
|
101
|
+
self._owner_id = uuid4().hex
|
|
102
|
+
self._entries_by_build: dict[str, _RuntimeEntry] = {}
|
|
103
|
+
self._build_by_instance: dict[str, str] = {}
|
|
104
|
+
self._lock = asyncio.Lock()
|
|
105
|
+
self._started = False
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def started(self) -> bool:
|
|
109
|
+
return self._started
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def active_runtime_count(self) -> int:
|
|
113
|
+
return len(self._entries_by_build)
|
|
114
|
+
|
|
115
|
+
async def start(self) -> None:
|
|
116
|
+
self._started = True
|
|
117
|
+
|
|
118
|
+
async def ensure_build(
|
|
119
|
+
self,
|
|
120
|
+
build_id: str,
|
|
121
|
+
*,
|
|
122
|
+
expected_agent_id: str | None = None,
|
|
123
|
+
) -> StudioScheduledRuntimeTarget:
|
|
124
|
+
"""Start or return the exact Runtime owned by one immutable Build."""
|
|
125
|
+
|
|
126
|
+
normalized = str(build_id).strip()
|
|
127
|
+
if not normalized:
|
|
128
|
+
raise StudioSchedulerRuntimeError(
|
|
129
|
+
"SCHEDULER_BUILD_REQUIRED",
|
|
130
|
+
"定时任务缺少不可变 Build 标识",
|
|
131
|
+
)
|
|
132
|
+
if not self._started:
|
|
133
|
+
raise StudioSchedulerRuntimeError(
|
|
134
|
+
"SCHEDULER_RUNTIME_NOT_STARTED",
|
|
135
|
+
"Studio Scheduler Runtime 尚未启动",
|
|
136
|
+
)
|
|
137
|
+
existing = self._entries_by_build.get(normalized)
|
|
138
|
+
if existing is not None:
|
|
139
|
+
self._require_agent(existing.spec, expected_agent_id)
|
|
140
|
+
return existing.target
|
|
141
|
+
|
|
142
|
+
async with self._lock:
|
|
143
|
+
existing = self._entries_by_build.get(normalized)
|
|
144
|
+
if existing is not None:
|
|
145
|
+
self._require_agent(existing.spec, expected_agent_id)
|
|
146
|
+
return existing.target
|
|
147
|
+
try:
|
|
148
|
+
spec = self._resolve_build(normalized)
|
|
149
|
+
except Exception as error:
|
|
150
|
+
raise StudioSchedulerRuntimeError(
|
|
151
|
+
"SCHEDULER_BUILD_UNAVAILABLE",
|
|
152
|
+
f"定时任务绑定的 Build {normalized!r} 不可用",
|
|
153
|
+
) from error
|
|
154
|
+
if spec.build_id != normalized:
|
|
155
|
+
raise StudioSchedulerRuntimeError(
|
|
156
|
+
"SCHEDULER_BUILD_MISMATCH",
|
|
157
|
+
"Build 解析结果与定时任务绑定不一致",
|
|
158
|
+
)
|
|
159
|
+
self._require_agent(spec, expected_agent_id)
|
|
160
|
+
try:
|
|
161
|
+
adapter_provider = self._resolve_adapter_provider(spec)
|
|
162
|
+
except Exception as error:
|
|
163
|
+
raise StudioSchedulerRuntimeError(
|
|
164
|
+
"SCHEDULER_PROVIDER_UNAVAILABLE",
|
|
165
|
+
f"Build {normalized!r} 的 AgentProvider 不可用",
|
|
166
|
+
) from error
|
|
167
|
+
|
|
168
|
+
# ``build_agent_kernel_runtime`` asks the provider once to snapshot
|
|
169
|
+
# capabilities. Retain that exact adapter for the first worker or
|
|
170
|
+
# recovery acquisition instead of constructing and discarding a
|
|
171
|
+
# resource-owning Codex/Harness adapter during registration.
|
|
172
|
+
retained_first_adapter: RuntimeAdapter | None = None
|
|
173
|
+
first_adapter_borrowed = False
|
|
174
|
+
|
|
175
|
+
def checked_adapter_provider() -> RuntimeAdapter:
|
|
176
|
+
nonlocal retained_first_adapter, first_adapter_borrowed
|
|
177
|
+
if retained_first_adapter is not None and not first_adapter_borrowed:
|
|
178
|
+
first_adapter_borrowed = True
|
|
179
|
+
adapter = retained_first_adapter
|
|
180
|
+
retained_first_adapter = None
|
|
181
|
+
return adapter
|
|
182
|
+
adapter = adapter_provider()
|
|
183
|
+
if not isinstance(adapter, RuntimeAdapter):
|
|
184
|
+
raise StudioSchedulerRuntimeError(
|
|
185
|
+
"SCHEDULER_PROVIDER_INVALID",
|
|
186
|
+
"AgentProvider 没有返回 RuntimeAdapter",
|
|
187
|
+
)
|
|
188
|
+
if retained_first_adapter is None and not first_adapter_borrowed:
|
|
189
|
+
retained_first_adapter = adapter
|
|
190
|
+
return adapter
|
|
191
|
+
|
|
192
|
+
instance_id = self._instance_id(normalized)
|
|
193
|
+
config = AgentKernelRuntimeConfig(
|
|
194
|
+
agent_instance_id=instance_id,
|
|
195
|
+
authority_mode="local",
|
|
196
|
+
driver="memory",
|
|
197
|
+
durability_tier="ephemeral",
|
|
198
|
+
adapter_provider=checked_adapter_provider,
|
|
199
|
+
session_service=self._session_service,
|
|
200
|
+
runtime_executor=self._runtime_executor,
|
|
201
|
+
launch_context=spec.launch_context,
|
|
202
|
+
start_request_defaults=self._start_defaults(spec),
|
|
203
|
+
tenant_id=self._tenant_id,
|
|
204
|
+
workspace_id=self._workspace_id,
|
|
205
|
+
poll_interval=self._poll_interval,
|
|
206
|
+
lease_ttl_seconds=self._lease_ttl_seconds,
|
|
207
|
+
activation_id=f"studio-scheduler:{self._owner_id}:{instance_id}",
|
|
208
|
+
runtime_type=spec.launch_context.runtime_type,
|
|
209
|
+
bundle_digest=spec.manifest_sha256 or normalized,
|
|
210
|
+
)
|
|
211
|
+
runtime = build_agent_kernel_runtime(config)
|
|
212
|
+
try:
|
|
213
|
+
await runtime.start()
|
|
214
|
+
except Exception:
|
|
215
|
+
await runtime.close()
|
|
216
|
+
raise
|
|
217
|
+
target = StudioScheduledRuntimeTarget(
|
|
218
|
+
build_id=normalized,
|
|
219
|
+
agent_id=spec.agent_id,
|
|
220
|
+
tenant_id=self._tenant_id,
|
|
221
|
+
agent_instance_id=instance_id,
|
|
222
|
+
)
|
|
223
|
+
self._entries_by_build[normalized] = _RuntimeEntry(
|
|
224
|
+
target=target,
|
|
225
|
+
spec=spec,
|
|
226
|
+
runtime=runtime,
|
|
227
|
+
)
|
|
228
|
+
self._build_by_instance[instance_id] = normalized
|
|
229
|
+
return target
|
|
230
|
+
|
|
231
|
+
async def ensure_target(
|
|
232
|
+
self, target: ScheduledTaskTarget
|
|
233
|
+
) -> StudioScheduledRuntimeTarget:
|
|
234
|
+
build_id = str(target.agent_version_ref or "").strip()
|
|
235
|
+
exact = await self.ensure_build(
|
|
236
|
+
build_id,
|
|
237
|
+
expected_agent_id=target.agent_id,
|
|
238
|
+
)
|
|
239
|
+
if (
|
|
240
|
+
target.tenant_id != exact.tenant_id
|
|
241
|
+
or target.agent_instance_id != exact.agent_instance_id
|
|
242
|
+
):
|
|
243
|
+
raise StudioSchedulerRuntimeError(
|
|
244
|
+
"SCHEDULER_TARGET_MISMATCH",
|
|
245
|
+
"定时任务目标与不可变 Build 的 Kernel 身份不一致",
|
|
246
|
+
)
|
|
247
|
+
return exact
|
|
248
|
+
|
|
249
|
+
def runtime_for_build(self, build_id: str) -> AgentKernelRuntime:
|
|
250
|
+
entry = self._entries_by_build.get(str(build_id))
|
|
251
|
+
if entry is None:
|
|
252
|
+
raise StudioSchedulerRuntimeError(
|
|
253
|
+
"SCHEDULER_TARGET_UNAVAILABLE",
|
|
254
|
+
"定时任务目标 Kernel 未注册",
|
|
255
|
+
)
|
|
256
|
+
return entry.runtime
|
|
257
|
+
|
|
258
|
+
async def submit(
|
|
259
|
+
self,
|
|
260
|
+
command: AgentControlCommand,
|
|
261
|
+
permit: AgentControlPermit,
|
|
262
|
+
) -> AgentControlReceipt:
|
|
263
|
+
entry = self._entry_for_instance(command.agent_instance_id)
|
|
264
|
+
if command.tenant_id != entry.target.tenant_id:
|
|
265
|
+
raise StudioSchedulerRuntimeError(
|
|
266
|
+
"SCHEDULER_TARGET_MISMATCH",
|
|
267
|
+
"AgentControl tenant 与 Build Kernel 不一致",
|
|
268
|
+
)
|
|
269
|
+
if await self._session_service.get_session(command.session_id) is None:
|
|
270
|
+
await self._session_service.create_session(
|
|
271
|
+
agent_id=entry.target.agent_id,
|
|
272
|
+
user_id=command.tenant_id,
|
|
273
|
+
session_id=command.session_id,
|
|
274
|
+
)
|
|
275
|
+
return await entry.runtime.kernel.submit(command, permit=permit)
|
|
276
|
+
|
|
277
|
+
async def read_events(
|
|
278
|
+
self, occurrence: ScheduleOccurrence
|
|
279
|
+
) -> tuple[tuple[int, object], ...]:
|
|
280
|
+
target = occurrence.target
|
|
281
|
+
if target is None:
|
|
282
|
+
return ()
|
|
283
|
+
await self.ensure_target(target)
|
|
284
|
+
entry = self._entry_for_instance(target.agent_instance_id)
|
|
285
|
+
trusted = trusted_context(
|
|
286
|
+
source_kind="scheduler",
|
|
287
|
+
source_ref=occurrence.occurrence_id,
|
|
288
|
+
tenant_id=target.tenant_id,
|
|
289
|
+
agent_instance_id=target.agent_instance_id,
|
|
290
|
+
session_id=occurrence.session_id,
|
|
291
|
+
operations=("subscribe_events",),
|
|
292
|
+
)
|
|
293
|
+
cursor = occurrence.last_event_seq
|
|
294
|
+
if cursor is None:
|
|
295
|
+
cursor = occurrence.accepted_seq or 0
|
|
296
|
+
subscription = SessionEventSubscription(
|
|
297
|
+
tenant_id=trusted.tenant_id,
|
|
298
|
+
agent_instance_id=trusted.agent_instance_id,
|
|
299
|
+
session_id=occurrence.session_id,
|
|
300
|
+
authorization_ref=trusted.permit.permit_id,
|
|
301
|
+
after_seq=cursor,
|
|
302
|
+
)
|
|
303
|
+
result: list[tuple[int, object]] = []
|
|
304
|
+
stream = entry.runtime.kernel.subscribe(
|
|
305
|
+
subscription,
|
|
306
|
+
permit=trusted.permit,
|
|
307
|
+
timeout=0.05,
|
|
308
|
+
)
|
|
309
|
+
try:
|
|
310
|
+
async for envelope in stream:
|
|
311
|
+
result.append((int(envelope.seq), envelope))
|
|
312
|
+
if len(result) >= 100:
|
|
313
|
+
break
|
|
314
|
+
finally:
|
|
315
|
+
await stream.aclose()
|
|
316
|
+
return tuple(result)
|
|
317
|
+
|
|
318
|
+
async def close(self) -> None:
|
|
319
|
+
async with self._lock:
|
|
320
|
+
entries = list(self._entries_by_build.values())
|
|
321
|
+
self._entries_by_build.clear()
|
|
322
|
+
self._build_by_instance.clear()
|
|
323
|
+
self._started = False
|
|
324
|
+
first_error: BaseException | None = None
|
|
325
|
+
for entry in reversed(entries):
|
|
326
|
+
try:
|
|
327
|
+
await entry.runtime.close()
|
|
328
|
+
except BaseException as error: # cleanup must continue for other Builds
|
|
329
|
+
if first_error is None:
|
|
330
|
+
first_error = error
|
|
331
|
+
if first_error is not None:
|
|
332
|
+
raise first_error
|
|
333
|
+
|
|
334
|
+
def _entry_for_instance(self, instance_id: str) -> _RuntimeEntry:
|
|
335
|
+
build_id = self._build_by_instance.get(str(instance_id))
|
|
336
|
+
entry = self._entries_by_build.get(build_id or "")
|
|
337
|
+
if entry is None:
|
|
338
|
+
raise StudioSchedulerRuntimeError(
|
|
339
|
+
"SCHEDULER_TARGET_UNAVAILABLE",
|
|
340
|
+
"定时任务目标 Kernel 未注册",
|
|
341
|
+
)
|
|
342
|
+
return entry
|
|
343
|
+
|
|
344
|
+
def _instance_id(self, build_id: str) -> str:
|
|
345
|
+
digest = hashlib.sha256(build_id.encode("utf-8")).hexdigest()[:24]
|
|
346
|
+
return f"studio-schedule-{digest}"
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _require_agent(spec: StudioRunSpec, expected_agent_id: str | None) -> None:
|
|
350
|
+
if expected_agent_id and spec.agent_id != expected_agent_id:
|
|
351
|
+
raise StudioSchedulerRuntimeError(
|
|
352
|
+
"SCHEDULER_AGENT_MISMATCH",
|
|
353
|
+
"定时任务 Agent 与不可变 Build 不一致",
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _start_defaults(spec: StudioRunSpec) -> dict[str, object]:
|
|
358
|
+
defaults: dict[str, object] = {
|
|
359
|
+
"agent_id": spec.agent_id,
|
|
360
|
+
"config": dict(spec.request_config),
|
|
361
|
+
}
|
|
362
|
+
if spec.model:
|
|
363
|
+
defaults["model"] = spec.model
|
|
364
|
+
defaults["allowed_models"] = [spec.model]
|
|
365
|
+
return defaults
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
__all__ = [
|
|
369
|
+
"ResolveAdapterProvider",
|
|
370
|
+
"ResolveBuild",
|
|
371
|
+
"StudioScheduledKernelRegistry",
|
|
372
|
+
"StudioScheduledRuntimeTarget",
|
|
373
|
+
"StudioSchedulerRuntimeError",
|
|
374
|
+
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Studio-facing lifecycle for the local Scheduler Lite backend.
|
|
2
|
+
|
|
3
|
+
This service owns only local scheduling intent and occurrence history. It
|
|
4
|
+
does not silently start a second runtime or submit prompts directly: when the
|
|
5
|
+
local AgentKernel route is unavailable it reports that condition explicitly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from ksadk.kernel.ingress import kernel_route_active
|
|
14
|
+
from ksadk.scheduler import (
|
|
15
|
+
AgentControlSchedulerDispatcher,
|
|
16
|
+
SchedulerDispatchError,
|
|
17
|
+
SchedulerEngine,
|
|
18
|
+
SchedulerSQLiteStore,
|
|
19
|
+
next_schedule_time,
|
|
20
|
+
)
|
|
21
|
+
from ksadk.scheduler.contracts import ScheduledTask, ScheduledTaskTarget, ScheduleOccurrence
|
|
22
|
+
from ksadk.studio.errors import StudioError, not_found
|
|
23
|
+
from ksadk.studio.workspace import Workspace
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from ksadk.studio.scheduler_runtime import StudioScheduledKernelRegistry
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StudioSchedulerService:
|
|
30
|
+
"""CRUD and explicit local process lifecycle for ``ScheduledTask/v1``."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
workspace: Workspace,
|
|
35
|
+
*,
|
|
36
|
+
runtime_registry: StudioScheduledKernelRegistry | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.workspace = workspace
|
|
39
|
+
self.runtime_registry = runtime_registry
|
|
40
|
+
self.store = SchedulerSQLiteStore(
|
|
41
|
+
workspace.resolve(".agentkit/scheduler/scheduler.sqlite3")
|
|
42
|
+
)
|
|
43
|
+
dispatcher = (
|
|
44
|
+
AgentControlSchedulerDispatcher(
|
|
45
|
+
runtime_registry.submit,
|
|
46
|
+
event_reader=runtime_registry.read_events,
|
|
47
|
+
target_preparer=self._prepare_target,
|
|
48
|
+
)
|
|
49
|
+
if runtime_registry is not None
|
|
50
|
+
else AgentControlSchedulerDispatcher()
|
|
51
|
+
)
|
|
52
|
+
self.engine = SchedulerEngine(
|
|
53
|
+
self.store,
|
|
54
|
+
dispatcher,
|
|
55
|
+
# Keep the local monitor alive when Studio starts before a Runtime.
|
|
56
|
+
# It observes availability on every scan and never claims work while
|
|
57
|
+
# the trusted Kernel route is absent.
|
|
58
|
+
tick_guard=self._runtime_available,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def availability(self) -> dict[str, object]:
|
|
62
|
+
active = self._runtime_available()
|
|
63
|
+
engine = self.engine.status()
|
|
64
|
+
return {
|
|
65
|
+
"available": active,
|
|
66
|
+
"scope": "local_studio_process",
|
|
67
|
+
"alwaysOn": False,
|
|
68
|
+
"reason": "agent_kernel_route_inactive" if not active else None,
|
|
69
|
+
"triggerActive": bool(active and engine["running"]),
|
|
70
|
+
"store": "sqlite",
|
|
71
|
+
**engine,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def list_tasks(self) -> list[ScheduledTask]:
|
|
75
|
+
return [task for task, _generation in self.store.list_tasks()]
|
|
76
|
+
|
|
77
|
+
def get_task(self, task_id: str) -> ScheduledTask:
|
|
78
|
+
item = self.store.get_task(task_id)
|
|
79
|
+
if item is None:
|
|
80
|
+
raise not_found("schedule", task_id)
|
|
81
|
+
return item[0]
|
|
82
|
+
|
|
83
|
+
def list_occurrences(self, task_id: str, *, limit: int = 50) -> list[ScheduleOccurrence]:
|
|
84
|
+
# A deleted task intentionally retains occurrence history, so query the
|
|
85
|
+
# history table directly rather than requiring a live task row.
|
|
86
|
+
return self.store.list_occurrences(task_id, limit=limit)
|
|
87
|
+
|
|
88
|
+
def list_all_occurrences(self, *, limit: int = 200) -> list[ScheduleOccurrence]:
|
|
89
|
+
return self.store.list_all_occurrences(limit=limit)
|
|
90
|
+
|
|
91
|
+
def create_task(self, task: ScheduledTask) -> ScheduledTask:
|
|
92
|
+
if self.store.get_task(task.task_id) is not None:
|
|
93
|
+
raise StudioError(
|
|
94
|
+
"SCHEDULE_ALREADY_EXISTS",
|
|
95
|
+
"该定时任务已存在",
|
|
96
|
+
status_code=409,
|
|
97
|
+
details={"id": task.task_id},
|
|
98
|
+
)
|
|
99
|
+
prepared = self._prepare(task)
|
|
100
|
+
self.store.put_task(prepared, generation=1)
|
|
101
|
+
return prepared
|
|
102
|
+
|
|
103
|
+
def update_task(self, task_id: str, task: ScheduledTask) -> ScheduledTask:
|
|
104
|
+
if task.task_id != task_id:
|
|
105
|
+
raise StudioError(
|
|
106
|
+
"SCHEDULE_ID_IMMUTABLE",
|
|
107
|
+
"定时任务 ID 不可修改",
|
|
108
|
+
status_code=422,
|
|
109
|
+
field="taskId",
|
|
110
|
+
)
|
|
111
|
+
existing = self.store.get_task(task_id)
|
|
112
|
+
if existing is None:
|
|
113
|
+
raise not_found("schedule", task_id)
|
|
114
|
+
old, generation = existing
|
|
115
|
+
prepared = self._prepare(
|
|
116
|
+
task.model_copy(update={"created_at": old.created_at}),
|
|
117
|
+
reset_next_run=True,
|
|
118
|
+
)
|
|
119
|
+
self.store.put_task(prepared, generation=generation + 1)
|
|
120
|
+
return prepared
|
|
121
|
+
|
|
122
|
+
def delete_task(self, task_id: str) -> None:
|
|
123
|
+
if not self.store.delete_task(task_id):
|
|
124
|
+
raise not_found("schedule", task_id)
|
|
125
|
+
|
|
126
|
+
async def run_now(self, task_id: str) -> ScheduleOccurrence:
|
|
127
|
+
self._require_kernel_route()
|
|
128
|
+
try:
|
|
129
|
+
return await self.engine.run_now(task_id)
|
|
130
|
+
except KeyError as exc:
|
|
131
|
+
raise not_found("schedule", task_id) from exc
|
|
132
|
+
except SchedulerDispatchError as exc:
|
|
133
|
+
raise StudioError(
|
|
134
|
+
exc.code,
|
|
135
|
+
exc.detail,
|
|
136
|
+
status_code=409 if exc.code in {"TASK_DISABLED", "CONCURRENCY_FORBID"} else 422,
|
|
137
|
+
) from exc
|
|
138
|
+
|
|
139
|
+
async def tick(self) -> list[ScheduleOccurrence]:
|
|
140
|
+
self._require_kernel_route()
|
|
141
|
+
return await self.engine.tick()
|
|
142
|
+
|
|
143
|
+
async def start_if_available(self) -> bool:
|
|
144
|
+
# The monitor is process-local and cheap. Starting it unconditionally
|
|
145
|
+
# is what lets a Runtime launched after Studio startup begin scheduling
|
|
146
|
+
# without a daemon restart. The engine's tick_guard is the fail-closed
|
|
147
|
+
# admission boundary while no Kernel route exists.
|
|
148
|
+
if self.runtime_registry is not None:
|
|
149
|
+
await self.runtime_registry.start()
|
|
150
|
+
await self.engine.start()
|
|
151
|
+
return self._runtime_available()
|
|
152
|
+
|
|
153
|
+
async def stop(self) -> None:
|
|
154
|
+
try:
|
|
155
|
+
await self.engine.stop()
|
|
156
|
+
finally:
|
|
157
|
+
if self.runtime_registry is not None:
|
|
158
|
+
await self.runtime_registry.close()
|
|
159
|
+
|
|
160
|
+
def _prepare(self, task: ScheduledTask, *, reset_next_run: bool = False) -> ScheduledTask:
|
|
161
|
+
now = datetime.now(timezone.utc)
|
|
162
|
+
next_run_at = task.next_run_at
|
|
163
|
+
if reset_next_run or next_run_at is None:
|
|
164
|
+
next_run_at = next_schedule_time(
|
|
165
|
+
task.schedule,
|
|
166
|
+
after=now,
|
|
167
|
+
anchor_at=task.created_at,
|
|
168
|
+
)
|
|
169
|
+
if task.enabled and next_run_at is None:
|
|
170
|
+
raise StudioError(
|
|
171
|
+
"SCHEDULE_NOT_FUTURE",
|
|
172
|
+
"启用的单次任务必须指定未来的触发时间",
|
|
173
|
+
status_code=422,
|
|
174
|
+
field="schedule.at",
|
|
175
|
+
)
|
|
176
|
+
return task.model_copy(update={"next_run_at": next_run_at, "updated_at": now})
|
|
177
|
+
|
|
178
|
+
def _runtime_available(self) -> bool:
|
|
179
|
+
if self.runtime_registry is not None:
|
|
180
|
+
return self.runtime_registry.started
|
|
181
|
+
return kernel_route_active()
|
|
182
|
+
|
|
183
|
+
def _require_kernel_route(self) -> None:
|
|
184
|
+
if not self._runtime_available():
|
|
185
|
+
raise StudioError(
|
|
186
|
+
"SCHEDULER_RUNTIME_UNAVAILABLE",
|
|
187
|
+
"本地 Agent Kernel 未启用,定时任务不会伪装为已运行",
|
|
188
|
+
status_code=409,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
async def _prepare_target(self, target: ScheduledTaskTarget) -> object:
|
|
192
|
+
"""Resolve the build-pinned Kernel target, preserving typed failures.
|
|
193
|
+
|
|
194
|
+
StudioScheduledKernelRegistry.ensure_target raises
|
|
195
|
+
StudioSchedulerRuntimeError (for example SCHEDULER_BUILD_UNAVAILABLE
|
|
196
|
+
when agent_version_ref is not a real immutable Build id). Without this
|
|
197
|
+
adapter the SchedulerEngine generic except Exception collapses every such
|
|
198
|
+
failure into an opaque DISPATCH_FAILED occurrence, hiding the real reason.
|
|
199
|
+
Re-raise it as a SchedulerDispatchError so the durable occurrence keeps
|
|
200
|
+
the actionable error code instead of the catch-all.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
from ksadk.studio.scheduler_runtime import StudioSchedulerRuntimeError
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
return await self.runtime_registry.ensure_target(target) # type: ignore[union-attr]
|
|
207
|
+
except StudioSchedulerRuntimeError as error:
|
|
208
|
+
raise SchedulerDispatchError(error.code, str(error)) from error
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
__all__ = ["StudioSchedulerService"]
|