@scemoon/cdh 1.0.4 → 1.0.5
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.
- package/ai-dlc-skill/SKILL.md +4 -2
- package/ai-dlc-skill/agents/master.md +6 -0
- package/ai-dlc-skill/agents/plan-agent.md +2 -2
- package/ai-dlc-skill/agents/understand-agent.md +4 -4
- package/ai-dlc-skill/agents/verify-agent.md +1 -1
- package/ai-dlc-skill/architecture/project-structure.md +10 -10
- package/ai-dlc-skill/brownfield/README.md +2 -2
- package/ai-dlc-skill/brownfield/generate-context.sh +3 -3
- package/ai-dlc-skill/brownfield/scripts/extract-api.sh +5 -5
- package/ai-dlc-skill/components/backend.md +1 -1
- package/ai-dlc-skill/components/web.md +2 -2
- package/ai-dlc-skill/contracts/README.md +3 -3
- package/ai-dlc-skill/cross-tool/cline/export.sh +1 -1
- package/ai-dlc-skill/cross-tool/copilot/export.sh +1 -1
- package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +7 -3
- package/ai-dlc-skill/cross-tool/onecode/export.sh +14 -8
- package/ai-dlc-skill/phases/deliver/lifecycle.md +6 -6
- package/ai-dlc-skill/phases/deliver/prompt.md +2 -2
- package/ai-dlc-skill/phases/deliver/rules.md +1 -1
- package/ai-dlc-skill/phases/plan/lifecycle.md +11 -11
- package/ai-dlc-skill/phases/plan/prompt.md +6 -6
- package/ai-dlc-skill/phases/plan/rules.md +1 -1
- package/ai-dlc-skill/phases/understand/entry.md +1 -1
- package/ai-dlc-skill/phases/understand/lifecycle.md +7 -7
- package/ai-dlc-skill/phases/understand/prompt.md +8 -8
- package/ai-dlc-skill/phases/understand/rules.md +5 -5
- package/ai-dlc-skill/phases/verify/lifecycle.md +24 -24
- package/ai-dlc-skill/phases/verify/prompt.md +8 -8
- package/ai-dlc-skill/phases/verify/rules.md +9 -9
- package/ai-dlc-skill/providers/README.md +2 -2
- package/ai-dlc-skill/providers/aliyun/preview.yaml +2 -2
- package/ai-dlc-skill/providers/tcb/preview.yaml +2 -2
- package/ai-dlc-skill/requirements.md +7 -7
- package/ai-dlc-skill/skill.yaml +4 -4
- package/ai-dlc-skill/templates/integration/CHANGELOG.md +2 -2
- package/ai-dlc-skill/templates/integration/contract-diff.md +7 -7
- package/ai-dlc-skill/templates/integration/contract-spec.md +7 -7
- package/ai-dlc-skill/templates/integration/runtime-contract.yaml +1 -1
- package/ai-dlc-skill/templates/project/README.md +18 -19
- package/ai-dlc-skill/templates/project/template.md +21 -21
- package/ai-dlc-skill/walkthrough/collect.sh +1 -1
- package/ai-dlc-skill/walkthrough/template.md +2 -2
- package/ai-dlc-skill/workflows/ai-dlc.yaml +12 -12
- package/ai-dlc-skill/workflows/deployment.yaml +4 -4
- package/cdh/__pycache__/cdh_mcp_injector.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_mcp_loader.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_mcp_manager.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_session_aggregator.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_skill_loader.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_skill_manager.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-313.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cdh_mcp_injector.py +115 -0
- package/cdh/cdh_mcp_loader.py +67 -0
- package/cdh/cdh_mcp_manager.py +73 -0
- package/cdh/cdh_session_aggregator.py +258 -0
- package/cdh/cdh_skill_manager.py +105 -0
- package/cdh/cli.py +14 -3
- package/cdh/scaffold.py +192 -41
- package/onecode/__init__.py +1 -1
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/__pycache__/migrate.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/project_doc.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +47 -164
- package/onecode/agent/attachments.py +0 -1
- package/onecode/agent/cdh_loader.py +17 -25
- package/onecode/agent/context.py +8 -3
- package/onecode/agent/engine.py +835 -220
- package/onecode/agent/hooks.py +1 -1
- package/onecode/agent/onecode_agent_acp.py +460 -47
- package/onecode/agent/onecode_agent_acp.py.bak +2447 -0
- package/onecode/agent/permissions.py +0 -1
- package/onecode/agent/project_doc.py +19 -0
- package/onecode/agent/session.py +17 -3
- package/onecode/agent/snapshot.py +3 -4
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -6
- package/onecode/agent/tools/apply_patch_tool.py +2 -8
- package/onecode/agent/tools/bash_tool.py +32 -12
- package/onecode/agent/tools/communication_tools.py +4 -5
- package/onecode/agent/tools/config_tool.py +3 -3
- package/onecode/agent/tools/cron_tools.py +4 -5
- package/onecode/agent/tools/file_ops.py +37 -3
- package/onecode/agent/tools/file_tools.py +10 -11
- package/onecode/agent/tools/git_tools.py +2 -2
- package/onecode/agent/tools/lsp_tools.py +2 -2
- package/onecode/agent/tools/mcp_tools.py +3 -5
- package/onecode/agent/tools/permission_handler.py +1 -1
- package/onecode/agent/tools/protocol.py +1 -1
- package/onecode/agent/tools/registry.py +69 -2
- package/onecode/agent/tools/sandbox.py +110 -16
- package/onecode/agent/tools/skill_tools.py +2 -2
- package/onecode/agent/tools/todo_tools.py +38 -19
- package/onecode/agent/tools/web_tools.py +6 -7
- package/onecode/builtin_skills/agent-browser/SKILL.md +212 -0
- package/onecode/builtin_skills/agent-browser/skill.yaml +8 -0
- package/onecode/builtin_skills/skill-creator/SKILL.md +106 -0
- package/onecode/builtin_skills/skill-creator/skill.yaml +8 -0
- package/onecode/cli.py +186 -6
- package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
- package/onecode/codebase/chunker.py +0 -5
- package/onecode/codebase/indexer.py +14 -1
- package/onecode/codebase/retriever.py +13 -2
- package/onecode/codebase/storage.py +5 -4
- package/onecode/commands.py +81 -6
- package/onecode/config.py +41 -12
- package/onecode/config_screen.py +7 -3
- package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/mcp/manager.py +2 -3
- package/onecode/memory/__init__.py +7 -53
- package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
- package/onecode/memory/backend.py +13 -39
- package/onecode/memory/pyramid.py +2 -71
- package/onecode/memory/recall.py +38 -3
- package/onecode/migrate.py +122 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/messages.py +58 -0
- package/onecode/models/provider.py +20 -2
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +59 -12
- package/onecode/models/providers/glm_provider.py +40 -0
- package/onecode/models/providers/minimax_provider.py +3 -0
- package/onecode/models/providers/minimaxi_provider.py +3 -0
- package/onecode/models/providers/ollama_provider.py +3 -0
- package/onecode/server/app.py +1 -1
- package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/skills/argument_substitution.py +0 -1
- package/onecode/skills/bootstrap.py +124 -0
- package/onecode/skills/loader.py +35 -53
- package/onecode/skills/manager.py +2 -2
- package/onecode/storage/activity.py +3 -4
- package/onecode/storage/session.py +3 -4
- package/onecode/tasks/manager.py +23 -10
- package/onecode/trace/tracer.py +2 -3
- package/package.json +1 -1
- package/pyproject.toml +5 -1
- package/tui/__pycache__/about.cpython-314.pyc +0 -0
- package/tui/__pycache__/app.cpython-313.pyc +0 -0
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/danger.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
- package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +125 -58
- package/tui/acp/api.py +12 -0
- package/tui/acp/messages.py +3 -1
- package/tui/acp/prompt.py +0 -57
- package/tui/acp/protocol.py +6 -0
- package/tui/app.py +11 -33
- package/tui/messages.py +2 -0
- package/tui/path_complete.py +1 -1
- package/tui/paths.py +16 -0
- package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-313.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/permissions.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/tool_content_screen.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +35 -15
- package/tui/screens/diff_screen.py +84 -0
- package/tui/screens/log.py +25 -25
- package/tui/screens/main.py +48 -26
- package/tui/screens/projects.tcss +5 -1
- package/tui/screens/projects_app.py +6 -6
- package/tui/screens/projects_screen.py +19 -39
- package/tui/screens/sessions.py +3 -5
- package/tui/session_tracker.py +3 -0
- package/tui/settings_schema.py +1 -1
- package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
- package/tui/visuals/columns.py +1 -2
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/agent_response.py +0 -1
- package/tui/widgets/agent_thought.py +0 -1
- package/tui/widgets/conversation.py +157 -14
- package/tui/widgets/diff_view.py +10 -0
- package/tui/widgets/load_more.py +24 -0
- package/tui/widgets/modified_files.py +86 -12
- package/tui/widgets/plan.py +1 -1
- package/tui/widgets/project_grid_select.py +9 -2
- package/tui/widgets/session_grid_select.py +6 -0
- package/tui/widgets/side_bar.py +10 -0
- package/tui/widgets/subagent.py +92 -58
- package/tui/widgets/subagent_screen.py +638 -115
- package/tui/widgets/terminal.py +1 -2
- package/tui/widgets/terminal_tool.py +11 -2
- package/tui/widgets/tool_call.py +35 -2
- package/onecode/memory/offload.py +0 -114
- package/onecode/memory/symbolic.py +0 -164
|
@@ -35,11 +35,11 @@ phases:
|
|
|
35
35
|
- UND-001..006: Understand rules
|
|
36
36
|
- Human review and approval
|
|
37
37
|
artifacts:
|
|
38
|
-
- requirements.md
|
|
39
|
-
- openspec/changes/{id}/spec-delta.md
|
|
38
|
+
- aidlc/requirements.md
|
|
39
|
+
- aidlc/openspec/changes/{id}/spec-delta.md
|
|
40
40
|
- apps/{component}/features/{domain}/{feature}.feature
|
|
41
|
-
- features/cross-stack/{domain}/{feature}.feature
|
|
42
|
-
- contracts/api|events/{name}.{yaml,graphql}
|
|
41
|
+
- aidlc/features/cross-stack/{domain}/{feature}.feature
|
|
42
|
+
- aidlc/contracts/api|events/{name}.{yaml,graphql}
|
|
43
43
|
|
|
44
44
|
- name: plan
|
|
45
45
|
label: "② Plan — 规划"
|
|
@@ -57,9 +57,9 @@ phases:
|
|
|
57
57
|
- PLN-001..004: Plan rules
|
|
58
58
|
- Human review and approval
|
|
59
59
|
artifacts:
|
|
60
|
-
- openspec/changes/{id}/design.md
|
|
61
|
-
- openspec/changes/{id}/task-list.md
|
|
62
|
-
- openspec/changes/{id}/contract-diff.md
|
|
60
|
+
- aidlc/openspec/changes/{id}/design.md
|
|
61
|
+
- aidlc/openspec/changes/{id}/task-list.md
|
|
62
|
+
- aidlc/openspec/changes/{id}/contract-diff.md
|
|
63
63
|
|
|
64
64
|
- name: verify
|
|
65
65
|
label: "③ Verify — 验证"
|
|
@@ -81,10 +81,10 @@ phases:
|
|
|
81
81
|
- apps/{component}/tests/{unit,integration,e2e}/test_{feature}.py
|
|
82
82
|
- apps/{component}/features/steps/test_{feature}_steps.py
|
|
83
83
|
- apps/{component}/src/{module}/{feature}.py
|
|
84
|
-
- tests/contract/test_{contract}.py
|
|
85
|
-
- tests/cross-stack/test_{flow}.py
|
|
86
|
-
- packages/shared/
|
|
87
|
-
- openspec/changes/{id}/contract-diff.md
|
|
84
|
+
- aidlc/tests/contract/test_{contract}.py
|
|
85
|
+
- aidlc/tests/cross-stack/test_{flow}.py
|
|
86
|
+
- aidlc/packages/shared/
|
|
87
|
+
- aidlc/openspec/changes/{id}/contract-diff.md
|
|
88
88
|
|
|
89
89
|
- name: deliver
|
|
90
90
|
label: "④ Deliver — 交付"
|
|
@@ -105,6 +105,6 @@ phases:
|
|
|
105
105
|
- STACK_URL / BACKEND_URL
|
|
106
106
|
- Per-component e2e report
|
|
107
107
|
- Cross-stack e2e report
|
|
108
|
-
- openspec/changes/{id}/contract-diff.md
|
|
108
|
+
- aidlc/openspec/changes/{id}/contract-diff.md
|
|
109
109
|
- Stack BVT report
|
|
110
110
|
- Deploy log
|
|
@@ -107,7 +107,7 @@ stages:
|
|
|
107
107
|
requires: [deploy_stack]
|
|
108
108
|
when: affects contains tta
|
|
109
109
|
- name: e2e_cross_stack
|
|
110
|
-
command: pytest tests/cross-stack/ --stack-url ${STACK_URL}
|
|
110
|
+
command: pytest aidlc/tests/cross-stack/ --stack-url ${STACK_URL}
|
|
111
111
|
requires: [e2e_backend, e2e_web, e2e_native, e2e_desktop, e2e_wxa, e2e_mya, e2e_tta]
|
|
112
112
|
when: affects contains >= 2 components
|
|
113
113
|
|
|
@@ -119,7 +119,7 @@ stages:
|
|
|
119
119
|
- name: smoke
|
|
120
120
|
command: smoke-test ${STACK_URL}
|
|
121
121
|
- name: cross_stack
|
|
122
|
-
command: pytest tests/cross-stack/ --stack-url ${STACK_URL}
|
|
122
|
+
command: pytest aidlc/tests/cross-stack/ --stack-url ${STACK_URL}
|
|
123
123
|
|
|
124
124
|
- name: production
|
|
125
125
|
description: Stack production deploy with stack BVT
|
|
@@ -132,7 +132,7 @@ stages:
|
|
|
132
132
|
- per_component_e2e_reports
|
|
133
133
|
- cross_stack_e2e_report
|
|
134
134
|
- staging_smoke_report
|
|
135
|
-
- openspec/changes/{id}/contract-diff.md
|
|
135
|
+
- aidlc/openspec/changes/{id}/contract-diff.md
|
|
136
136
|
- name: deploy_stack
|
|
137
137
|
command: deploy_stack --env production
|
|
138
138
|
- name: bvt
|
|
@@ -142,7 +142,7 @@ stages:
|
|
|
142
142
|
command: curl -f ${PRODUCTION_URL}/health
|
|
143
143
|
timeout: 30s
|
|
144
144
|
- name: archive_contract_diff
|
|
145
|
-
command: cp openspec/changes/{id}/contract-diff.md deploy-artifacts/
|
|
145
|
+
command: cp aidlc/openspec/changes/{id}/contract-diff.md aidlc/deploy-artifacts/
|
|
146
146
|
|
|
147
147
|
rollback:
|
|
148
148
|
triggers:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""cdh platform MCP injector — injects platform MCPs into engines.
|
|
2
|
+
|
|
3
|
+
Strategies (per engine capability):
|
|
4
|
+
- env var: Set CDH_SHARED_MCP with JSON-serialized platform MCP configs
|
|
5
|
+
- CLI args: Inject via engine-specific CLI flags (--mcp-server name=url)
|
|
6
|
+
- config file: Write a temp config that the engine reads at startup
|
|
7
|
+
|
|
8
|
+
Name conflict: engine private MCPs win over cdh platform MCPs.
|
|
9
|
+
Engines that don't support injection get a silent fallback (INFO log).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("cdh.mcp.injector")
|
|
23
|
+
|
|
24
|
+
CDH_SHARED_MCP_ENV = "CDH_SHARED_MCP"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CdhMcpInjector:
|
|
28
|
+
SUPPORTED_ENGINES = {"onecode", "opencode"}
|
|
29
|
+
|
|
30
|
+
def __init__(self, platform_mcps_dir: Path | None = None):
|
|
31
|
+
from cdh.cdh_mcp_manager import CDH_PLATFORM_MCPS_DIR
|
|
32
|
+
|
|
33
|
+
self.platform_mcps_dir = platform_mcps_dir or CDH_PLATFORM_MCPS_DIR
|
|
34
|
+
|
|
35
|
+
def inject_env(self) -> None:
|
|
36
|
+
"""Set CDH_SHARED_MCP env var with platform MCP configs."""
|
|
37
|
+
config_path = self.platform_mcps_dir / "mcps.yaml"
|
|
38
|
+
if not config_path.exists():
|
|
39
|
+
return
|
|
40
|
+
try:
|
|
41
|
+
data = yaml.safe_load(config_path.read_text()) or {}
|
|
42
|
+
os.environ[CDH_SHARED_MCP_ENV] = json.dumps(data)
|
|
43
|
+
logger.debug("Set %s with %d MCP servers", CDH_SHARED_MCP_ENV, len(data))
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.warning("Failed to inject platform MCPs via env: %s", e)
|
|
46
|
+
|
|
47
|
+
def clear_env(self) -> None:
|
|
48
|
+
os.environ.pop(CDH_SHARED_MCP_ENV, None)
|
|
49
|
+
|
|
50
|
+
def get_shared_mcp_config(self) -> dict[str, dict]:
|
|
51
|
+
raw = os.environ.get(CDH_SHARED_MCP_ENV, "")
|
|
52
|
+
if not raw:
|
|
53
|
+
return {}
|
|
54
|
+
try:
|
|
55
|
+
return json.loads(raw)
|
|
56
|
+
except json.JSONDecodeError:
|
|
57
|
+
logger.warning("Invalid %s value, ignoring", CDH_SHARED_MCP_ENV)
|
|
58
|
+
return {}
|
|
59
|
+
|
|
60
|
+
def inject_for_engine(
|
|
61
|
+
self,
|
|
62
|
+
engine_name: str,
|
|
63
|
+
engine_mcps_path: Path | None = None,
|
|
64
|
+
) -> Optional[str]:
|
|
65
|
+
"""Inject platform MCPs for a specific engine.
|
|
66
|
+
|
|
67
|
+
Returns an info/warning message, or None if no injection needed.
|
|
68
|
+
|
|
69
|
+
For supported engines: sets CDH_SHARED_MCP env var.
|
|
70
|
+
For unsupported engines: logs a silent INFO fallback.
|
|
71
|
+
"""
|
|
72
|
+
platform_config = self._load_platform_config()
|
|
73
|
+
if not platform_config:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
if engine_name not in self.SUPPORTED_ENGINES:
|
|
77
|
+
logger.info(
|
|
78
|
+
"Engine '%s' does not support cdh platform MCP injection. "
|
|
79
|
+
"Platform MCPs available via %s env var.",
|
|
80
|
+
engine_name,
|
|
81
|
+
CDH_SHARED_MCP_ENV,
|
|
82
|
+
)
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
# Merge with engine private (engine wins)
|
|
86
|
+
merged = dict(platform_config)
|
|
87
|
+
if engine_mcps_path and engine_mcps_path.exists():
|
|
88
|
+
try:
|
|
89
|
+
engine_data = yaml.safe_load(engine_mcps_path.read_text()) or {}
|
|
90
|
+
engine_data = {
|
|
91
|
+
k: v for k, v in engine_data.items() if isinstance(v, dict)
|
|
92
|
+
}
|
|
93
|
+
except Exception:
|
|
94
|
+
engine_data = {}
|
|
95
|
+
merged.update(engine_data)
|
|
96
|
+
|
|
97
|
+
os.environ[CDH_SHARED_MCP_ENV] = json.dumps(merged)
|
|
98
|
+
logger.debug(
|
|
99
|
+
"Injected %d platform MCPs + %d engine MCPs via %s for '%s'",
|
|
100
|
+
len(platform_config),
|
|
101
|
+
len(engine_data) if engine_mcps_path and engine_mcps_path.exists() else 0,
|
|
102
|
+
CDH_SHARED_MCP_ENV,
|
|
103
|
+
engine_name,
|
|
104
|
+
)
|
|
105
|
+
return f"{len(platform_config)} platform MCP(s) injected for '{engine_name}'"
|
|
106
|
+
|
|
107
|
+
def _load_platform_config(self) -> dict[str, dict]:
|
|
108
|
+
config_path = self.platform_mcps_dir / "mcps.yaml"
|
|
109
|
+
if not config_path.exists():
|
|
110
|
+
return {}
|
|
111
|
+
try:
|
|
112
|
+
data = yaml.safe_load(config_path.read_text()) or {}
|
|
113
|
+
return {k: v for k, v in data.items() if isinstance(v, dict)}
|
|
114
|
+
except Exception:
|
|
115
|
+
return {}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""cdh platform MCP loader — merges platform MCPs with engine-private MCPs.
|
|
2
|
+
|
|
3
|
+
Discovery order:
|
|
4
|
+
1. Read engine-private MCPs from engine's own config (e.g. ~/.onecode/mcps/mcps.yaml)
|
|
5
|
+
2. Read cdh platform MCPs from ~/.cdh/mcps/mcps.yaml
|
|
6
|
+
3. Merge: engine private wins on name conflict
|
|
7
|
+
4. Return merged list for injection
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
from cdh.cdh_mcp_manager import CDH_PLATFORM_MCPS_DIR
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("cdh.mcp.loader")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CdhMcpLoader:
|
|
23
|
+
def __init__(self, platform_mcps_dir: Path | None = None):
|
|
24
|
+
self.platform_mcps_dir = platform_mcps_dir or CDH_PLATFORM_MCPS_DIR
|
|
25
|
+
self._cache: dict[str, dict] | None = None
|
|
26
|
+
|
|
27
|
+
def load_platform(self) -> dict[str, dict]:
|
|
28
|
+
config_path = self.platform_mcps_dir / "mcps.yaml"
|
|
29
|
+
if not config_path.exists():
|
|
30
|
+
return {}
|
|
31
|
+
try:
|
|
32
|
+
data = yaml.safe_load(config_path.read_text()) or {}
|
|
33
|
+
return {k: v for k, v in data.items() if isinstance(v, dict)}
|
|
34
|
+
except Exception:
|
|
35
|
+
logger.warning("Failed to load platform MCP config from %s", config_path)
|
|
36
|
+
return {}
|
|
37
|
+
|
|
38
|
+
def get_merged(
|
|
39
|
+
self, engine_mcps_path: Path | None = None
|
|
40
|
+
) -> list[dict]:
|
|
41
|
+
platform = self.load_platform()
|
|
42
|
+
|
|
43
|
+
if engine_mcps_path and engine_mcps_path.exists():
|
|
44
|
+
try:
|
|
45
|
+
engine_data = yaml.safe_load(engine_mcps_path.read_text()) or {}
|
|
46
|
+
engine_data = {k: v for k, v in engine_data.items() if isinstance(v, dict)}
|
|
47
|
+
except Exception:
|
|
48
|
+
engine_data = {}
|
|
49
|
+
else:
|
|
50
|
+
engine_data = {}
|
|
51
|
+
|
|
52
|
+
merged = dict(platform)
|
|
53
|
+
merged.update(engine_data)
|
|
54
|
+
|
|
55
|
+
return [
|
|
56
|
+
{"name": name, **cfg, "source": "engine" if name in engine_data else "platform"}
|
|
57
|
+
for name, cfg in merged.items()
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def get_platform_only(self) -> list[dict]:
|
|
61
|
+
return [
|
|
62
|
+
{"name": name, **cfg, "source": "platform"}
|
|
63
|
+
for name, cfg in self.load_platform().items()
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def invalidate_cache(self) -> None:
|
|
67
|
+
self._cache = None
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""cdh platform MCP manager — operates ~/.cdh/mcps/.
|
|
2
|
+
|
|
3
|
+
This is the cdh platform equivalent of onecode.mcp.manager.MCPManager,
|
|
4
|
+
but operates strictly on ~/.cdh/mcps/ (cdh platform shared MCP pool).
|
|
5
|
+
Engines (onecode/opencode/claude) read platform MCPs via env var
|
|
6
|
+
injection at launch time (see cdh_mcp_injector.py).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("cdh.mcp")
|
|
18
|
+
|
|
19
|
+
CDH_PLATFORM_MCPS_DIR = Path.home() / ".cdh" / "mcps"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CdhMcpManager:
|
|
23
|
+
def __init__(self, mcps_dir: Path | None = None):
|
|
24
|
+
self.mcps_dir = mcps_dir or CDH_PLATFORM_MCPS_DIR
|
|
25
|
+
self.mcps_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
self.config_path = self.mcps_dir / "mcps.yaml"
|
|
27
|
+
self._data: dict[str, dict] = {}
|
|
28
|
+
if self.config_path.exists():
|
|
29
|
+
try:
|
|
30
|
+
self._data = yaml.safe_load(self.config_path.read_text()) or {}
|
|
31
|
+
except Exception:
|
|
32
|
+
self._data = {}
|
|
33
|
+
|
|
34
|
+
def add(self, name: str, url: str, transport: str = "sse") -> None:
|
|
35
|
+
self._data[name] = {"url": url, "transport": transport, "enabled": True}
|
|
36
|
+
self._save()
|
|
37
|
+
|
|
38
|
+
def add_stdio(self, name: str, command: str, args: list[str] | None = None) -> None:
|
|
39
|
+
self._data[name] = {
|
|
40
|
+
"command": command,
|
|
41
|
+
"args": args or [],
|
|
42
|
+
"transport": "stdio",
|
|
43
|
+
"enabled": True,
|
|
44
|
+
}
|
|
45
|
+
self._save()
|
|
46
|
+
|
|
47
|
+
def list(self) -> list[dict]:
|
|
48
|
+
return [{"name": name, **cfg} for name, cfg in self._data.items()]
|
|
49
|
+
|
|
50
|
+
def get(self, name: str) -> Optional[dict]:
|
|
51
|
+
return self._data.get(name)
|
|
52
|
+
|
|
53
|
+
def enable(self, name: str, enabled: bool = True) -> Optional[str]:
|
|
54
|
+
if name not in self._data:
|
|
55
|
+
return f"MCP server '{name}' not found"
|
|
56
|
+
self._data[name]["enabled"] = enabled
|
|
57
|
+
self._save()
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def remove(self, name: str) -> Optional[str]:
|
|
61
|
+
if name not in self._data:
|
|
62
|
+
return f"MCP server '{name}' not found"
|
|
63
|
+
del self._data[name]
|
|
64
|
+
self._save()
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> dict[str, dict]:
|
|
68
|
+
return dict(self._data)
|
|
69
|
+
|
|
70
|
+
def _save(self) -> None:
|
|
71
|
+
self.config_path.write_text(
|
|
72
|
+
yaml.dump(self._data, default_flow_style=False), encoding="utf-8"
|
|
73
|
+
)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""cdh platform session aggregator — cross-engine session registry.
|
|
2
|
+
|
|
3
|
+
Maintains a session registry at ~/.cdh/sessions/sessions.db (SQLite).
|
|
4
|
+
Provides a cross-engine view across all registered engines.
|
|
5
|
+
Engines register sessions via the aggregator API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import sqlite3
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("cdh.session")
|
|
18
|
+
|
|
19
|
+
CDH_SESSIONS_DIR = Path.home() / ".cdh" / "sessions"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CdhSessionAggregator:
|
|
23
|
+
def __init__(self, sessions_dir: Path | None = None):
|
|
24
|
+
self.sessions_dir = sessions_dir or CDH_SESSIONS_DIR
|
|
25
|
+
self.sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
self.db_path = self.sessions_dir / "sessions.db"
|
|
27
|
+
self._init_db()
|
|
28
|
+
|
|
29
|
+
def _init_db(self) -> None:
|
|
30
|
+
try:
|
|
31
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
32
|
+
conn.execute(
|
|
33
|
+
"""
|
|
34
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
35
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
36
|
+
engine TEXT NOT NULL,
|
|
37
|
+
engine_session_id TEXT NOT NULL,
|
|
38
|
+
project_name TEXT DEFAULT '',
|
|
39
|
+
agent TEXT NOT NULL,
|
|
40
|
+
title TEXT NOT NULL,
|
|
41
|
+
protocol TEXT DEFAULT 'acp',
|
|
42
|
+
prompt_count INTEGER DEFAULT 0,
|
|
43
|
+
created_at TEXT NOT NULL,
|
|
44
|
+
last_used TEXT NOT NULL,
|
|
45
|
+
meta_json TEXT DEFAULT '{}',
|
|
46
|
+
UNIQUE(engine, engine_session_id)
|
|
47
|
+
)
|
|
48
|
+
"""
|
|
49
|
+
)
|
|
50
|
+
conn.commit()
|
|
51
|
+
conn.close()
|
|
52
|
+
except sqlite3.Error as e:
|
|
53
|
+
logger.error("Failed to init session db: %s", e)
|
|
54
|
+
|
|
55
|
+
def register(
|
|
56
|
+
self,
|
|
57
|
+
engine: str,
|
|
58
|
+
engine_session_id: str,
|
|
59
|
+
agent: str,
|
|
60
|
+
title: str,
|
|
61
|
+
project_name: str = "",
|
|
62
|
+
protocol: str = "acp",
|
|
63
|
+
meta: dict[str, Any] | None = None,
|
|
64
|
+
) -> Optional[int]:
|
|
65
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
66
|
+
try:
|
|
67
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
68
|
+
conn.execute(
|
|
69
|
+
"""
|
|
70
|
+
INSERT OR REPLACE INTO sessions
|
|
71
|
+
(engine, engine_session_id, project_name, agent, title, protocol,
|
|
72
|
+
prompt_count, created_at, last_used, meta_json)
|
|
73
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)
|
|
74
|
+
""",
|
|
75
|
+
(
|
|
76
|
+
engine,
|
|
77
|
+
engine_session_id,
|
|
78
|
+
project_name,
|
|
79
|
+
agent,
|
|
80
|
+
title,
|
|
81
|
+
protocol,
|
|
82
|
+
now,
|
|
83
|
+
now,
|
|
84
|
+
json.dumps(meta or {}),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
conn.commit()
|
|
88
|
+
cursor = conn.execute("SELECT last_insert_rowid()")
|
|
89
|
+
row_id = cursor.fetchone()[0]
|
|
90
|
+
conn.close()
|
|
91
|
+
return row_id
|
|
92
|
+
except sqlite3.Error as e:
|
|
93
|
+
logger.error("Failed to register session: %s", e)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def update_last_used(self, engine: str, engine_session_id: str) -> bool:
|
|
97
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
98
|
+
try:
|
|
99
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
100
|
+
conn.execute(
|
|
101
|
+
"UPDATE sessions SET last_used = ? WHERE engine = ? AND engine_session_id = ?",
|
|
102
|
+
(now, engine, engine_session_id),
|
|
103
|
+
)
|
|
104
|
+
conn.commit()
|
|
105
|
+
conn.close()
|
|
106
|
+
return True
|
|
107
|
+
except sqlite3.Error:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
def increment_prompt_count(self, engine: str, engine_session_id: str) -> bool:
|
|
111
|
+
try:
|
|
112
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
113
|
+
conn.execute(
|
|
114
|
+
"UPDATE sessions SET prompt_count = prompt_count + 1 WHERE engine = ? AND engine_session_id = ?",
|
|
115
|
+
(engine, engine_session_id),
|
|
116
|
+
)
|
|
117
|
+
conn.commit()
|
|
118
|
+
conn.close()
|
|
119
|
+
return True
|
|
120
|
+
except sqlite3.Error:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def get(self, session_id: int) -> Optional[dict]:
|
|
124
|
+
try:
|
|
125
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
126
|
+
conn.row_factory = sqlite3.Row
|
|
127
|
+
cursor = conn.execute("SELECT * FROM sessions WHERE id = ?", (session_id,))
|
|
128
|
+
row = cursor.fetchone()
|
|
129
|
+
conn.close()
|
|
130
|
+
return dict(row) if row else None
|
|
131
|
+
except sqlite3.Error:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
def get_by_engine_id(self, engine: str, engine_session_id: str) -> Optional[dict]:
|
|
135
|
+
try:
|
|
136
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
137
|
+
conn.row_factory = sqlite3.Row
|
|
138
|
+
cursor = conn.execute(
|
|
139
|
+
"SELECT * FROM sessions WHERE engine = ? AND engine_session_id = ?",
|
|
140
|
+
(engine, engine_session_id),
|
|
141
|
+
)
|
|
142
|
+
row = cursor.fetchone()
|
|
143
|
+
conn.close()
|
|
144
|
+
return dict(row) if row else None
|
|
145
|
+
except sqlite3.Error:
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
def get_recent(self, max_results: int = 100) -> list[dict]:
|
|
149
|
+
try:
|
|
150
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
151
|
+
conn.row_factory = sqlite3.Row
|
|
152
|
+
cursor = conn.execute(
|
|
153
|
+
"SELECT * FROM sessions ORDER BY last_used DESC LIMIT ?",
|
|
154
|
+
(max_results,),
|
|
155
|
+
)
|
|
156
|
+
rows = cursor.fetchall()
|
|
157
|
+
conn.close()
|
|
158
|
+
return [dict(r) for r in rows]
|
|
159
|
+
except sqlite3.Error:
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
def get_by_engine(self, engine: str, max_results: int = 100) -> list[dict]:
|
|
163
|
+
try:
|
|
164
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
165
|
+
conn.row_factory = sqlite3.Row
|
|
166
|
+
cursor = conn.execute(
|
|
167
|
+
"SELECT * FROM sessions WHERE engine = ? ORDER BY last_used DESC LIMIT ?",
|
|
168
|
+
(engine, max_results),
|
|
169
|
+
)
|
|
170
|
+
rows = cursor.fetchall()
|
|
171
|
+
conn.close()
|
|
172
|
+
return [dict(r) for r in rows]
|
|
173
|
+
except sqlite3.Error:
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
def get_by_project(self, project_name: str, max_results: int = 100) -> list[dict]:
|
|
177
|
+
try:
|
|
178
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
179
|
+
conn.row_factory = sqlite3.Row
|
|
180
|
+
cursor = conn.execute(
|
|
181
|
+
"SELECT * FROM sessions WHERE project_name = ? ORDER BY last_used DESC LIMIT ?",
|
|
182
|
+
(project_name, max_results),
|
|
183
|
+
)
|
|
184
|
+
rows = cursor.fetchall()
|
|
185
|
+
conn.close()
|
|
186
|
+
return [dict(r) for r in rows]
|
|
187
|
+
except sqlite3.Error:
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
def delete(self, session_id: int) -> bool:
|
|
191
|
+
try:
|
|
192
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
193
|
+
conn.execute("DELETE FROM sessions WHERE id = ?", (session_id,))
|
|
194
|
+
conn.commit()
|
|
195
|
+
conn.close()
|
|
196
|
+
return True
|
|
197
|
+
except sqlite3.Error:
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
def count(self) -> int:
|
|
201
|
+
try:
|
|
202
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
203
|
+
cursor = conn.execute("SELECT COUNT(*) FROM sessions")
|
|
204
|
+
count = cursor.fetchone()[0]
|
|
205
|
+
conn.close()
|
|
206
|
+
return count
|
|
207
|
+
except sqlite3.Error:
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
def list_engines(self) -> list[str]:
|
|
211
|
+
try:
|
|
212
|
+
conn = sqlite3.connect(str(self.db_path))
|
|
213
|
+
cursor = conn.execute("SELECT DISTINCT engine FROM sessions ORDER BY engine")
|
|
214
|
+
rows = cursor.fetchall()
|
|
215
|
+
conn.close()
|
|
216
|
+
return [r[0] for r in rows]
|
|
217
|
+
except sqlite3.Error:
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
def import_from_tui_db(self, tui_db_path: Path) -> int:
|
|
221
|
+
"""Import sessions from a tui.db into the aggregator.
|
|
222
|
+
|
|
223
|
+
Returns the number of sessions imported.
|
|
224
|
+
"""
|
|
225
|
+
if not tui_db_path.exists():
|
|
226
|
+
logger.info("No tui.db found at %s", tui_db_path)
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
imported = 0
|
|
230
|
+
try:
|
|
231
|
+
tui_conn = sqlite3.connect(str(tui_db_path))
|
|
232
|
+
tui_conn.row_factory = sqlite3.Row
|
|
233
|
+
cursor = tui_conn.execute(
|
|
234
|
+
"SELECT * FROM sessions ORDER BY id"
|
|
235
|
+
)
|
|
236
|
+
rows = cursor.fetchall()
|
|
237
|
+
tui_conn.close()
|
|
238
|
+
|
|
239
|
+
for row in rows:
|
|
240
|
+
sess = dict(row)
|
|
241
|
+
engine = sess.get("agent_identity", "tui").lower()
|
|
242
|
+
engine_session_id = sess.get("agent_session_id", str(sess["id"]))
|
|
243
|
+
if self.get_by_engine_id(engine, engine_session_id) is None:
|
|
244
|
+
self.register(
|
|
245
|
+
engine=engine,
|
|
246
|
+
engine_session_id=engine_session_id,
|
|
247
|
+
agent=sess.get("agent", engine),
|
|
248
|
+
title=sess.get("title", f"Session {sess['id']}"),
|
|
249
|
+
protocol=sess.get("protocol", "acp"),
|
|
250
|
+
meta={"imported_from": str(tui_db_path), "original_id": sess["id"]},
|
|
251
|
+
)
|
|
252
|
+
imported += 1
|
|
253
|
+
|
|
254
|
+
logger.info("Imported %d sessions from %s", imported, tui_db_path)
|
|
255
|
+
except sqlite3.Error as e:
|
|
256
|
+
logger.error("Failed to import from tui.db: %s", e)
|
|
257
|
+
|
|
258
|
+
return imported
|