@scemoon/cdh 1.0.0
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 +75 -0
- package/ai-dlc-skill/agents/deliver-agent.md +24 -0
- package/ai-dlc-skill/agents/master.md +44 -0
- package/ai-dlc-skill/agents/plan-agent.md +23 -0
- package/ai-dlc-skill/agents/understand-agent.md +23 -0
- package/ai-dlc-skill/agents/verify-agent.md +27 -0
- package/ai-dlc-skill/architecture/project-structure.md +78 -0
- package/ai-dlc-skill/brownfield/README.md +30 -0
- package/ai-dlc-skill/brownfield/generate-context.sh +64 -0
- package/ai-dlc-skill/brownfield/scripts/deps.sh +27 -0
- package/ai-dlc-skill/brownfield/scripts/discover.sh +34 -0
- package/ai-dlc-skill/brownfield/scripts/extract-api.sh +45 -0
- package/ai-dlc-skill/components/backend.md +34 -0
- package/ai-dlc-skill/components/desktop.md +30 -0
- package/ai-dlc-skill/components/mya.md +29 -0
- package/ai-dlc-skill/components/native.md +30 -0
- package/ai-dlc-skill/components/tta.md +29 -0
- package/ai-dlc-skill/components/web.md +31 -0
- package/ai-dlc-skill/components/wxa.md +29 -0
- package/ai-dlc-skill/contracts/README.md +25 -0
- package/ai-dlc-skill/core/adaptive-flow.md +36 -0
- package/ai-dlc-skill/core/security.md +29 -0
- package/ai-dlc-skill/cross-tool/cline/export.sh +31 -0
- package/ai-dlc-skill/cross-tool/copilot/export.sh +47 -0
- package/ai-dlc-skill/cross-tool/cursor/export.sh +32 -0
- package/ai-dlc-skill/cross-tool/export.sh +22 -0
- package/ai-dlc-skill/cross-tool/onecode/config.yaml.tpl +17 -0
- package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +31 -0
- package/ai-dlc-skill/cross-tool/onecode/export.sh +48 -0
- package/ai-dlc-skill/phases/deliver/entry.md +26 -0
- package/ai-dlc-skill/phases/deliver/lifecycle.md +185 -0
- package/ai-dlc-skill/phases/deliver/prompt.md +40 -0
- package/ai-dlc-skill/phases/deliver/rules.md +61 -0
- package/ai-dlc-skill/phases/plan/entry.md +25 -0
- package/ai-dlc-skill/phases/plan/lifecycle.md +204 -0
- package/ai-dlc-skill/phases/plan/prompt.md +38 -0
- package/ai-dlc-skill/phases/plan/rules.md +25 -0
- package/ai-dlc-skill/phases/understand/entry.md +26 -0
- package/ai-dlc-skill/phases/understand/lifecycle.md +184 -0
- package/ai-dlc-skill/phases/understand/prompt.md +41 -0
- package/ai-dlc-skill/phases/understand/rules.md +37 -0
- package/ai-dlc-skill/phases/verify/entry.md +26 -0
- package/ai-dlc-skill/phases/verify/lifecycle.md +192 -0
- package/ai-dlc-skill/phases/verify/prompt.md +43 -0
- package/ai-dlc-skill/phases/verify/rules.md +73 -0
- package/ai-dlc-skill/practices/bdd.md +91 -0
- package/ai-dlc-skill/practices/sdd.md +84 -0
- package/ai-dlc-skill/practices/tdd.md +95 -0
- package/ai-dlc-skill/providers/README.md +114 -0
- package/ai-dlc-skill/providers/aliyun/deployment.yaml +66 -0
- package/ai-dlc-skill/providers/aliyun/preview.yaml +84 -0
- package/ai-dlc-skill/providers/aliyun/provider.yaml +160 -0
- package/ai-dlc-skill/providers/tcb/deployment.yaml +69 -0
- package/ai-dlc-skill/providers/tcb/preview.yaml +83 -0
- package/ai-dlc-skill/providers/tcb/provider.yaml +172 -0
- package/ai-dlc-skill/requirements.md +140 -0
- package/ai-dlc-skill/skill.yaml +147 -0
- package/ai-dlc-skill/templates/integration/CHANGELOG.md +41 -0
- package/ai-dlc-skill/templates/integration/asyncapi.yaml +43 -0
- package/ai-dlc-skill/templates/integration/contract-diff.md +63 -0
- package/ai-dlc-skill/templates/integration/contract-spec.md +49 -0
- package/ai-dlc-skill/templates/integration/openapi.yaml +18 -0
- package/ai-dlc-skill/templates/integration/runtime-contract.yaml +36 -0
- package/ai-dlc-skill/templates/plan/design.md +49 -0
- package/ai-dlc-skill/templates/plan/task-list.md +32 -0
- package/ai-dlc-skill/templates/project/README.md +127 -0
- package/ai-dlc-skill/templates/project/template.md +119 -0
- package/ai-dlc-skill/templates/understand/feature.feature +23 -0
- package/ai-dlc-skill/templates/understand/intent.md +29 -0
- package/ai-dlc-skill/templates/understand/spec-delta.md +47 -0
- package/ai-dlc-skill/templates/verify/test-unit.py +27 -0
- package/ai-dlc-skill/walkthrough/collect.sh +50 -0
- package/ai-dlc-skill/walkthrough/template.md +54 -0
- package/ai-dlc-skill/workflows/ai-dlc.yaml +110 -0
- package/ai-dlc-skill/workflows/deployment.yaml +199 -0
- package/cdh/__init__.py +0 -0
- package/cdh/__main__.py +4 -0
- package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
- package/cdh/__pycache__/__init__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +361 -0
- package/cdh/scaffold.py +312 -0
- package/cli.js +2 -0
- package/onecode/__init__.py +1 -0
- package/onecode/__main__.py +4 -0
- package/onecode/__pycache__/__init__.cpython-314.pyc +0 -0
- 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__/mock_data.cpython-314.pyc +0 -0
- package/onecode/agent/__init__.py +0 -0
- package/onecode/agent/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.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__/harness_skill.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__/permissions_store.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__init__.py +29 -0
- package/onecode/agent/agents/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +662 -0
- package/onecode/agent/attachments.py +113 -0
- package/onecode/agent/cdh_loader.py +262 -0
- package/onecode/agent/context.py +338 -0
- package/onecode/agent/engine.py +1894 -0
- package/onecode/agent/hooks.py +86 -0
- package/onecode/agent/onecode_agent_acp.py +2203 -0
- package/onecode/agent/permissions.py +151 -0
- package/onecode/agent/permissions_store.py +62 -0
- package/onecode/agent/session.py +186 -0
- package/onecode/agent/snapshot.py +145 -0
- package/onecode/agent/tools/__init__.py +17 -0
- package/onecode/agent/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- 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__/errors.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__/permissions.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__/schema_validation.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_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 +134 -0
- package/onecode/agent/tools/apply_patch_tool.py +144 -0
- package/onecode/agent/tools/bash_tool.py +91 -0
- package/onecode/agent/tools/communication_tools.py +149 -0
- package/onecode/agent/tools/config_tool.py +130 -0
- package/onecode/agent/tools/cron_tools.py +179 -0
- package/onecode/agent/tools/errors.py +17 -0
- package/onecode/agent/tools/file_ops.py +187 -0
- package/onecode/agent/tools/file_tools.py +226 -0
- package/onecode/agent/tools/git_tools.py +88 -0
- package/onecode/agent/tools/lsp_tools.py +404 -0
- package/onecode/agent/tools/mcp_tools.py +129 -0
- package/onecode/agent/tools/permission_handler.py +46 -0
- package/onecode/agent/tools/permissions.py +77 -0
- package/onecode/agent/tools/protocol.py +20 -0
- package/onecode/agent/tools/registry.py +134 -0
- package/onecode/agent/tools/sandbox.py +264 -0
- package/onecode/agent/tools/schema_validation.py +100 -0
- package/onecode/agent/tools/skill_tools.py +52 -0
- package/onecode/agent/tools/task_tools.py +249 -0
- package/onecode/agent/tools/web_tools.py +170 -0
- package/onecode/builtin_skills/git/SKILL.md +64 -0
- package/onecode/builtin_skills/git/skill.yaml +7 -0
- package/onecode/builtin_skills/shell/SKILL.md +58 -0
- package/onecode/builtin_skills/shell/skill.yaml +8 -0
- package/onecode/cli.py +755 -0
- package/onecode/cloud/__init__.py +0 -0
- package/onecode/cloud/base.py +20 -0
- package/onecode/codebase/__init__.py +69 -0
- package/onecode/codebase/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/config.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/__pycache__/tools.cpython-314.pyc +0 -0
- package/onecode/codebase/chunker.py +130 -0
- package/onecode/codebase/indexer.py +136 -0
- package/onecode/codebase/retriever.py +192 -0
- package/onecode/codebase/storage.py +130 -0
- package/onecode/codebase/tools.py +59 -0
- package/onecode/commands.py +276 -0
- package/onecode/config.py +285 -0
- package/onecode/config_screen.py +594 -0
- package/onecode/cron/__init__.py +3 -0
- package/onecode/lsp/__init__.py +3 -0
- package/onecode/mcp/__init__.py +4 -0
- package/onecode/mcp/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/mcp/__pycache__/client.cpython-314.pyc +0 -0
- package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/mcp/client.py +193 -0
- package/onecode/mcp/manager.py +191 -0
- package/onecode/memory/__init__.py +95 -0
- 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/__pycache__/symbolic.cpython-314.pyc +0 -0
- package/onecode/memory/backend.py +185 -0
- package/onecode/memory/offload.py +114 -0
- package/onecode/memory/pyramid.py +202 -0
- package/onecode/memory/recall.py +168 -0
- package/onecode/memory/symbolic.py +164 -0
- package/onecode/models/__init__.py +4 -0
- package/onecode/models/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/registry.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +94 -0
- package/onecode/models/messages.py +662 -0
- package/onecode/models/provider.py +450 -0
- package/onecode/models/providers/__init__.py +17 -0
- package/onecode/models/providers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_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__/mock_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +224 -0
- package/onecode/models/providers/deepseek_provider.py +210 -0
- package/onecode/models/providers/glm_provider.py +154 -0
- package/onecode/models/providers/minimax_provider.py +154 -0
- package/onecode/models/providers/minimaxi_provider.py +363 -0
- package/onecode/models/providers/ollama_provider.py +96 -0
- package/onecode/models/providers/openai_provider.py +233 -0
- package/onecode/models/registry.py +212 -0
- package/onecode/server/__init__.py +3 -0
- package/onecode/server/app.py +224 -0
- package/onecode/skills/__init__.py +11 -0
- package/onecode/skills/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/create.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/frontmatter.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/__pycache__/model.cpython-314.pyc +0 -0
- package/onecode/skills/argument_substitution.py +39 -0
- package/onecode/skills/create.py +56 -0
- package/onecode/skills/frontmatter.py +33 -0
- package/onecode/skills/loader.py +157 -0
- package/onecode/skills/manager.py +75 -0
- package/onecode/skills/model.py +43 -0
- package/onecode/storage/__init__.py +0 -0
- package/onecode/storage/activity.py +64 -0
- package/onecode/storage/project.py +54 -0
- package/onecode/storage/session.py +113 -0
- package/onecode/tasks/__init__.py +26 -0
- package/onecode/tasks/manager.py +219 -0
- package/onecode/tasks/models.py +240 -0
- package/onecode/trace/__init__.py +0 -0
- package/onecode/trace/tracer.py +174 -0
- package/onecode/utils/__init__.py +0 -0
- package/onecode/utils/helpers.py +20 -0
- package/onecode-cli.js +2 -0
- package/package.json +32 -0
- package/pyproject.toml +60 -0
- package/run.js +86 -0
- package/tui/__init__.py +46 -0
- package/tui/__main__.py +4 -0
- package/tui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/__pycache__/_loop.cpython-314.pyc +0 -0
- package/tui/__pycache__/_path_fuzzy_search.cpython-314.pyc +0 -0
- package/tui/__pycache__/_path_match.cpython-314.pyc +0 -0
- package/tui/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/__pycache__/agent_schema.cpython-314.pyc +0 -0
- package/tui/__pycache__/agents.cpython-314.pyc +0 -0
- package/tui/__pycache__/answer.cpython-314.pyc +0 -0
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/atomic.cpython-314.pyc +0 -0
- package/tui/__pycache__/complete.cpython-314.pyc +0 -0
- package/tui/__pycache__/constants.cpython-314.pyc +0 -0
- package/tui/__pycache__/conversation_markdown.cpython-314.pyc +0 -0
- package/tui/__pycache__/danger.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/dec.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/format_path.cpython-314.pyc +0 -0
- package/tui/__pycache__/fuzzy.cpython-314.pyc +0 -0
- package/tui/__pycache__/fuzzy_index.cpython-314.pyc +0 -0
- package/tui/__pycache__/history.cpython-314.pyc +0 -0
- package/tui/__pycache__/jsonrpc.cpython-314.pyc +0 -0
- package/tui/__pycache__/menus.cpython-314.pyc +0 -0
- package/tui/__pycache__/message_log.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__/path_filter.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/__pycache__/pill.cpython-314.pyc +0 -0
- package/tui/__pycache__/platform_commands.cpython-314.pyc +0 -0
- package/tui/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
- package/tui/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
- package/tui/__pycache__/shell.cpython-314.pyc +0 -0
- package/tui/__pycache__/shell_read.cpython-314.pyc +0 -0
- package/tui/__pycache__/slash_command.cpython-314.pyc +0 -0
- package/tui/__pycache__/version.cpython-314.pyc +0 -0
- package/tui/_loop.py +86 -0
- package/tui/_path_fuzzy_search.py +48 -0
- package/tui/_path_match.py +141 -0
- package/tui/about.py +95 -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__/encode_tool_call_id.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 +886 -0
- package/tui/acp/api.py +55 -0
- package/tui/acp/encode_tool_call_id.py +12 -0
- package/tui/acp/messages.py +190 -0
- package/tui/acp/prompt.py +110 -0
- package/tui/acp/protocol.py +462 -0
- package/tui/agent.py +70 -0
- package/tui/agent_schema.py +76 -0
- package/tui/agents.py +45 -0
- package/tui/ansi/__init__.py +1 -0
- package/tui/ansi/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_ansi.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_ansi_colors.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_control_codes.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_keys.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_sgr_styles.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_stream_parser.cpython-314.pyc +0 -0
- package/tui/ansi/_ansi.py +1689 -0
- package/tui/ansi/_ansi_colors.py +264 -0
- package/tui/ansi/_control_codes.py +37 -0
- package/tui/ansi/_keys.py +251 -0
- package/tui/ansi/_sgr_styles.py +64 -0
- package/tui/ansi/_stream_parser.py +418 -0
- package/tui/answer.py +12 -0
- package/tui/app.py +1236 -0
- package/tui/atomic.py +37 -0
- package/tui/cli.py +313 -0
- package/tui/code_analyze.py +27 -0
- package/tui/complete.py +38 -0
- package/tui/constants.py +61 -0
- package/tui/conversation_markdown.py +30 -0
- package/tui/danger.py +372 -0
- package/tui/data/agents/ampcode.com.toml +51 -0
- package/tui/data/agents/augmentcode.com.toml +40 -0
- package/tui/data/agents/cdh.cloud-dev-harness.toml +22 -0
- package/tui/data/agents/claude.com.toml +41 -0
- package/tui/data/agents/copilot.github.com.toml +29 -0
- package/tui/data/agents/crowai.dev.toml +35 -0
- package/tui/data/agents/cursor.com.toml +38 -0
- package/tui/data/agents/deepagents.langchain.com +33 -0
- package/tui/data/agents/docker.com.toml +59 -0
- package/tui/data/agents/geminicli.com.toml +28 -0
- package/tui/data/agents/goose.ai.toml +51 -0
- package/tui/data/agents/inference.huggingface.co.toml +41 -0
- package/tui/data/agents/kimi.com.toml +35 -0
- package/tui/data/agents/kiro.dev.toml +42 -0
- package/tui/data/agents/openai.com.toml +53 -0
- package/tui/data/agents/openclaw.ai.toml +37 -0
- package/tui/data/agents/opencode.ai.toml +39 -0
- package/tui/data/agents/openhands.dev.toml +45 -0
- package/tui/data/agents/stakpak.dev.toml +61 -0
- package/tui/data/agents/vibe.mistral.ai.toml +30 -0
- package/tui/data/agents/vtcode.dev.toml +62 -0
- package/tui/data/images/frog.png +0 -0
- package/tui/data/sounds/question.wav +0 -0
- package/tui/data/sounds/turn-over.wav +0 -0
- package/tui/db.py +190 -0
- package/tui/dec.py +332 -0
- package/tui/directory.py +234 -0
- package/tui/directory_suggester.py +93 -0
- package/tui/directory_watcher.py +197 -0
- package/tui/format_path.py +25 -0
- package/tui/fuzzy.py +140 -0
- package/tui/fuzzy_index.py +275 -0
- package/tui/gist.py +33 -0
- package/tui/history.py +138 -0
- package/tui/jsonrpc.py +578 -0
- package/tui/menus.py +14 -0
- package/tui/message_log.py +69 -0
- package/tui/messages.py +139 -0
- package/tui/option_content.py +51 -0
- package/tui/os.py +0 -0
- package/tui/path_complete.py +217 -0
- package/tui/path_filter.py +124 -0
- package/tui/paths.py +71 -0
- package/tui/pill.py +23 -0
- package/tui/platform_commands.py +52 -0
- package/tui/prompt/__pycache__/extract.cpython-314.pyc +0 -0
- package/tui/prompt/__pycache__/resource.cpython-314.pyc +0 -0
- package/tui/prompt/extract.py +19 -0
- package/tui/prompt/resource.py +68 -0
- package/tui/protocol.py +28 -0
- package/tui/screens/__init__.py +0 -0
- package/tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.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__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/action_modal.py +94 -0
- package/tui/screens/agent_modal.py +175 -0
- package/tui/screens/command_edit_modal.py +74 -0
- package/tui/screens/log.py +553 -0
- package/tui/screens/main.py +411 -0
- package/tui/screens/main.tcss +655 -0
- package/tui/screens/permissions.py +404 -0
- package/tui/screens/permissions.tcss +72 -0
- package/tui/screens/projects.tcss +66 -0
- package/tui/screens/projects_app.py +319 -0
- package/tui/screens/projects_screen.py +142 -0
- package/tui/screens/session_resume_modal.py +172 -0
- package/tui/screens/session_resume_modal.tcss +35 -0
- package/tui/screens/sessions.py +166 -0
- package/tui/screens/sessions.tcss +119 -0
- package/tui/screens/settings.py +254 -0
- package/tui/screens/settings.tcss +101 -0
- package/tui/screens/store.py +500 -0
- package/tui/screens/store.tcss +274 -0
- package/tui/screens/tool_content_screen.py +59 -0
- package/tui/session_tracker.py +120 -0
- package/tui/settings.py +353 -0
- package/tui/settings_schema.py +431 -0
- package/tui/shell.py +304 -0
- package/tui/shell_read.py +42 -0
- package/tui/slash_command.py +34 -0
- package/tui/tui.tcss +246 -0
- package/tui/twitter.py +38 -0
- package/tui/version.py +80 -0
- package/tui/visuals/__init__.py +0 -0
- package/tui/visuals/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
- package/tui/visuals/columns.py +273 -0
- package/tui/widgets/__init__.py +0 -0
- package/tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
- 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__/ask_user.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/condensed_path.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/context_stats.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-313.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__/flash.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__/highlighted_textarea.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__/menu.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/non_selectable_label.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/note.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/path_search.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_directory_tree.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__/prompt.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.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__/session_tabs.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_terminal.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/slash_complete.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/strike_text.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/throbber.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/user_input.cpython-314.pyc +0 -0
- package/tui/widgets/acp_content.py +70 -0
- package/tui/widgets/agent_response.py +77 -0
- package/tui/widgets/agent_thought.py +168 -0
- package/tui/widgets/ask_user.py +264 -0
- package/tui/widgets/command_pane.py +223 -0
- package/tui/widgets/condensed_path.py +115 -0
- package/tui/widgets/context_stats.py +66 -0
- package/tui/widgets/conversation.py +2441 -0
- package/tui/widgets/danger_warning.py +65 -0
- package/tui/widgets/diff_view.py +59 -0
- package/tui/widgets/directory_input.py +8 -0
- package/tui/widgets/flash.py +81 -0
- package/tui/widgets/future_text.py +126 -0
- package/tui/widgets/grid_select.py +234 -0
- package/tui/widgets/highlighted_textarea.py +180 -0
- package/tui/widgets/mandelbrot.py +294 -0
- package/tui/widgets/markdown_note.py +13 -0
- package/tui/widgets/menu.py +147 -0
- package/tui/widgets/modified_files.py +106 -0
- package/tui/widgets/non_selectable_label.py +5 -0
- package/tui/widgets/note.py +18 -0
- package/tui/widgets/path_search.py +492 -0
- package/tui/widgets/plan.py +263 -0
- package/tui/widgets/project_directory_tree.py +90 -0
- package/tui/widgets/project_grid_select.py +35 -0
- package/tui/widgets/project_summary.py +33 -0
- package/tui/widgets/prompt.py +795 -0
- package/tui/widgets/question.py +360 -0
- package/tui/widgets/session_grid_select.py +224 -0
- package/tui/widgets/session_summary.py +115 -0
- package/tui/widgets/session_tabs.py +188 -0
- package/tui/widgets/shell_result.py +35 -0
- package/tui/widgets/shell_terminal.py +40 -0
- package/tui/widgets/side_bar.py +114 -0
- package/tui/widgets/slash_complete.py +235 -0
- package/tui/widgets/strike_text.py +70 -0
- package/tui/widgets/subagent.py +87 -0
- package/tui/widgets/terminal.py +577 -0
- package/tui/widgets/terminal_tool.py +351 -0
- package/tui/widgets/throbber.py +100 -0
- package/tui/widgets/tool_call.py +368 -0
- package/tui/widgets/user_input.py +23 -0
- package/tui/widgets/version.py +5 -0
- package/tui/widgets/welcome.py +31 -0
package/tui/acp/agent.py
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger("tui.acp.agent")
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, cast, NamedTuple
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
|
|
13
|
+
import rich.repr
|
|
14
|
+
|
|
15
|
+
from textual.content import Content
|
|
16
|
+
from textual.message import Message
|
|
17
|
+
from textual.message_pump import MessagePump
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from tui import jsonrpc
|
|
21
|
+
import tui
|
|
22
|
+
from tui.agent_schema import Agent as AgentData
|
|
23
|
+
from tui.agent import AgentBase, AgentReady, AgentFail
|
|
24
|
+
from tui.acp import protocol
|
|
25
|
+
from tui.acp import api
|
|
26
|
+
from tui.acp.api import API
|
|
27
|
+
from tui.acp import messages
|
|
28
|
+
from tui.acp.prompt import build as build_prompt
|
|
29
|
+
from tui import messages as tui_messages
|
|
30
|
+
from tui.db import DB
|
|
31
|
+
from tui import paths
|
|
32
|
+
from tui import constants
|
|
33
|
+
from tui.answer import Answer
|
|
34
|
+
|
|
35
|
+
PROTOCOL_VERSION = 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Mode(NamedTuple):
|
|
39
|
+
"""An agent mode."""
|
|
40
|
+
|
|
41
|
+
id: str
|
|
42
|
+
name: str
|
|
43
|
+
description: str | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@rich.repr.auto
|
|
47
|
+
class Agent(AgentBase):
|
|
48
|
+
"""An agent that speaks the APC (https://agentclientprotocol.com/overview/introduction) protocol."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
project_root: Path,
|
|
53
|
+
agent: AgentData,
|
|
54
|
+
session_id: str | None,
|
|
55
|
+
session_pk: int | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
project_root: Project root path.
|
|
61
|
+
command: Command to launch agent.
|
|
62
|
+
"""
|
|
63
|
+
super().__init__(project_root)
|
|
64
|
+
|
|
65
|
+
self._agent_data = agent
|
|
66
|
+
self.session_id = session_id
|
|
67
|
+
|
|
68
|
+
self.server = jsonrpc.Server()
|
|
69
|
+
self.server.expose_instance(self)
|
|
70
|
+
|
|
71
|
+
self._agent_task: asyncio.Task | None = None
|
|
72
|
+
self._task: asyncio.Task | None = None
|
|
73
|
+
self._process: asyncio.subprocess.Process | None = None
|
|
74
|
+
self.done_event = asyncio.Event()
|
|
75
|
+
|
|
76
|
+
self.agent_capabilities: protocol.AgentCapabilities = {
|
|
77
|
+
"loadSession": False,
|
|
78
|
+
"promptCapabilities": {
|
|
79
|
+
"audio": False,
|
|
80
|
+
"embeddedContent": True,
|
|
81
|
+
"image": True,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
self.auth_methods: list[protocol.AuthMethod] = []
|
|
85
|
+
self.session_pk: int | None = session_pk
|
|
86
|
+
self._pending_session_data: dict | None = None
|
|
87
|
+
self.tool_calls: dict[str, protocol.ToolCall] = {}
|
|
88
|
+
self._message_target: MessagePump | None = None
|
|
89
|
+
|
|
90
|
+
self._terminal_count: int = 0
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def command(self) -> str | None:
|
|
94
|
+
"""The command used to launch the agent, or `None` if there isn't one."""
|
|
95
|
+
acp_command = tui.get_os_matrix(self._agent_data["run_command"])
|
|
96
|
+
return acp_command
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def supports_load_session(self) -> bool:
|
|
100
|
+
"""Does the agent support loading sessions?"""
|
|
101
|
+
return self.agent_capabilities.get("loadSession", False)
|
|
102
|
+
|
|
103
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
104
|
+
yield self.project_root_path
|
|
105
|
+
yield self.command
|
|
106
|
+
|
|
107
|
+
def get_info(self) -> Content:
|
|
108
|
+
agent_name = self._agent_data["name"]
|
|
109
|
+
return Content(agent_name)
|
|
110
|
+
|
|
111
|
+
async def start(self, message_target: MessagePump | None = None) -> None:
|
|
112
|
+
"""Start the agent."""
|
|
113
|
+
self._message_target = message_target
|
|
114
|
+
self._agent_task = asyncio.create_task(self._run_agent())
|
|
115
|
+
|
|
116
|
+
def send(self, request: jsonrpc.Request) -> None:
|
|
117
|
+
"""Send a request to the agent.
|
|
118
|
+
|
|
119
|
+
This is called automatically, if you go through `self.request`.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
request: JSONRPC request object.
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
assert self._process is not None, "Process should be present here"
|
|
126
|
+
|
|
127
|
+
logger.debug("[client] %s", request.body)
|
|
128
|
+
if (stdin := self._process.stdin) is not None:
|
|
129
|
+
stdin.write(b"%s\n" % request.body_json)
|
|
130
|
+
|
|
131
|
+
def request(self) -> jsonrpc.Request:
|
|
132
|
+
"""Create a request object."""
|
|
133
|
+
return API.request(self.send)
|
|
134
|
+
|
|
135
|
+
def post_message(self, message: Message) -> bool:
|
|
136
|
+
"""Post a message to the message target (the Conversation).
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
message: Message object.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
`True` if the message was posted successfully, or `False` if it wasn't.
|
|
143
|
+
"""
|
|
144
|
+
if (message_target := self._message_target) is None:
|
|
145
|
+
return False
|
|
146
|
+
return message_target.post_message(message)
|
|
147
|
+
|
|
148
|
+
@jsonrpc.expose("session/update")
|
|
149
|
+
def rpc_session_update(
|
|
150
|
+
self,
|
|
151
|
+
sessionId: str,
|
|
152
|
+
update: protocol.SessionUpdate,
|
|
153
|
+
_meta: dict[str, Any] | None = None,
|
|
154
|
+
):
|
|
155
|
+
"""Agent requests an update.
|
|
156
|
+
|
|
157
|
+
https://agentclientprotocol.com/protocol/schema
|
|
158
|
+
"""
|
|
159
|
+
status_line: str | None = None
|
|
160
|
+
if _meta and (field_meta := _meta.get("field_meta")) is not None:
|
|
161
|
+
if (
|
|
162
|
+
open_hands_metrics := field_meta.get("openhands.dev/metrics")
|
|
163
|
+
) is not None:
|
|
164
|
+
status_line = open_hands_metrics.get("status_line")
|
|
165
|
+
|
|
166
|
+
match update:
|
|
167
|
+
case {
|
|
168
|
+
"sessionUpdate": "user_message_chunk",
|
|
169
|
+
"content": {"type": type, "text": text},
|
|
170
|
+
}:
|
|
171
|
+
if text:
|
|
172
|
+
self.post_message(messages.UserMessage(type, text))
|
|
173
|
+
|
|
174
|
+
case {
|
|
175
|
+
"sessionUpdate": "agent_message_chunk",
|
|
176
|
+
"content": {"type": type, "text": text},
|
|
177
|
+
}:
|
|
178
|
+
self.post_message(messages.Update(type, text))
|
|
179
|
+
|
|
180
|
+
case {
|
|
181
|
+
"sessionUpdate": "agent_thought_chunk",
|
|
182
|
+
"content": {"type": type, "text": text},
|
|
183
|
+
}:
|
|
184
|
+
self.post_message(messages.Thinking(type, text))
|
|
185
|
+
|
|
186
|
+
case {
|
|
187
|
+
"sessionUpdate": "tool_call",
|
|
188
|
+
"toolCallId": tool_call_id,
|
|
189
|
+
}:
|
|
190
|
+
logger.debug(
|
|
191
|
+
"session/update: tool_call id=%s title=%s kind=%s status=%s content_blocks=%d",
|
|
192
|
+
tool_call_id,
|
|
193
|
+
update.get('title','?'), update.get('kind','?'),
|
|
194
|
+
update.get('status','?'), len(update.get('content',[]) or []),
|
|
195
|
+
)
|
|
196
|
+
self.tool_calls[tool_call_id] = deepcopy(update)
|
|
197
|
+
self.post_message(messages.ToolCall(deepcopy(update)))
|
|
198
|
+
|
|
199
|
+
case {"sessionUpdate": "plan", "entries": entries}:
|
|
200
|
+
self.post_message(messages.Plan(entries))
|
|
201
|
+
|
|
202
|
+
case {
|
|
203
|
+
"sessionUpdate": "tool_call_update",
|
|
204
|
+
"toolCallId": tool_call_id,
|
|
205
|
+
}:
|
|
206
|
+
if tool_call_id in self.tool_calls:
|
|
207
|
+
current_tool_call = self.tool_calls[tool_call_id]
|
|
208
|
+
for key, value in update.items():
|
|
209
|
+
if value is not None:
|
|
210
|
+
current_tool_call[key] = value
|
|
211
|
+
|
|
212
|
+
logger.debug(
|
|
213
|
+
"session/update: tool_call_update id=%s status=%s content_blocks=%d",
|
|
214
|
+
tool_call_id,
|
|
215
|
+
update.get('status','?'),
|
|
216
|
+
len(update.get('content',[]) or []),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
self.post_message(
|
|
220
|
+
messages.ToolCallUpdate(deepcopy(current_tool_call), update)
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
logger.debug(
|
|
224
|
+
"session/update: tool_call_update ORPHAN id=%s "
|
|
225
|
+
"(no prior tool_call, creating widget from update)",
|
|
226
|
+
tool_call_id,
|
|
227
|
+
)
|
|
228
|
+
current_tool_call: protocol.ToolCall = {
|
|
229
|
+
"sessionUpdate": "tool_call",
|
|
230
|
+
"toolCallId": tool_call_id,
|
|
231
|
+
"title": "Tool call",
|
|
232
|
+
}
|
|
233
|
+
for key, value in update.items():
|
|
234
|
+
if value is not None:
|
|
235
|
+
current_tool_call[key] = value
|
|
236
|
+
|
|
237
|
+
self.tool_calls[tool_call_id] = current_tool_call
|
|
238
|
+
self.post_message(messages.ToolCall(deepcopy(current_tool_call)))
|
|
239
|
+
|
|
240
|
+
case {
|
|
241
|
+
"sessionUpdate": "usage_update",
|
|
242
|
+
}:
|
|
243
|
+
self.post_message(messages.ContextUpdate(
|
|
244
|
+
used=update.get("used", 0),
|
|
245
|
+
size=update.get("size", 0),
|
|
246
|
+
))
|
|
247
|
+
|
|
248
|
+
case {
|
|
249
|
+
"sessionUpdate": "ask_user",
|
|
250
|
+
"question": question,
|
|
251
|
+
}:
|
|
252
|
+
self.post_message(messages.AskUser(
|
|
253
|
+
question=question,
|
|
254
|
+
context=update.get("context", ""),
|
|
255
|
+
options=update.get("options", []),
|
|
256
|
+
questions=update.get("questions", []),
|
|
257
|
+
tool_id=update.get("toolId", ""),
|
|
258
|
+
))
|
|
259
|
+
|
|
260
|
+
case {
|
|
261
|
+
"sessionUpdate": "ask_user",
|
|
262
|
+
"questions": questions,
|
|
263
|
+
} if questions:
|
|
264
|
+
self.post_message(messages.AskUser(
|
|
265
|
+
questions=questions,
|
|
266
|
+
context=update.get("context", ""),
|
|
267
|
+
tool_id=update.get("toolId", ""),
|
|
268
|
+
))
|
|
269
|
+
|
|
270
|
+
case {
|
|
271
|
+
"sessionUpdate": "available_commands_update",
|
|
272
|
+
"availableCommands": available_commands,
|
|
273
|
+
}:
|
|
274
|
+
self.post_message(messages.AvailableCommandsUpdate(available_commands))
|
|
275
|
+
|
|
276
|
+
case {
|
|
277
|
+
"sessionUpdate": "subagent_start",
|
|
278
|
+
"subagentId": subagent_id,
|
|
279
|
+
"agentType": agent_type,
|
|
280
|
+
}:
|
|
281
|
+
self.post_message(messages.SubAgentStart(subagent_id, agent_type))
|
|
282
|
+
|
|
283
|
+
case {
|
|
284
|
+
"sessionUpdate": "subagent_chunk",
|
|
285
|
+
"subagentId": subagent_id,
|
|
286
|
+
"text": text,
|
|
287
|
+
}:
|
|
288
|
+
self.post_message(messages.SubAgentChunk(subagent_id, text))
|
|
289
|
+
|
|
290
|
+
case {
|
|
291
|
+
"sessionUpdate": "subagent_end",
|
|
292
|
+
"subagentId": subagent_id,
|
|
293
|
+
"agentType": agent_type,
|
|
294
|
+
}:
|
|
295
|
+
self.post_message(messages.SubAgentEnd(subagent_id, agent_type))
|
|
296
|
+
|
|
297
|
+
if status_line is not None:
|
|
298
|
+
self.post_message(messages.UpdateStatusLine(status_line))
|
|
299
|
+
|
|
300
|
+
@jsonrpc.expose("session/request_permission")
|
|
301
|
+
async def rpc_request_permission(
|
|
302
|
+
self,
|
|
303
|
+
sessionId: str,
|
|
304
|
+
options: list[protocol.PermissionOption],
|
|
305
|
+
toolCall: protocol.ToolCallUpdatePermissionRequest,
|
|
306
|
+
_meta: dict | None = None,
|
|
307
|
+
) -> protocol.RequestPermissionResponse:
|
|
308
|
+
"""Agent requests permission to make a tool call.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
sessionId: The session ID.
|
|
312
|
+
options: A list of permission options (potential replies).
|
|
313
|
+
toolCall: The tool or tools the agent is requesting permission to call.
|
|
314
|
+
_meta: Optional meta information.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
The response to the permission request.
|
|
318
|
+
"""
|
|
319
|
+
result_future: asyncio.Future[Answer] = asyncio.Future()
|
|
320
|
+
tool_call_id = toolCall["toolCallId"]
|
|
321
|
+
|
|
322
|
+
permission_tool_call = toolCall.copy()
|
|
323
|
+
permission_tool_call.pop("sessionUpdate", None)
|
|
324
|
+
tool_call = cast(protocol.ToolCall, permission_tool_call)
|
|
325
|
+
if tool_call_id in self.tool_calls:
|
|
326
|
+
self.tool_calls[tool_call_id] |= tool_call
|
|
327
|
+
else:
|
|
328
|
+
self.tool_calls[tool_call_id] = deepcopy(tool_call)
|
|
329
|
+
|
|
330
|
+
# Forward content to the ToolCall widget so the user can see what
|
|
331
|
+
# the agent is requesting permission for (e.g. the bash command).
|
|
332
|
+
merged = deepcopy(self.tool_calls[tool_call_id])
|
|
333
|
+
self.post_message(messages.ToolCallUpdate(merged, tool_call))
|
|
334
|
+
|
|
335
|
+
message = messages.RequestPermission(options, merged, result_future)
|
|
336
|
+
self.post_message(message)
|
|
337
|
+
await result_future
|
|
338
|
+
ask_result = result_future.result()
|
|
339
|
+
|
|
340
|
+
request_permission_outcome: protocol.OutcomeSelected = {
|
|
341
|
+
"optionId": ask_result.id,
|
|
342
|
+
"outcome": "selected",
|
|
343
|
+
}
|
|
344
|
+
result: protocol.RequestPermissionResponse = {
|
|
345
|
+
"outcome": request_permission_outcome
|
|
346
|
+
}
|
|
347
|
+
return result
|
|
348
|
+
|
|
349
|
+
def send_ask_user_answer(self, answer: str, cancelled: bool) -> None:
|
|
350
|
+
"""Send the user's answer back to the CDHA agent."""
|
|
351
|
+
import uuid
|
|
352
|
+
|
|
353
|
+
request = {
|
|
354
|
+
"jsonrpc": "2.0",
|
|
355
|
+
"method": "session/ask_user_answer",
|
|
356
|
+
"params": {"answer": answer, "cancelled": cancelled},
|
|
357
|
+
"id": str(uuid.uuid4()),
|
|
358
|
+
}
|
|
359
|
+
if self._process is not None and self._process.stdin is not None:
|
|
360
|
+
self._process.stdin.write(b"%s\n" % json.dumps(request).encode("utf-8"))
|
|
361
|
+
|
|
362
|
+
@jsonrpc.expose("fs/read_text_file")
|
|
363
|
+
def rpc_read_text_file(
|
|
364
|
+
self,
|
|
365
|
+
sessionId: str,
|
|
366
|
+
path: str,
|
|
367
|
+
line: int | None = None,
|
|
368
|
+
limit: int | None = None,
|
|
369
|
+
) -> dict[str, str]:
|
|
370
|
+
"""Read a file in the project."""
|
|
371
|
+
# TODO: what if the read is outside of the project path?
|
|
372
|
+
# https://agentclientprotocol.com/protocol/file-system#reading-files
|
|
373
|
+
read_path = self.project_root_path / path
|
|
374
|
+
try:
|
|
375
|
+
text = read_path.read_text(encoding="utf-8", errors="ignore")
|
|
376
|
+
except IOError:
|
|
377
|
+
text = ""
|
|
378
|
+
if line is not None:
|
|
379
|
+
line = max(0, line - 1)
|
|
380
|
+
if limit is None:
|
|
381
|
+
text = "\n".join(text.splitlines()[line:])
|
|
382
|
+
else:
|
|
383
|
+
text = "\n".join(text.splitlines()[line : line + limit])
|
|
384
|
+
return {"content": text}
|
|
385
|
+
|
|
386
|
+
@jsonrpc.expose("fs/write_text_file")
|
|
387
|
+
def rpc_write_text_file(self, sessionId: str, path: str, content: str) -> None:
|
|
388
|
+
# TODO: What if the agent wants to write outside of the project path?
|
|
389
|
+
# https://agentclientprotocol.com/protocol/file-system#writing-files
|
|
390
|
+
|
|
391
|
+
write_path = self.project_root_path / path
|
|
392
|
+
write_path.write_text(content, encoding="utf-8", errors="ignore")
|
|
393
|
+
|
|
394
|
+
# https://agentclientprotocol.com/protocol/schema#createterminalrequest
|
|
395
|
+
@jsonrpc.expose("terminal/create")
|
|
396
|
+
async def rpc_terminal_create(
|
|
397
|
+
self,
|
|
398
|
+
command: str,
|
|
399
|
+
_meta: dict | None = None,
|
|
400
|
+
args: list[str] | None = None,
|
|
401
|
+
cwd: str | None = None,
|
|
402
|
+
env: list[protocol.EnvVariable] | None = None,
|
|
403
|
+
outputByteLimit: int | None = None,
|
|
404
|
+
sessionId: str | None = None,
|
|
405
|
+
) -> protocol.CreateTerminalResponse:
|
|
406
|
+
# Assign a terminal id
|
|
407
|
+
self._terminal_count = self._terminal_count + 1
|
|
408
|
+
terminal_id = f"terminal-{self._terminal_count}"
|
|
409
|
+
|
|
410
|
+
terminal_env = (
|
|
411
|
+
{variable["name"]: variable["value"] for variable in env} if env else {}
|
|
412
|
+
)
|
|
413
|
+
result_future: asyncio.Future[bool] = asyncio.Future()
|
|
414
|
+
self.post_message(
|
|
415
|
+
messages.CreateTerminal(
|
|
416
|
+
terminal_id,
|
|
417
|
+
command=command,
|
|
418
|
+
args=args,
|
|
419
|
+
cwd=cwd,
|
|
420
|
+
env=terminal_env,
|
|
421
|
+
output_byte_limit=outputByteLimit,
|
|
422
|
+
result_future=result_future,
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
await result_future
|
|
426
|
+
if not result_future.result():
|
|
427
|
+
raise jsonrpc.JSONRPCError("Failed to create a terminal.")
|
|
428
|
+
return {"terminalId": terminal_id}
|
|
429
|
+
|
|
430
|
+
# https://agentclientprotocol.com/protocol/schema#killterminalcommandrequest
|
|
431
|
+
@jsonrpc.expose("terminal/kill")
|
|
432
|
+
def rpc_terminal_kill(
|
|
433
|
+
self, sessionID: str, terminalId: str, _meta: dict | None = None
|
|
434
|
+
) -> protocol.KillTerminalCommandResponse:
|
|
435
|
+
self.post_message(messages.KillTerminal(terminalId))
|
|
436
|
+
return {}
|
|
437
|
+
|
|
438
|
+
# https://agentclientprotocol.com/protocol/schema#terminal%2Foutput
|
|
439
|
+
@jsonrpc.expose("terminal/output")
|
|
440
|
+
async def rpc_terminal_output(
|
|
441
|
+
self, sessionId: str, terminalId: str, _meta: dict | None = None
|
|
442
|
+
) -> protocol.TerminalOutputResponse:
|
|
443
|
+
from tui.widgets.terminal_tool import ToolState
|
|
444
|
+
|
|
445
|
+
result_future: asyncio.Future[ToolState] = asyncio.Future()
|
|
446
|
+
|
|
447
|
+
if not self.post_message(messages.GetTerminalState(terminalId, result_future)):
|
|
448
|
+
raise RuntimeError("Unable to get terminal output")
|
|
449
|
+
|
|
450
|
+
await result_future
|
|
451
|
+
terminal_state = result_future.result()
|
|
452
|
+
|
|
453
|
+
result: protocol.TerminalOutputResponse = {
|
|
454
|
+
"output": terminal_state.output,
|
|
455
|
+
"truncated": terminal_state.truncated,
|
|
456
|
+
}
|
|
457
|
+
if (return_code := terminal_state.return_code) is not None:
|
|
458
|
+
result["exitStatus"] = {"exitCode": return_code}
|
|
459
|
+
return result
|
|
460
|
+
|
|
461
|
+
# https://agentclientprotocol.com/protocol/schema#terminal%2Frelease
|
|
462
|
+
@jsonrpc.expose("terminal/release")
|
|
463
|
+
def rpc_terminal_release(
|
|
464
|
+
self, sessionId: str, terminalId: str, _meta: dict | None = None
|
|
465
|
+
) -> protocol.ReleaseTerminalResponse:
|
|
466
|
+
self.post_message(messages.ReleaseTerminal(terminalId))
|
|
467
|
+
return {}
|
|
468
|
+
|
|
469
|
+
# https://agentclientprotocol.com/protocol/schema#terminal%2Fwait-for-exit
|
|
470
|
+
@jsonrpc.expose("terminal/wait_for_exit")
|
|
471
|
+
async def rpc_terminal_wait_for_exit(
|
|
472
|
+
self, sessionId: str, terminalId: str, _meta: dict | None = None
|
|
473
|
+
) -> protocol.WaitForTerminalExitResponse:
|
|
474
|
+
result_future: asyncio.Future[tuple[int, str | None]] = asyncio.Future()
|
|
475
|
+
if not self.post_message(
|
|
476
|
+
messages.WaitForTerminalExit(terminalId, result_future)
|
|
477
|
+
):
|
|
478
|
+
raise RuntimeError("Unable to wait for terminal exit; no terminal found")
|
|
479
|
+
|
|
480
|
+
await result_future
|
|
481
|
+
return_code, signal = result_future.result()
|
|
482
|
+
return {"exitCode": return_code, "signal": signal}
|
|
483
|
+
|
|
484
|
+
async def _run_agent(self) -> None:
|
|
485
|
+
"""Task to communicate with the agent subprocess."""
|
|
486
|
+
|
|
487
|
+
PIPE = asyncio.subprocess.PIPE
|
|
488
|
+
env = os.environ.copy()
|
|
489
|
+
env["TOAD_CWD"] = str(self.project_root_path)
|
|
490
|
+
python_bin = str(Path(sys.executable).parent)
|
|
491
|
+
env["PATH"] = f"{python_bin}:{env.get('PATH', '')}"
|
|
492
|
+
|
|
493
|
+
if (command := self.command) is None:
|
|
494
|
+
self.post_message(
|
|
495
|
+
AgentFail("Failed to start agent; no run command for this OS")
|
|
496
|
+
)
|
|
497
|
+
return
|
|
498
|
+
try:
|
|
499
|
+
process = self._process = await asyncio.create_subprocess_shell(
|
|
500
|
+
command,
|
|
501
|
+
stdin=PIPE,
|
|
502
|
+
stdout=PIPE,
|
|
503
|
+
stderr=PIPE,
|
|
504
|
+
env=env,
|
|
505
|
+
cwd=str(self.project_root_path),
|
|
506
|
+
limit=10 * 1024 * 1024,
|
|
507
|
+
)
|
|
508
|
+
except Exception as error:
|
|
509
|
+
self.post_message(AgentFail("Failed to start agent", details=str(error)))
|
|
510
|
+
return
|
|
511
|
+
|
|
512
|
+
self._task = asyncio.create_task(self.run())
|
|
513
|
+
|
|
514
|
+
assert process.stdout is not None
|
|
515
|
+
assert process.stdin is not None
|
|
516
|
+
|
|
517
|
+
tasks: set[asyncio.Task] = set()
|
|
518
|
+
|
|
519
|
+
async def call_jsonrpc(request: jsonrpc.JSONObject | jsonrpc.JSONList) -> None:
|
|
520
|
+
try:
|
|
521
|
+
if (result := await self.server.call(request)) is not None:
|
|
522
|
+
result_json = json.dumps(result).encode("utf-8")
|
|
523
|
+
if process.stdin is not None:
|
|
524
|
+
process.stdin.write(b"%s\n" % result_json)
|
|
525
|
+
finally:
|
|
526
|
+
if (task := asyncio.current_task()) is not None:
|
|
527
|
+
tasks.discard(task)
|
|
528
|
+
|
|
529
|
+
while line := await process.stdout.readline():
|
|
530
|
+
# This line should contain JSON, which may be:
|
|
531
|
+
# A) a JSONRPC request
|
|
532
|
+
# B) a JSONRPC response to a previous request
|
|
533
|
+
if not line.strip():
|
|
534
|
+
continue
|
|
535
|
+
|
|
536
|
+
try:
|
|
537
|
+
line_str = line.decode("utf-8")
|
|
538
|
+
except Exception as error:
|
|
539
|
+
logger.error("Unable to decode utf-8 from agent: %s", error)
|
|
540
|
+
continue
|
|
541
|
+
|
|
542
|
+
logger.debug("[agent] %s", line_str.rstrip())
|
|
543
|
+
try:
|
|
544
|
+
agent_data: jsonrpc.JSONType = json.loads(line_str)
|
|
545
|
+
except Exception as error:
|
|
546
|
+
logger.error("Failed to decode JSON from agent: %s", error)
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
if isinstance(agent_data, dict):
|
|
550
|
+
if "result" in agent_data or "error" in agent_data:
|
|
551
|
+
# Wait for pending notification dispatch tasks so all ACP
|
|
552
|
+
# messages are queued before the response future resolves.
|
|
553
|
+
if tasks:
|
|
554
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
555
|
+
tasks.clear()
|
|
556
|
+
API.process_response(agent_data)
|
|
557
|
+
continue
|
|
558
|
+
|
|
559
|
+
elif isinstance(agent_data, list):
|
|
560
|
+
if not all(isinstance(datum, dict) for datum in agent_data):
|
|
561
|
+
logger.error("Agent sent invalid data: %r", agent_data)
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
if not isinstance(agent_data, dict):
|
|
565
|
+
logger.error("Invalid JSON from agent: %r", agent_data)
|
|
566
|
+
continue
|
|
567
|
+
|
|
568
|
+
if not isinstance(agent_data, dict):
|
|
569
|
+
logger.error("Invalid JSON from agent: %r", agent_data)
|
|
570
|
+
continue
|
|
571
|
+
|
|
572
|
+
# By this point we know it is a JSON RPC call
|
|
573
|
+
assert isinstance(agent_data, dict)
|
|
574
|
+
tasks.add(asyncio.create_task(call_jsonrpc(agent_data)))
|
|
575
|
+
await asyncio.sleep(0)
|
|
576
|
+
|
|
577
|
+
# Cancel all remaining tasks and wait for them to finish
|
|
578
|
+
for task in tasks:
|
|
579
|
+
task.cancel()
|
|
580
|
+
|
|
581
|
+
# Wait for all tasks to complete cancellation
|
|
582
|
+
if tasks:
|
|
583
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
584
|
+
|
|
585
|
+
if process.returncode:
|
|
586
|
+
assert process.stderr is not None
|
|
587
|
+
fail_details = (await process.stderr.read()).decode("utf-8", "replace")
|
|
588
|
+
self.post_message(
|
|
589
|
+
AgentFail(
|
|
590
|
+
f"Agent returned a failure code: [b]{process.returncode}",
|
|
591
|
+
details=fail_details,
|
|
592
|
+
)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
self._process = None
|
|
596
|
+
|
|
597
|
+
async def stop(self) -> None:
|
|
598
|
+
"""Gracefully stop the process."""
|
|
599
|
+
if self.session_pk is not None:
|
|
600
|
+
db = DB()
|
|
601
|
+
await db.session_update_last_used(self.session_pk)
|
|
602
|
+
|
|
603
|
+
if self._process is not None:
|
|
604
|
+
try:
|
|
605
|
+
self._process.terminate()
|
|
606
|
+
except OSError:
|
|
607
|
+
pass
|
|
608
|
+
|
|
609
|
+
async def run(self) -> None:
|
|
610
|
+
"""The main logic of the Agent."""
|
|
611
|
+
if constants.ACP_INITIALIZE:
|
|
612
|
+
try:
|
|
613
|
+
await self.acp_initialize()
|
|
614
|
+
|
|
615
|
+
if self.session_id is None:
|
|
616
|
+
await self.acp_new_session()
|
|
617
|
+
else:
|
|
618
|
+
if not self.agent_capabilities.get("loadSession", False):
|
|
619
|
+
self.post_message(
|
|
620
|
+
AgentFail(
|
|
621
|
+
"Resume not supported",
|
|
622
|
+
f"{self._agent_data['name']} does not currently support resuming sessions.",
|
|
623
|
+
help="no_resume",
|
|
624
|
+
)
|
|
625
|
+
)
|
|
626
|
+
return
|
|
627
|
+
await self.acp_load_session()
|
|
628
|
+
if self.session_pk is not None:
|
|
629
|
+
db = DB()
|
|
630
|
+
await db.session_update_last_used(self.session_pk)
|
|
631
|
+
except jsonrpc.APIError as error:
|
|
632
|
+
if isinstance(error.data, dict):
|
|
633
|
+
reason = str(
|
|
634
|
+
error.data.get("reason") or "Failed to initialize agent"
|
|
635
|
+
)
|
|
636
|
+
details = str(
|
|
637
|
+
error.data.get("details") or error.data.get("error") or ""
|
|
638
|
+
)
|
|
639
|
+
else:
|
|
640
|
+
reason = "Failed to initialize agent"
|
|
641
|
+
details = ""
|
|
642
|
+
self.post_message(AgentFail(reason, details))
|
|
643
|
+
elif self.session_id is None:
|
|
644
|
+
await self.acp_new_session()
|
|
645
|
+
if self.session_pk is not None:
|
|
646
|
+
db = DB()
|
|
647
|
+
await db.session_update_last_used(self.session_pk)
|
|
648
|
+
|
|
649
|
+
self.post_message(AgentReady())
|
|
650
|
+
|
|
651
|
+
async def _ensure_db_session(self) -> None:
|
|
652
|
+
if self.session_pk is not None:
|
|
653
|
+
return
|
|
654
|
+
if not self.supports_load_session:
|
|
655
|
+
return
|
|
656
|
+
data = getattr(self, "_pending_session_data", None)
|
|
657
|
+
if data is None:
|
|
658
|
+
return
|
|
659
|
+
|
|
660
|
+
db = DB()
|
|
661
|
+
self.session_pk = await db.session_new(
|
|
662
|
+
data["session_name"],
|
|
663
|
+
data["agent_name"],
|
|
664
|
+
data["agent_identity"],
|
|
665
|
+
data["session_id"],
|
|
666
|
+
protocol=data["protocol"],
|
|
667
|
+
meta=data["meta"],
|
|
668
|
+
)
|
|
669
|
+
self._pending_session_data = None
|
|
670
|
+
await self._save_last_session_to_cdh()
|
|
671
|
+
|
|
672
|
+
async def _save_last_session_to_cdh(self) -> None:
|
|
673
|
+
if self.session_pk is None:
|
|
674
|
+
return
|
|
675
|
+
try:
|
|
676
|
+
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
677
|
+
|
|
678
|
+
cdh_dir = CdhProjectLoader.find_cdh_dir(self.project_root_path)
|
|
679
|
+
if cdh_dir is None:
|
|
680
|
+
return
|
|
681
|
+
CdhProjectLoader.save_last_session(
|
|
682
|
+
cdh_dir,
|
|
683
|
+
{
|
|
684
|
+
"agent_session_id": self.session_id,
|
|
685
|
+
"session_pk": self.session_pk,
|
|
686
|
+
"agent_identity": self._agent_data["identity"],
|
|
687
|
+
},
|
|
688
|
+
)
|
|
689
|
+
except Exception:
|
|
690
|
+
pass
|
|
691
|
+
|
|
692
|
+
async def send_prompt(self, prompt: str) -> str | None:
|
|
693
|
+
"""Send a prompt to the agent.
|
|
694
|
+
|
|
695
|
+
!!! note
|
|
696
|
+
This method blocks as it may defer to a thread to read resources.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
prompt: Prompt text.
|
|
700
|
+
"""
|
|
701
|
+
await self._ensure_db_session()
|
|
702
|
+
prompt_content_blocks = await asyncio.to_thread(
|
|
703
|
+
build_prompt, self.project_root_path, prompt
|
|
704
|
+
)
|
|
705
|
+
return await self.acp_session_prompt(prompt_content_blocks)
|
|
706
|
+
|
|
707
|
+
async def acp_initialize(self):
|
|
708
|
+
"""Initialize agent."""
|
|
709
|
+
with self.request():
|
|
710
|
+
initialize_response = api.initialize(
|
|
711
|
+
PROTOCOL_VERSION,
|
|
712
|
+
{
|
|
713
|
+
"fs": {
|
|
714
|
+
"readTextFile": True,
|
|
715
|
+
"writeTextFile": True,
|
|
716
|
+
},
|
|
717
|
+
"terminal": True,
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
"name": tui.NAME,
|
|
721
|
+
"title": tui.TITLE,
|
|
722
|
+
"version": tui.get_version(),
|
|
723
|
+
},
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
response = await initialize_response.wait()
|
|
727
|
+
assert response is not None
|
|
728
|
+
|
|
729
|
+
# Store agents capabilities
|
|
730
|
+
if agent_capabilities := response.get("agentCapabilities"):
|
|
731
|
+
self.agent_capabilities = agent_capabilities
|
|
732
|
+
if auth_methods := response.get("authMethods"):
|
|
733
|
+
self.auth_methods = auth_methods
|
|
734
|
+
|
|
735
|
+
async def acp_new_session(self) -> None:
|
|
736
|
+
"""Create a new session."""
|
|
737
|
+
with self.request():
|
|
738
|
+
session_new_response = api.session_new(
|
|
739
|
+
str(self.project_root_path),
|
|
740
|
+
[],
|
|
741
|
+
)
|
|
742
|
+
response = await session_new_response.wait()
|
|
743
|
+
assert response is not None
|
|
744
|
+
self.session_id = response["sessionId"]
|
|
745
|
+
|
|
746
|
+
if self.supports_load_session:
|
|
747
|
+
self._pending_session_data = {
|
|
748
|
+
"session_name": "New Session",
|
|
749
|
+
"agent_name": self._agent_data["name"],
|
|
750
|
+
"agent_identity": self._agent_data["identity"],
|
|
751
|
+
"session_id": self.session_id,
|
|
752
|
+
"protocol": "acp",
|
|
753
|
+
"meta": {
|
|
754
|
+
"cwd": str(self.project_root_path),
|
|
755
|
+
"agent_data": self._agent_data,
|
|
756
|
+
},
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (modes := response.get("modes", None)) is not None:
|
|
760
|
+
current_mode = modes["currentModeId"]
|
|
761
|
+
available_modes = modes["availableModes"]
|
|
762
|
+
modes_update = {
|
|
763
|
+
mode["id"]: Mode(
|
|
764
|
+
mode["id"], mode["name"], mode.get("description", None)
|
|
765
|
+
)
|
|
766
|
+
for mode in available_modes
|
|
767
|
+
}
|
|
768
|
+
self.post_message(messages.SetModes(current_mode, modes_update))
|
|
769
|
+
|
|
770
|
+
async def acp_load_session(self) -> None:
|
|
771
|
+
assert self.session_id is not None, "Session id must be set"
|
|
772
|
+
cwd = str(self.project_root_path)
|
|
773
|
+
session_title = None
|
|
774
|
+
if self.session_pk is not None:
|
|
775
|
+
db = DB()
|
|
776
|
+
if (session := await db.session_get(self.session_pk)) is not None:
|
|
777
|
+
session_title = session.get("title") or "Untitled"
|
|
778
|
+
if session["meta_json"]:
|
|
779
|
+
meta = json.loads(session["meta_json"])
|
|
780
|
+
if session_cwd := meta.get("cwd", None):
|
|
781
|
+
cwd = session_cwd
|
|
782
|
+
if agent_data := meta.get("agent_data"):
|
|
783
|
+
self._agent_data = agent_data
|
|
784
|
+
|
|
785
|
+
with self.request():
|
|
786
|
+
session_load_response = api.session_load(cwd, [], self.session_id)
|
|
787
|
+
self.post_message(messages.SessionReplay(active=True))
|
|
788
|
+
try:
|
|
789
|
+
response = await session_load_response.wait()
|
|
790
|
+
finally:
|
|
791
|
+
self.post_message(messages.SessionReplay(active=False))
|
|
792
|
+
|
|
793
|
+
if (modes := response.get("modes", None)) is not None:
|
|
794
|
+
current_mode = modes["currentModeId"]
|
|
795
|
+
available_modes = modes["availableModes"]
|
|
796
|
+
modes_update = {
|
|
797
|
+
mode["id"]: Mode(
|
|
798
|
+
mode["id"], mode["name"], mode.get("description", None)
|
|
799
|
+
)
|
|
800
|
+
for mode in available_modes
|
|
801
|
+
}
|
|
802
|
+
self.post_message(messages.SetModes(current_mode, modes_update))
|
|
803
|
+
|
|
804
|
+
if session_title:
|
|
805
|
+
self.post_message(tui_messages.SessionUpdate(name=session_title))
|
|
806
|
+
|
|
807
|
+
await self._save_last_session_to_cdh()
|
|
808
|
+
|
|
809
|
+
async def acp_session_prompt(
|
|
810
|
+
self, prompt: list[protocol.ContentBlock]
|
|
811
|
+
) -> str | None:
|
|
812
|
+
"""Send the prompt to the agent.
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
The stop reason.
|
|
816
|
+
|
|
817
|
+
"""
|
|
818
|
+
with self.request():
|
|
819
|
+
session_prompt = api.session_prompt(prompt, self.session_id)
|
|
820
|
+
try:
|
|
821
|
+
result = await session_prompt.wait()
|
|
822
|
+
except jsonrpc.APIError as error:
|
|
823
|
+
details = ""
|
|
824
|
+
match error.data:
|
|
825
|
+
case {"details": details}:
|
|
826
|
+
pass
|
|
827
|
+
|
|
828
|
+
self.post_message(
|
|
829
|
+
AgentFail(
|
|
830
|
+
"Failed to send prompt" or error.message,
|
|
831
|
+
(
|
|
832
|
+
str(details)
|
|
833
|
+
if details
|
|
834
|
+
else f"{self._agent_data['name']} returned an error"
|
|
835
|
+
),
|
|
836
|
+
)
|
|
837
|
+
)
|
|
838
|
+
return None
|
|
839
|
+
except jsonrpc.JSONRPCError as error:
|
|
840
|
+
self.post_message(
|
|
841
|
+
AgentFail(
|
|
842
|
+
"Failed to send prompt" or error.message,
|
|
843
|
+
(error.message or f"{self._agent_data['name']} returned an error"),
|
|
844
|
+
)
|
|
845
|
+
)
|
|
846
|
+
return None
|
|
847
|
+
|
|
848
|
+
assert result is not None
|
|
849
|
+
return result.get("stopReason")
|
|
850
|
+
|
|
851
|
+
async def acp_session_set_mode(self, mode_id: str) -> str | None:
|
|
852
|
+
"""Update the current mode with the agent."""
|
|
853
|
+
with self.request():
|
|
854
|
+
response = api.session_set_mode(self.session_id, mode_id)
|
|
855
|
+
try:
|
|
856
|
+
await response.wait()
|
|
857
|
+
except jsonrpc.APIError as error:
|
|
858
|
+
match error.data:
|
|
859
|
+
case {"details": details}:
|
|
860
|
+
return details if isinstance(details, str) else "Failed to set mode"
|
|
861
|
+
return "Failed to set mode"
|
|
862
|
+
else:
|
|
863
|
+
return None
|
|
864
|
+
|
|
865
|
+
async def set_mode(self, mode_id: str) -> str | None:
|
|
866
|
+
return await self.acp_session_set_mode(mode_id)
|
|
867
|
+
|
|
868
|
+
async def set_session_name(self, name: str) -> None:
|
|
869
|
+
if self.session_pk is None:
|
|
870
|
+
return
|
|
871
|
+
db = DB()
|
|
872
|
+
await db.session_update_title(self.session_pk, name)
|
|
873
|
+
self.post_message(tui_messages.SessionUpdate(name=name))
|
|
874
|
+
|
|
875
|
+
async def acp_session_cancel(self) -> bool:
|
|
876
|
+
with self.request():
|
|
877
|
+
response = api.session_cancel(self.session_id, {})
|
|
878
|
+
try:
|
|
879
|
+
await response.wait()
|
|
880
|
+
except jsonrpc.APIError:
|
|
881
|
+
# No-op if there is nothing to cancel
|
|
882
|
+
return False
|
|
883
|
+
return True
|
|
884
|
+
|
|
885
|
+
async def cancel(self) -> bool:
|
|
886
|
+
return await self.acp_session_cancel()
|