connectonion 0.6.2__py3-none-any.whl → 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- connectonion/__init__.py +46 -9
- connectonion/cli/__init__.py +11 -1
- connectonion/cli/browser_agent/__init__.py +11 -1
- connectonion/cli/browser_agent/browser.py +13 -3
- connectonion/cli/browser_agent/element_finder.py +8 -0
- connectonion/cli/browser_agent/highlight_screenshot.py +9 -1
- connectonion/cli/browser_agent/scroll.py +8 -0
- connectonion/cli/co_ai/__init__.py +6 -0
- connectonion/cli/co_ai/agent.py +87 -0
- connectonion/cli/co_ai/agents/__init__.py +5 -0
- connectonion/cli/co_ai/agents/registry.py +57 -0
- connectonion/cli/co_ai/commands/__init__.py +45 -0
- connectonion/cli/co_ai/commands/compact.py +173 -0
- connectonion/cli/co_ai/commands/cost.py +77 -0
- connectonion/cli/co_ai/commands/export.py +60 -0
- connectonion/cli/co_ai/commands/help.py +80 -0
- connectonion/cli/co_ai/commands/init.py +101 -0
- connectonion/cli/co_ai/commands/sessions.py +55 -0
- connectonion/cli/co_ai/commands/tasks.py +63 -0
- connectonion/cli/co_ai/commands/undo.py +103 -0
- connectonion/cli/co_ai/context.py +127 -0
- connectonion/cli/co_ai/main.py +52 -0
- connectonion/cli/co_ai/plugins/__init__.py +5 -0
- connectonion/cli/co_ai/plugins/system_reminder.py +154 -0
- connectonion/cli/co_ai/prompts/agents/explore.md +79 -0
- connectonion/cli/co_ai/prompts/agents/plan.md +60 -0
- connectonion/cli/co_ai/prompts/assembler.py +303 -0
- connectonion/cli/{docs/co-vibecoding-principles-docs-contexts-all-in-one.md → co_ai/prompts/connectonion/README.md} +26 -0
- connectonion/cli/co_ai/prompts/connectonion/api.md +457 -0
- connectonion/cli/co_ai/prompts/connectonion/cli/README.md +805 -0
- connectonion/cli/co_ai/prompts/connectonion/cli/auth.md +46 -0
- connectonion/cli/co_ai/prompts/connectonion/cli/browser.md +235 -0
- connectonion/cli/co_ai/prompts/connectonion/cli/copy.md +184 -0
- connectonion/cli/co_ai/prompts/connectonion/cli/create.md +335 -0
- connectonion/cli/co_ai/prompts/connectonion/cli/init.md +431 -0
- connectonion/cli/co_ai/prompts/connectonion/co-directory-structure.md +214 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/agent.md +1078 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/events.md +816 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/llm_do.md +256 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/max_iterations.md +362 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/models.md +641 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/plugins.md +100 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/prompts.md +122 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/tools.md +512 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/transcribe.md +156 -0
- connectonion/cli/co_ai/prompts/connectonion/concepts/trust.md +291 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/README.md +18 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/auto_debug.md +1026 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/console.md +129 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/eval-format.md +178 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/eval.md +230 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/exceptions.md +307 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/log.md +117 -0
- connectonion/cli/co_ai/prompts/connectonion/debug/xray.md +215 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/001-choosing-input-method.md +202 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/002-choosing-llm-function-name.md +202 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/003-choosing-trust-keyword.md +141 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/004-cli-create-flow.md +117 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/005-designing-agent-network-protocol.md +503 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/006-agent-address-format.md +305 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/007-authentication-backend-design.md +240 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/008-naming-is-hard.md +228 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/009-why-connect-function.md +167 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/010-cli-ux-progressive-disclosure.md +176 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/011-global-config-identity-management.md +357 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/012-tool-execution-separation.md +259 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/013-debug-and-logging-design.md +253 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/014-hook-system-design.md +510 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/015-interactive-auto-debug-design.md +837 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/016-why-no-zero-knowledge-proofs.md +358 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/017-session-logging-and-eval-format.md +120 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/018-event-api-naming.md +274 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/019-agent-lifecycle-design.md +655 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/020-trust-system-and-network-architecture.md +503 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/021-task-storage-jsonl-design.md +496 -0
- connectonion/cli/co_ai/prompts/connectonion/design-decisions/022-raw-asgi-implementation.md +273 -0
- connectonion/cli/co_ai/prompts/connectonion/examples/agent_reasoning.md +62 -0
- connectonion/cli/co_ai/prompts/connectonion/examples/atomic_tools.md +24 -0
- connectonion/cli/co_ai/prompts/connectonion/examples/load_guide.md +18 -0
- connectonion/cli/co_ai/prompts/connectonion/examples.md +0 -0
- connectonion/cli/co_ai/prompts/connectonion/hook-system-options.md +364 -0
- connectonion/cli/co_ai/prompts/connectonion/index.md +162 -0
- connectonion/cli/co_ai/prompts/connectonion/integrations/README.md +12 -0
- connectonion/cli/co_ai/prompts/connectonion/integrations/auth.md +450 -0
- connectonion/cli/co_ai/prompts/connectonion/integrations/google.md +431 -0
- connectonion/cli/co_ai/prompts/connectonion/integrations/microsoft.md +370 -0
- connectonion/cli/co_ai/prompts/connectonion/network/README.md +14 -0
- connectonion/cli/co_ai/prompts/connectonion/network/connect.md +543 -0
- connectonion/cli/co_ai/prompts/connectonion/network/connection.md +538 -0
- connectonion/cli/co_ai/prompts/connectonion/network/deploy.md +123 -0
- connectonion/cli/co_ai/prompts/connectonion/network/host.md +1049 -0
- connectonion/cli/co_ai/prompts/connectonion/network/protocol/agent-relay-protocol.md +495 -0
- connectonion/cli/co_ai/prompts/connectonion/network/protocol/announce-message.md +115 -0
- connectonion/cli/co_ai/prompts/connectonion/principles.md +124 -0
- connectonion/cli/co_ai/prompts/connectonion/quickstart.md +261 -0
- connectonion/cli/co_ai/prompts/connectonion/roadmap.md +81 -0
- connectonion/cli/co_ai/prompts/connectonion/templates/README.md +77 -0
- connectonion/cli/co_ai/prompts/connectonion/templates/meta-agent.md +152 -0
- connectonion/cli/co_ai/prompts/connectonion/templates/minimal.md +105 -0
- connectonion/cli/co_ai/prompts/connectonion/templates/playwright.md +130 -0
- connectonion/cli/co_ai/prompts/connectonion/templates/web-research.md +144 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/README.md +95 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/chat.md +181 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/divider.md +63 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/dropdown.md +83 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/footer.md +44 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/fuzzy.md +68 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/input.md +84 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/keys.md +77 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/pick.md +71 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/providers.md +89 -0
- connectonion/cli/co_ai/prompts/connectonion/tui/status_bar.md +67 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/README.md +156 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/calendar_plugin.md +68 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/eval.md +89 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/gmail_plugin.md +68 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/image_result_formatter.md +74 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/re_act.md +86 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_plugins/shell_approval.md +69 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/README.md +81 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/diff_writer.md +138 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/get_emails.md +499 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/gmail.md +135 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/google_calendar.md +106 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/memory.md +486 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/microsoft_calendar.md +106 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/outlook.md +120 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/send_email.md +403 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/shell.md +95 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/slash_command.md +96 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/terminal.md +97 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/todo_list.md +252 -0
- connectonion/cli/co_ai/prompts/connectonion/useful_tools/web_fetch.md +130 -0
- connectonion/cli/co_ai/prompts/connectonion/vibe-coding-guide.md +97 -0
- connectonion/cli/co_ai/prompts/connectonion/windows-support.md +258 -0
- connectonion/cli/co_ai/prompts/main.md +247 -0
- connectonion/cli/co_ai/prompts/summarization.md +55 -0
- connectonion/cli/co_ai/prompts/system-reminders/agent.md +23 -0
- connectonion/cli/co_ai/prompts/system-reminders/plan_mode.md +13 -0
- connectonion/cli/co_ai/prompts/system-reminders/security.md +14 -0
- connectonion/cli/co_ai/prompts/system-reminders/simplicity.md +14 -0
- connectonion/cli/co_ai/prompts/tools/ask_user.md +61 -0
- connectonion/cli/co_ai/prompts/tools/background.md +57 -0
- connectonion/cli/co_ai/prompts/tools/edit.md +90 -0
- connectonion/cli/co_ai/prompts/tools/glob.md +52 -0
- connectonion/cli/co_ai/prompts/tools/grep.md +55 -0
- connectonion/cli/co_ai/prompts/tools/plan_mode.md +80 -0
- connectonion/cli/co_ai/prompts/tools/read.md +40 -0
- connectonion/cli/co_ai/prompts/tools/shell.md +67 -0
- connectonion/cli/co_ai/prompts/tools/task.md +51 -0
- connectonion/cli/co_ai/prompts/tools/todo.md +139 -0
- connectonion/cli/co_ai/prompts/tools/write.md +47 -0
- connectonion/cli/co_ai/prompts/workflow.md +89 -0
- connectonion/cli/co_ai/sessions.py +110 -0
- connectonion/cli/co_ai/skills/__init__.py +37 -0
- connectonion/cli/co_ai/skills/builtin/commit/SKILL.md +63 -0
- connectonion/cli/co_ai/skills/builtin/review-pr/SKILL.md +76 -0
- connectonion/cli/co_ai/skills/loader.py +166 -0
- connectonion/cli/co_ai/skills/tool.py +46 -0
- connectonion/cli/co_ai/tools/__init__.py +92 -0
- connectonion/cli/co_ai/tools/ask_user.py +35 -0
- connectonion/cli/co_ai/tools/background.py +201 -0
- connectonion/cli/co_ai/tools/diff_writer.py +291 -0
- connectonion/cli/co_ai/tools/edit.py +89 -0
- connectonion/cli/co_ai/tools/glob.py +84 -0
- connectonion/cli/co_ai/tools/grep.py +158 -0
- connectonion/cli/co_ai/tools/load_guide.py +23 -0
- connectonion/cli/co_ai/tools/multi_edit.py +116 -0
- connectonion/cli/co_ai/tools/plan_mode.py +169 -0
- connectonion/cli/co_ai/tools/read.py +61 -0
- connectonion/cli/co_ai/tools/task.py +59 -0
- connectonion/cli/co_ai/tools/todo_list.py +159 -0
- connectonion/cli/co_ai/tools/write.py +126 -0
- connectonion/cli/commands/__init__.py +11 -1
- connectonion/cli/commands/ai_commands.py +34 -0
- connectonion/cli/commands/copy_commands.py +55 -6
- connectonion/cli/commands/create.py +20 -17
- connectonion/cli/commands/init.py +19 -22
- connectonion/cli/commands/project_cmd_lib.py +15 -0
- connectonion/cli/main.py +11 -0
- connectonion/console.py +15 -1
- connectonion/core/__init__.py +10 -1
- connectonion/core/agent.py +37 -16
- connectonion/core/exceptions.py +74 -0
- connectonion/core/llm.py +54 -6
- connectonion/core/tool_executor.py +32 -31
- connectonion/core/tool_factory.py +47 -10
- connectonion/debug/__init__.py +10 -1
- connectonion/debug/debug_explainer/__init__.py +10 -1
- connectonion/debug/execution_analyzer/__init__.py +10 -1
- connectonion/debug/execution_analyzer/execution_analysis.py +5 -2
- connectonion/debug/runtime_inspector/__init__.py +10 -1
- connectonion/docs/.package-ignore +6 -0
- connectonion/docs/README.md +2036 -0
- connectonion/docs/api.md +457 -0
- connectonion/docs/archive/001-ai-agent-is-just-prompt-plus-function.md +249 -0
- connectonion/docs/archive/README.md +53 -0
- connectonion/docs/archive/archive/consolidation-plan.md +72 -0
- connectonion/docs/archive/archive/core-principles-extracted.md +239 -0
- connectonion/docs/archive/archive/master-principles.md +222 -0
- connectonion/docs/archive/archive/principles.md +293 -0
- connectonion/docs/archive/archive/simplicity-principles.md +221 -0
- connectonion/docs/archive/attack-defense-insights.md +410 -0
- connectonion/docs/archive/business-model.md +305 -0
- connectonion/docs/archive/core-principles-unified.md +190 -0
- connectonion/docs/archive/discussion-journey.md +178 -0
- connectonion/docs/archive/economic-analysis.md +323 -0
- connectonion/docs/archive/features/01-share-and-find.md +256 -0
- connectonion/docs/archive/features/02-agent-authentication.md +93 -0
- connectonion/docs/archive/features/03-test-before-trust.md +71 -0
- connectonion/docs/archive/features/06-reliability-and-offline.md +197 -0
- connectonion/docs/archive/features/README.md +46 -0
- connectonion/docs/archive/features-roadmap.md +247 -0
- connectonion/docs/archive/mcp-comparison-insights.md +215 -0
- connectonion/docs/archive/migration-strategy.md +571 -0
- connectonion/docs/archive/mini-whitepaper.md +293 -0
- connectonion/docs/archive/network-protocol.md +394 -0
- connectonion/docs/archive/semantic-revolution.md +367 -0
- connectonion/docs/archive/technical-architecture.md +453 -0
- connectonion/docs/archive/the-semantic-insight.md +207 -0
- connectonion/docs/archive/threat-model.md +164 -0
- connectonion/docs/cli/README.md +805 -0
- connectonion/docs/cli/auth.md +46 -0
- connectonion/docs/cli/browser.md +235 -0
- connectonion/docs/cli/copy.md +232 -0
- connectonion/docs/cli/create.md +335 -0
- connectonion/docs/cli/init.md +431 -0
- connectonion/docs/co-directory-structure.md +214 -0
- connectonion/docs/concepts/agent.md +1078 -0
- connectonion/docs/concepts/events.md +699 -0
- connectonion/docs/concepts/llm_do.md +256 -0
- connectonion/docs/concepts/max_iterations.md +362 -0
- connectonion/docs/concepts/models.md +641 -0
- connectonion/docs/concepts/plugins.md +101 -0
- connectonion/docs/concepts/prompts.md +122 -0
- connectonion/docs/concepts/session.md +428 -0
- connectonion/docs/concepts/tools.md +512 -0
- connectonion/docs/concepts/transcribe.md +156 -0
- connectonion/docs/concepts/trust.md +291 -0
- connectonion/docs/connectonion.md +1256 -0
- connectonion/docs/debug/README.md +18 -0
- connectonion/docs/debug/auto_debug.md +1026 -0
- connectonion/docs/debug/console.md +129 -0
- connectonion/docs/debug/eval-format.md +178 -0
- connectonion/docs/debug/eval.md +230 -0
- connectonion/docs/debug/exceptions.md +307 -0
- connectonion/docs/debug/log.md +117 -0
- connectonion/docs/debug/xray.md +215 -0
- connectonion/docs/design-decisions/001-choosing-input-method.md +202 -0
- connectonion/docs/design-decisions/002-choosing-llm-function-name.md +202 -0
- connectonion/docs/design-decisions/003-choosing-trust-keyword.md +141 -0
- connectonion/docs/design-decisions/004-cli-create-flow.md +117 -0
- connectonion/docs/design-decisions/005-designing-agent-network-protocol.md +503 -0
- connectonion/docs/design-decisions/006-agent-address-format.md +305 -0
- connectonion/docs/design-decisions/007-authentication-backend-design.md +240 -0
- connectonion/docs/design-decisions/008-naming-is-hard.md +228 -0
- connectonion/docs/design-decisions/009-why-connect-function.md +167 -0
- connectonion/docs/design-decisions/010-cli-ux-progressive-disclosure.md +176 -0
- connectonion/docs/design-decisions/011-global-config-identity-management.md +357 -0
- connectonion/docs/design-decisions/012-tool-execution-separation.md +259 -0
- connectonion/docs/design-decisions/013-debug-and-logging-design.md +253 -0
- connectonion/docs/design-decisions/014-hook-system-design.md +510 -0
- connectonion/docs/design-decisions/015-interactive-auto-debug-design.md +837 -0
- connectonion/docs/design-decisions/016-why-no-zero-knowledge-proofs.md +358 -0
- connectonion/docs/design-decisions/017-session-logging-and-eval-format.md +120 -0
- connectonion/docs/design-decisions/018-event-api-naming.md +274 -0
- connectonion/docs/design-decisions/019-agent-lifecycle-design.md +655 -0
- connectonion/docs/design-decisions/020-trust-system-and-network-architecture.md +503 -0
- connectonion/docs/design-decisions/021-task-storage-jsonl-design.md +496 -0
- connectonion/docs/design-decisions/022-raw-asgi-implementation.md +273 -0
- connectonion/docs/examples.md +0 -0
- connectonion/docs/hook-system-options.md +364 -0
- connectonion/docs/integrations/README.md +12 -0
- connectonion/docs/integrations/auth.md +450 -0
- connectonion/docs/integrations/google.md +431 -0
- connectonion/docs/integrations/microsoft.md +370 -0
- connectonion/docs/network/README.md +14 -0
- connectonion/docs/network/connect.md +629 -0
- connectonion/docs/network/deploy.md +124 -0
- connectonion/docs/network/host.md +1087 -0
- connectonion/docs/network/io.md +538 -0
- connectonion/docs/network/protocol/agent-relay-protocol.md +495 -0
- connectonion/docs/network/protocol/announce-message.md +115 -0
- connectonion/docs/principles.md +124 -0
- connectonion/docs/quickstart.md +261 -0
- connectonion/docs/roadmap.md +81 -0
- connectonion/docs/templates/README.md +77 -0
- connectonion/docs/templates/meta-agent.md +152 -0
- connectonion/docs/templates/minimal.md +105 -0
- connectonion/docs/templates/playwright.md +130 -0
- connectonion/docs/templates/web-research.md +144 -0
- connectonion/docs/tui/README.md +95 -0
- connectonion/docs/tui/chat.md +181 -0
- connectonion/docs/tui/divider.md +63 -0
- connectonion/docs/tui/dropdown.md +83 -0
- connectonion/docs/tui/footer.md +44 -0
- connectonion/docs/tui/fuzzy.md +68 -0
- connectonion/docs/tui/input.md +84 -0
- connectonion/docs/tui/keys.md +77 -0
- connectonion/docs/tui/pick.md +71 -0
- connectonion/docs/tui/providers.md +89 -0
- connectonion/docs/tui/status_bar.md +67 -0
- connectonion/docs/useful_plugins/README.md +160 -0
- connectonion/docs/useful_plugins/calendar_plugin.md +68 -0
- connectonion/docs/useful_plugins/eval.md +89 -0
- connectonion/docs/useful_plugins/gmail_plugin.md +68 -0
- connectonion/docs/useful_plugins/image_result_formatter.md +74 -0
- connectonion/docs/useful_plugins/re_act.md +86 -0
- connectonion/docs/useful_plugins/shell_approval.md +69 -0
- connectonion/docs/useful_plugins/system_reminder.md +210 -0
- connectonion/docs/useful_plugins/tool_approval.md +139 -0
- connectonion/docs/useful_prompts/README.md +127 -0
- connectonion/docs/useful_prompts/coding_agent.md +214 -0
- connectonion/docs/useful_tools/README.md +81 -0
- connectonion/docs/useful_tools/ask_user.md +103 -0
- connectonion/docs/useful_tools/diff_writer.md +158 -0
- connectonion/docs/useful_tools/get_emails.md +519 -0
- connectonion/docs/useful_tools/gmail.md +155 -0
- connectonion/docs/useful_tools/google_calendar.md +126 -0
- connectonion/docs/useful_tools/memory.md +506 -0
- connectonion/docs/useful_tools/microsoft_calendar.md +126 -0
- connectonion/docs/useful_tools/outlook.md +140 -0
- connectonion/docs/useful_tools/send_email.md +423 -0
- connectonion/docs/useful_tools/shell.md +115 -0
- connectonion/docs/useful_tools/slash_command.md +116 -0
- connectonion/docs/useful_tools/terminal.md +115 -0
- connectonion/docs/useful_tools/todo_list.md +272 -0
- connectonion/docs/useful_tools/web_fetch.md +150 -0
- connectonion/docs/vibe-coding-guide.md +97 -0
- connectonion/docs/windows-support.md +258 -0
- connectonion/logger.py +3 -3
- connectonion/network/__init__.py +19 -6
- connectonion/network/asgi/__init__.py +81 -0
- connectonion/network/asgi/http.py +205 -0
- connectonion/network/asgi/websocket.py +217 -0
- connectonion/network/connect.py +232 -185
- connectonion/network/host/__init__.py +59 -0
- connectonion/network/host/auth.py +191 -0
- connectonion/network/host/routes.py +135 -0
- connectonion/network/host/server.py +289 -0
- connectonion/network/host/session.py +78 -0
- connectonion/network/io/__init__.py +21 -0
- connectonion/network/{connection.py → io/base.py} +17 -42
- connectonion/network/io/websocket.py +55 -0
- connectonion/network/relay.py +37 -16
- connectonion/network/trust/__init__.py +30 -0
- connectonion/network/trust/factory.py +138 -0
- connectonion/network/{trust_agents.py → trust/prompts.py} +3 -3
- connectonion/network/{trust_functions.py → trust/tools.py} +2 -2
- connectonion/prompt_files/__init__.py +11 -1
- connectonion/prompt_files/react_acknowledge.md +26 -0
- connectonion/prompts.py +10 -1
- connectonion/tui/chat.py +10 -1
- connectonion/tui/divider.py +10 -1
- connectonion/tui/dropdown.py +10 -1
- connectonion/tui/footer.py +8 -0
- connectonion/tui/fuzzy.py +11 -1
- connectonion/tui/input.py +118 -70
- connectonion/tui/keys.py +133 -6
- connectonion/tui/providers.py +11 -1
- connectonion/tui/status_bar.py +10 -1
- connectonion/useful_events_handlers/__init__.py +8 -0
- connectonion/useful_events_handlers/reflect.py +19 -4
- connectonion/useful_plugins/__init__.py +3 -1
- connectonion/useful_plugins/eval.py +2 -2
- connectonion/useful_plugins/gmail_plugin.py +3 -3
- connectonion/useful_plugins/image_result_formatter.py +3 -3
- connectonion/useful_plugins/re_act.py +114 -28
- connectonion/useful_plugins/shell_approval.py +2 -2
- connectonion/useful_plugins/system_reminder.py +103 -0
- connectonion/useful_plugins/tool_approval.py +233 -0
- connectonion/useful_plugins/ui_stream.py +18 -133
- connectonion/useful_prompts/README.md +61 -0
- connectonion/useful_prompts/__init__.py +45 -0
- connectonion/useful_prompts/coding_agent/README.md +106 -0
- connectonion/useful_prompts/coding_agent/assembler.py +123 -0
- connectonion/useful_prompts/coding_agent/prompts/main.md +227 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/ask_user.md +61 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/background.md +57 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/edit.md +90 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/glob.md +52 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/grep.md +55 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/plan_mode.md +80 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/read.md +40 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/shell.md +67 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/task.md +51 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/todo.md +139 -0
- connectonion/useful_prompts/coding_agent/prompts/tools/write.md +48 -0
- connectonion/useful_prompts/system-reminders/security-warning.md +14 -0
- connectonion/useful_prompts/system-reminders/test-reminder.md +11 -0
- connectonion/useful_tools/__init__.py +31 -4
- connectonion/useful_tools/ask_user.py +35 -0
- connectonion/useful_tools/bash.py +69 -0
- connectonion/useful_tools/diff_writer.py +186 -94
- connectonion/useful_tools/edit.py +102 -0
- connectonion/useful_tools/glob_files.py +97 -0
- connectonion/useful_tools/grep_files.py +171 -0
- connectonion/useful_tools/multi_edit.py +116 -0
- connectonion/useful_tools/read_file.py +73 -0
- connectonion/useful_tools/shell.py +50 -45
- connectonion/useful_tools/write_file.py +129 -0
- {connectonion-0.6.2.dist-info → connectonion-0.6.4.dist-info}/METADATA +10 -3
- connectonion-0.6.4.dist-info/RECORD +472 -0
- connectonion/network/asgi.py +0 -407
- connectonion/network/host.py +0 -616
- connectonion/network/trust.py +0 -166
- connectonion-0.6.2.dist-info/RECORD +0 -129
- /connectonion/cli/{docs → co_ai/prompts/connectonion}/connectonion.md +0 -0
- {connectonion-0.6.2.dist-info → connectonion-0.6.4.dist-info}/WHEEL +0 -0
- {connectonion-0.6.2.dist-info → connectonion-0.6.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Ask user a question during agent execution via connection
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [typing] | imported by [useful_tools/__init__.py]
|
|
5
|
+
Data flow: agent calls ask_user tool → sends ask_user event via connection → waits for response → returns answer
|
|
6
|
+
State/Effects: blocks until user responds via connection
|
|
7
|
+
Integration: requires agent.connection to be set | agent parameter injected by tool_executor
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ask_user(
|
|
14
|
+
agent,
|
|
15
|
+
question: str,
|
|
16
|
+
options: Optional[List[str]] = None,
|
|
17
|
+
multi_select: bool = False
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Ask the user a question and wait for their response.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
question: The question to ask the user
|
|
23
|
+
options: Optional list of choices for the user to select from
|
|
24
|
+
multi_select: If True, user can select multiple options
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The user's answer (or comma-separated answers if multi_select)
|
|
28
|
+
"""
|
|
29
|
+
agent.connection.send({
|
|
30
|
+
"type": "ask_user",
|
|
31
|
+
"question": question,
|
|
32
|
+
"options": options,
|
|
33
|
+
"multi_select": multi_select
|
|
34
|
+
})
|
|
35
|
+
return agent.connection.receive().get("answer", "")
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Background task execution for long-running operations."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaskStatus(Enum):
|
|
12
|
+
RUNNING = "running"
|
|
13
|
+
COMPLETED = "completed"
|
|
14
|
+
FAILED = "failed"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class BackgroundTask:
|
|
19
|
+
"""A background task with its process and output."""
|
|
20
|
+
id: str
|
|
21
|
+
command: str
|
|
22
|
+
process: subprocess.Popen
|
|
23
|
+
output: list = field(default_factory=list)
|
|
24
|
+
status: TaskStatus = TaskStatus.RUNNING
|
|
25
|
+
start_time: float = field(default_factory=time.time)
|
|
26
|
+
end_time: Optional[float] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Global task registry
|
|
30
|
+
_tasks: Dict[str, BackgroundTask] = {}
|
|
31
|
+
_task_counter = 0
|
|
32
|
+
_lock = threading.Lock()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _reset_for_testing():
|
|
36
|
+
"""Reset state for testing. Not for production use."""
|
|
37
|
+
global _tasks, _task_counter
|
|
38
|
+
with _lock:
|
|
39
|
+
for task in _tasks.values():
|
|
40
|
+
if task.status == TaskStatus.RUNNING:
|
|
41
|
+
task.process.terminate()
|
|
42
|
+
_tasks.clear()
|
|
43
|
+
_task_counter = 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _read_output(task: BackgroundTask):
|
|
47
|
+
"""Read output from process in background thread."""
|
|
48
|
+
for line in iter(task.process.stdout.readline, ""):
|
|
49
|
+
if not line:
|
|
50
|
+
break
|
|
51
|
+
task.output.append(line.rstrip())
|
|
52
|
+
|
|
53
|
+
task.process.wait()
|
|
54
|
+
task.end_time = time.time()
|
|
55
|
+
task.status = TaskStatus.COMPLETED if task.process.returncode == 0 else TaskStatus.FAILED
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run_background(command: str, description: str = "") -> str:
|
|
59
|
+
"""
|
|
60
|
+
Run a shell command in the background.
|
|
61
|
+
|
|
62
|
+
Use this for long-running operations like builds, tests, or servers.
|
|
63
|
+
Returns immediately with a task ID. Use task_output() to check results.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
command: Shell command to run (e.g., "npm run build", "pytest")
|
|
67
|
+
description: Optional description for the task
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Task ID and confirmation message
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
run_background("npm run build") # Returns: "Task bg_1 started: npm run build"
|
|
74
|
+
task_output("bg_1") # Check output later
|
|
75
|
+
"""
|
|
76
|
+
global _task_counter
|
|
77
|
+
|
|
78
|
+
with _lock:
|
|
79
|
+
_task_counter += 1
|
|
80
|
+
task_id = f"bg_{_task_counter}"
|
|
81
|
+
|
|
82
|
+
process = subprocess.Popen(
|
|
83
|
+
command,
|
|
84
|
+
shell=True,
|
|
85
|
+
stdout=subprocess.PIPE,
|
|
86
|
+
stderr=subprocess.STDOUT,
|
|
87
|
+
text=True,
|
|
88
|
+
bufsize=1,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
task = BackgroundTask(
|
|
92
|
+
id=task_id,
|
|
93
|
+
command=command,
|
|
94
|
+
process=process,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
with _lock:
|
|
98
|
+
_tasks[task_id] = task
|
|
99
|
+
|
|
100
|
+
# Start output reader thread
|
|
101
|
+
thread = threading.Thread(target=_read_output, args=(task,), daemon=True)
|
|
102
|
+
thread.start()
|
|
103
|
+
|
|
104
|
+
desc = f" ({description})" if description else ""
|
|
105
|
+
return f"Task {task_id} started{desc}: {command}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def task_output(task_id: str, tail: int = 50) -> str:
|
|
109
|
+
"""
|
|
110
|
+
Get output from a background task.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
task_id: The task ID (e.g., "bg_1")
|
|
114
|
+
tail: Number of recent lines to show (default: 50)
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Task status and output
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
task_output("bg_1") # Get output from task bg_1
|
|
121
|
+
task_output("bg_1", tail=100) # Get last 100 lines
|
|
122
|
+
"""
|
|
123
|
+
with _lock:
|
|
124
|
+
task = _tasks.get(task_id)
|
|
125
|
+
|
|
126
|
+
if not task:
|
|
127
|
+
available = list(_tasks.keys())
|
|
128
|
+
if available:
|
|
129
|
+
return f"Task '{task_id}' not found. Available: {', '.join(available)}"
|
|
130
|
+
return f"Task '{task_id}' not found. No background tasks running."
|
|
131
|
+
|
|
132
|
+
elapsed = time.time() - task.start_time
|
|
133
|
+
if task.end_time:
|
|
134
|
+
elapsed = task.end_time - task.start_time
|
|
135
|
+
|
|
136
|
+
status_line = f"Task {task_id}: {task.status.value} ({elapsed:.1f}s)"
|
|
137
|
+
status_line += f"\nCommand: {task.command}"
|
|
138
|
+
|
|
139
|
+
if task.status == TaskStatus.FAILED:
|
|
140
|
+
status_line += f"\nExit code: {task.process.returncode}"
|
|
141
|
+
|
|
142
|
+
output_lines = task.output[-tail:] if task.output else []
|
|
143
|
+
if output_lines:
|
|
144
|
+
output_text = "\n".join(output_lines)
|
|
145
|
+
if len(task.output) > tail:
|
|
146
|
+
output_text = f"... ({len(task.output) - tail} lines omitted)\n{output_text}"
|
|
147
|
+
return f"{status_line}\n\nOutput:\n{output_text}"
|
|
148
|
+
|
|
149
|
+
return f"{status_line}\n\n(no output yet)"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def kill_task(task_id: str) -> str:
|
|
153
|
+
"""
|
|
154
|
+
Kill a running background task.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
task_id: The task ID to kill (e.g., "bg_1")
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Confirmation message
|
|
161
|
+
"""
|
|
162
|
+
with _lock:
|
|
163
|
+
task = _tasks.get(task_id)
|
|
164
|
+
|
|
165
|
+
if not task:
|
|
166
|
+
return f"Task '{task_id}' not found."
|
|
167
|
+
|
|
168
|
+
if task.status != TaskStatus.RUNNING:
|
|
169
|
+
return f"Task '{task_id}' is not running (status: {task.status.value})"
|
|
170
|
+
|
|
171
|
+
task.process.terminate()
|
|
172
|
+
task.status = TaskStatus.FAILED
|
|
173
|
+
task.end_time = time.time()
|
|
174
|
+
|
|
175
|
+
return f"Task '{task_id}' terminated."
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def list_tasks() -> str:
|
|
179
|
+
"""
|
|
180
|
+
List all background tasks.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Table of all tasks with their status
|
|
184
|
+
"""
|
|
185
|
+
with _lock:
|
|
186
|
+
tasks = list(_tasks.values())
|
|
187
|
+
|
|
188
|
+
if not tasks:
|
|
189
|
+
return "No background tasks."
|
|
190
|
+
|
|
191
|
+
lines = ["Background Tasks:", ""]
|
|
192
|
+
for t in tasks:
|
|
193
|
+
elapsed = time.time() - t.start_time
|
|
194
|
+
if t.end_time:
|
|
195
|
+
elapsed = t.end_time - t.start_time
|
|
196
|
+
|
|
197
|
+
status_icon = {"running": "⏳", "completed": "✓", "failed": "✗"}[t.status.value]
|
|
198
|
+
cmd_short = t.command[:40] + "..." if len(t.command) > 40 else t.command
|
|
199
|
+
lines.append(f" {status_icon} {t.id}: {cmd_short} ({elapsed:.1f}s)")
|
|
200
|
+
|
|
201
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Web-based file writing tool with Claude Code-style permission modes
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [difflib, pathlib, typing] | imported by [co_ai.tools.__init__]
|
|
5
|
+
Data flow: Agent calls DiffWriter.write(path, content) -> show diff via io -> ask approval via io -> write file -> return status
|
|
6
|
+
State/Effects: reads and writes files on filesystem | sends diff_preview and ask_user events via io | requires io channel for approval
|
|
7
|
+
Integration: exposes DiffWriter class with write(path, content), diff(path, content), read(path) | used as agent tool via Agent(tools=[DiffWriter()])
|
|
8
|
+
Errors: returns error string if file unreadable | returns user feedback on rejection | no exceptions raised
|
|
9
|
+
|
|
10
|
+
Permission Modes (like Claude Code's Shift+Tab cycle):
|
|
11
|
+
- normal: Prompt for every edit (default)
|
|
12
|
+
- auto: Auto-approve all edits without prompting
|
|
13
|
+
- plan: Read-only mode, no writes allowed
|
|
14
|
+
|
|
15
|
+
Architecture:
|
|
16
|
+
|
|
17
|
+
Agent Thread WebSocketIO Browser
|
|
18
|
+
│ │ │
|
|
19
|
+
│ 1. write("app.py", code) │ │
|
|
20
|
+
│ │ │
|
|
21
|
+
│ 2. io.send({type: "diff_preview", path, diff, ...}) │
|
|
22
|
+
│─────────────────────────────▶│─────────────────────────────▶
|
|
23
|
+
│ │ │
|
|
24
|
+
│ 3. io.send({type: "ask_user", question, options}) │
|
|
25
|
+
│─────────────────────────────▶│─────────────────────────────▶
|
|
26
|
+
│ │ │
|
|
27
|
+
│ 4. io.receive() [BLOCKS] │ User clicks option │
|
|
28
|
+
│◀─────────────────────────────│◀────────────────────────────│
|
|
29
|
+
│ │ │
|
|
30
|
+
│ 5. Write file or return rejection with feedback │
|
|
31
|
+
│ │ │
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import difflib
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Optional, Tuple
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Permission modes (like Claude Code's Shift+Tab cycle)
|
|
40
|
+
MODE_NORMAL = "normal" # Prompt for every edit
|
|
41
|
+
MODE_AUTO = "auto" # Auto-approve edits
|
|
42
|
+
MODE_PLAN = "plan" # Read-only, no writes
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DiffWriter:
|
|
46
|
+
"""File writer with Claude Code-style permission modes (web mode).
|
|
47
|
+
|
|
48
|
+
Requires io channel for approval prompts. Without io, falls back to auto-approve.
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
writer = DiffWriter(mode="normal") # Prompt for every edit
|
|
52
|
+
writer = DiffWriter(mode="auto") # Auto-approve all edits
|
|
53
|
+
writer = DiffWriter(mode="plan") # Read-only, preview only
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, mode: str = MODE_NORMAL, preview_limit: int = 2000):
|
|
57
|
+
"""Initialize DiffWriter.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
mode: Permission mode - "normal" (prompt), "auto" (auto-approve), "plan" (read-only)
|
|
61
|
+
preview_limit: Max chars to include in diff preview
|
|
62
|
+
"""
|
|
63
|
+
self.mode = mode
|
|
64
|
+
self.preview_limit = preview_limit
|
|
65
|
+
self.io = None # Set by agent's _sync_tool_io event handler
|
|
66
|
+
|
|
67
|
+
def write(self, path: str, content: str) -> str:
|
|
68
|
+
"""Write content to a file with diff display and approval.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
path: File path to write to
|
|
72
|
+
content: Content to write
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Success message, rejection message with feedback, or plan mode preview
|
|
76
|
+
"""
|
|
77
|
+
file_path = Path(path)
|
|
78
|
+
file_exists = file_path.exists()
|
|
79
|
+
|
|
80
|
+
# Plan mode = read-only, just show what would happen
|
|
81
|
+
if self.mode == MODE_PLAN:
|
|
82
|
+
diff_text = self._generate_diff(path, content)
|
|
83
|
+
preview = self._build_preview(diff_text, content, file_exists)
|
|
84
|
+
return f"[Plan mode] Would write {len(content)} bytes to {path}\n\nPreview:\n{preview[:500]}"
|
|
85
|
+
|
|
86
|
+
# Generate diff for display
|
|
87
|
+
diff_text = self._generate_diff(path, content)
|
|
88
|
+
preview = self._build_preview(diff_text, content, file_exists)
|
|
89
|
+
preview, truncated = self._truncate_preview(preview)
|
|
90
|
+
|
|
91
|
+
# Send diff preview to UI (best-effort, doesn't block)
|
|
92
|
+
self._send_preview(path, preview, truncated, file_exists)
|
|
93
|
+
|
|
94
|
+
# Check approval based on mode
|
|
95
|
+
if self.mode == MODE_NORMAL:
|
|
96
|
+
choice = self._ask_approval(path, preview, truncated)
|
|
97
|
+
|
|
98
|
+
if choice == "reject":
|
|
99
|
+
feedback = self._ask_feedback(path)
|
|
100
|
+
return f"User rejected changes to {path}. Feedback: {feedback}"
|
|
101
|
+
|
|
102
|
+
if choice == "approve_all":
|
|
103
|
+
self.mode = MODE_AUTO # Switch to auto mode for rest of session
|
|
104
|
+
|
|
105
|
+
# Auto mode or approved: write file
|
|
106
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
file_path.write_text(content, encoding="utf-8")
|
|
108
|
+
|
|
109
|
+
mode_note = "" if self.mode == MODE_NORMAL else f" [{self.mode} mode]"
|
|
110
|
+
return f"Wrote {len(content)} bytes to {path}{mode_note}"
|
|
111
|
+
|
|
112
|
+
def diff(self, path: str, content: str) -> str:
|
|
113
|
+
"""Show diff without writing (preview mode).
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
path: File path to compare against
|
|
117
|
+
content: New content to compare
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Diff string in unified format
|
|
121
|
+
"""
|
|
122
|
+
diff_text = self._generate_diff(path, content)
|
|
123
|
+
if diff_text:
|
|
124
|
+
return diff_text
|
|
125
|
+
return f"No changes to {path}"
|
|
126
|
+
|
|
127
|
+
def read(self, path: str) -> str:
|
|
128
|
+
"""Read file contents.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
path: File path to read
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
File contents or error message
|
|
135
|
+
"""
|
|
136
|
+
file_path = Path(path)
|
|
137
|
+
if not file_path.exists():
|
|
138
|
+
return f"Error: File {path} not found"
|
|
139
|
+
return file_path.read_text(encoding="utf-8")
|
|
140
|
+
|
|
141
|
+
# =========================================================================
|
|
142
|
+
# IO Communication (Web Mode)
|
|
143
|
+
# =========================================================================
|
|
144
|
+
|
|
145
|
+
def _get_io(self):
|
|
146
|
+
"""Return active io channel if available."""
|
|
147
|
+
return self.io
|
|
148
|
+
|
|
149
|
+
def _send_preview(self, path: str, preview: str, truncated: bool, file_exists: bool) -> None:
|
|
150
|
+
"""Send diff preview event to UI client.
|
|
151
|
+
|
|
152
|
+
This is informational - UI can render a nice diff view.
|
|
153
|
+
Does not block or wait for response.
|
|
154
|
+
"""
|
|
155
|
+
io = self._get_io()
|
|
156
|
+
if not io:
|
|
157
|
+
return
|
|
158
|
+
io.send({
|
|
159
|
+
"type": "diff_preview",
|
|
160
|
+
"path": path,
|
|
161
|
+
"preview": preview,
|
|
162
|
+
"truncated": truncated,
|
|
163
|
+
"file_exists": file_exists,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
def _ask_approval(self, path: str, preview: str, truncated: bool) -> str:
|
|
167
|
+
"""Ask user for approval via io channel.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
"approve" - Apply this change
|
|
171
|
+
"approve_all" - Apply and switch to auto mode
|
|
172
|
+
"reject" - Reject and ask for feedback
|
|
173
|
+
"""
|
|
174
|
+
io = self._get_io()
|
|
175
|
+
if not io:
|
|
176
|
+
# No io channel = auto-approve (for non-web usage)
|
|
177
|
+
return "approve"
|
|
178
|
+
|
|
179
|
+
question = f"Apply changes to {path}?"
|
|
180
|
+
if truncated:
|
|
181
|
+
question += " (preview truncated)"
|
|
182
|
+
|
|
183
|
+
response = self._ask_user(
|
|
184
|
+
question,
|
|
185
|
+
options=[
|
|
186
|
+
"Yes, apply this change",
|
|
187
|
+
"Yes to all (auto-approve)",
|
|
188
|
+
"No, reject and give feedback",
|
|
189
|
+
],
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Parse response
|
|
193
|
+
if response == "Yes to all (auto-approve)":
|
|
194
|
+
return "approve_all"
|
|
195
|
+
if response == "Yes, apply this change":
|
|
196
|
+
return "approve"
|
|
197
|
+
|
|
198
|
+
# Handle free-text responses
|
|
199
|
+
if response:
|
|
200
|
+
lowered = response.strip().lower()
|
|
201
|
+
if "yes to all" in lowered or lowered == "auto":
|
|
202
|
+
return "approve_all"
|
|
203
|
+
if lowered.startswith("yes") or lowered == "approve":
|
|
204
|
+
return "approve"
|
|
205
|
+
|
|
206
|
+
return "reject"
|
|
207
|
+
|
|
208
|
+
def _ask_feedback(self, path: str) -> str:
|
|
209
|
+
"""Ask user for feedback when changes are rejected."""
|
|
210
|
+
feedback = self._ask_user(f"What should the agent do instead for {path}?")
|
|
211
|
+
return feedback or "No feedback provided"
|
|
212
|
+
|
|
213
|
+
def _ask_user(self, question: str, options: Optional[list] = None) -> str:
|
|
214
|
+
"""Send ask_user event and block until user responds.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
question: Question to display
|
|
218
|
+
options: List of option strings (buttons in UI)
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
User's answer string
|
|
222
|
+
"""
|
|
223
|
+
io = self._get_io()
|
|
224
|
+
if not io:
|
|
225
|
+
return ""
|
|
226
|
+
|
|
227
|
+
# Send ask_user event
|
|
228
|
+
io.send({
|
|
229
|
+
"type": "ask_user",
|
|
230
|
+
"question": question,
|
|
231
|
+
"options": options,
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
# Block waiting for response
|
|
235
|
+
response = io.receive()
|
|
236
|
+
|
|
237
|
+
# Handle connection closed
|
|
238
|
+
if response.get("type") == "io_closed":
|
|
239
|
+
return ""
|
|
240
|
+
|
|
241
|
+
# Extract answer
|
|
242
|
+
answer = response.get("answer", "")
|
|
243
|
+
if isinstance(answer, list):
|
|
244
|
+
return ", ".join([str(a) for a in answer])
|
|
245
|
+
return str(answer) if answer is not None else ""
|
|
246
|
+
|
|
247
|
+
# =========================================================================
|
|
248
|
+
# Diff Generation
|
|
249
|
+
# =========================================================================
|
|
250
|
+
|
|
251
|
+
def _generate_diff(self, path: str, new_content: str) -> str:
|
|
252
|
+
"""Generate unified diff between existing file and new content."""
|
|
253
|
+
file_path = Path(path)
|
|
254
|
+
|
|
255
|
+
if not file_path.exists():
|
|
256
|
+
return "" # New file, no diff to show
|
|
257
|
+
|
|
258
|
+
original_lines = file_path.read_text(encoding="utf-8").splitlines(keepends=True)
|
|
259
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
260
|
+
|
|
261
|
+
diff = difflib.unified_diff(
|
|
262
|
+
original_lines,
|
|
263
|
+
new_lines,
|
|
264
|
+
fromfile=f"a/{path}",
|
|
265
|
+
tofile=f"b/{path}",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return "".join(diff)
|
|
269
|
+
|
|
270
|
+
def _build_preview(self, diff_text: str, content: str, file_exists: bool) -> str:
|
|
271
|
+
"""Build human-readable preview for approval dialog."""
|
|
272
|
+
if diff_text:
|
|
273
|
+
return diff_text
|
|
274
|
+
if file_exists:
|
|
275
|
+
return "(no changes)"
|
|
276
|
+
return self._new_file_preview(content)
|
|
277
|
+
|
|
278
|
+
def _truncate_preview(self, preview: str) -> Tuple[str, bool]:
|
|
279
|
+
"""Truncate preview to configured limit."""
|
|
280
|
+
if len(preview) <= self.preview_limit:
|
|
281
|
+
return preview, False
|
|
282
|
+
return preview[: self.preview_limit] + "\n...(truncated)", True
|
|
283
|
+
|
|
284
|
+
def _new_file_preview(self, content: str, max_lines: int = 50) -> str:
|
|
285
|
+
"""Create preview for new file (no existing file to diff against)."""
|
|
286
|
+
lines = content.splitlines()
|
|
287
|
+
preview_lines = lines[:max_lines]
|
|
288
|
+
preview = "\n".join([f"+ {line}" for line in preview_lines])
|
|
289
|
+
if len(lines) > max_lines:
|
|
290
|
+
preview += f"\n... ({len(lines) - max_lines} more lines)"
|
|
291
|
+
return preview
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Edit tool for precise string replacement."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def edit(
|
|
7
|
+
file_path: str,
|
|
8
|
+
old_string: str,
|
|
9
|
+
new_string: str,
|
|
10
|
+
replace_all: bool = False,
|
|
11
|
+
) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Replace a string in a file with precise matching.
|
|
14
|
+
|
|
15
|
+
More token-efficient than rewriting entire files. Use for small, targeted changes.
|
|
16
|
+
The old_string must exist in the file (and be unique unless replace_all=True).
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
file_path: Path to the file to edit
|
|
20
|
+
old_string: Exact string to replace (must be unique in file)
|
|
21
|
+
new_string: String to replace with
|
|
22
|
+
replace_all: If True, replace all occurrences; if False, old_string must be unique
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Success message or error description
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
edit("app.py", "def foo():", "def bar():")
|
|
29
|
+
edit("config.json", '"debug": false', '"debug": true')
|
|
30
|
+
edit("README.md", "v1.0", "v2.0", replace_all=True)
|
|
31
|
+
"""
|
|
32
|
+
path = Path(file_path)
|
|
33
|
+
|
|
34
|
+
if not path.exists():
|
|
35
|
+
return f"Error: File '{file_path}' does not exist"
|
|
36
|
+
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
return f"Error: '{file_path}' is not a file"
|
|
39
|
+
|
|
40
|
+
content = path.read_text(encoding="utf-8")
|
|
41
|
+
|
|
42
|
+
# Check if old_string exists
|
|
43
|
+
count = content.count(old_string)
|
|
44
|
+
|
|
45
|
+
if count == 0:
|
|
46
|
+
# Try to help debug: show similar strings
|
|
47
|
+
lines_with_similar = []
|
|
48
|
+
for i, line in enumerate(content.splitlines(), 1):
|
|
49
|
+
# Check if any significant part of old_string is in the line
|
|
50
|
+
if len(old_string) > 10:
|
|
51
|
+
# For longer strings, check first 20 chars
|
|
52
|
+
if old_string[:20] in line or old_string[-20:] in line:
|
|
53
|
+
lines_with_similar.append(f" Line {i}: {line[:80]}")
|
|
54
|
+
elif old_string.strip() in line:
|
|
55
|
+
lines_with_similar.append(f" Line {i}: {line[:80]}")
|
|
56
|
+
|
|
57
|
+
msg = f"Error: String not found in '{file_path}'"
|
|
58
|
+
if lines_with_similar:
|
|
59
|
+
msg += f"\n\nSimilar content found:\n" + "\n".join(lines_with_similar[:5])
|
|
60
|
+
return msg
|
|
61
|
+
|
|
62
|
+
if count > 1 and not replace_all:
|
|
63
|
+
# Show where the duplicates are
|
|
64
|
+
lines_with_match = []
|
|
65
|
+
for i, line in enumerate(content.splitlines(), 1):
|
|
66
|
+
if old_string in line:
|
|
67
|
+
lines_with_match.append(f" Line {i}: {line[:80]}")
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
f"Error: String appears {count} times in '{file_path}'. "
|
|
71
|
+
f"Use replace_all=True to replace all, or provide more context to make it unique.\n\n"
|
|
72
|
+
f"Found at:\n" + "\n".join(lines_with_match[:10])
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Perform replacement
|
|
76
|
+
if replace_all:
|
|
77
|
+
new_content = content.replace(old_string, new_string)
|
|
78
|
+
replaced_count = count
|
|
79
|
+
else:
|
|
80
|
+
new_content = content.replace(old_string, new_string, 1)
|
|
81
|
+
replaced_count = 1
|
|
82
|
+
|
|
83
|
+
# Write back
|
|
84
|
+
path.write_text(new_content, encoding="utf-8")
|
|
85
|
+
|
|
86
|
+
if replace_all and replaced_count > 1:
|
|
87
|
+
return f"Replaced {replaced_count} occurrences in '{file_path}'"
|
|
88
|
+
else:
|
|
89
|
+
return f"Successfully edited '{file_path}'"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Glob tool for file pattern matching."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
IGNORE_DIRS = {
|
|
7
|
+
".git",
|
|
8
|
+
"node_modules",
|
|
9
|
+
"__pycache__",
|
|
10
|
+
".venv",
|
|
11
|
+
"venv",
|
|
12
|
+
".env",
|
|
13
|
+
"dist",
|
|
14
|
+
"build",
|
|
15
|
+
".next",
|
|
16
|
+
".nuxt",
|
|
17
|
+
"target",
|
|
18
|
+
".idea",
|
|
19
|
+
".vscode",
|
|
20
|
+
"*.egg-info",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def glob(pattern: str, path: Optional[str] = None) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Search for files matching a glob pattern.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
pattern: Glob pattern (e.g., "**/*.py", "src/**/*.ts")
|
|
30
|
+
path: Directory to search in (default: current directory)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Matching file paths, one per line, sorted by modification time (newest first)
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
glob("**/*.py") # All Python files
|
|
37
|
+
glob("src/**/*.tsx") # All TSX files in src/
|
|
38
|
+
glob("**/test_*.py") # All test files
|
|
39
|
+
glob("*.md", "docs") # Markdown files in docs/
|
|
40
|
+
"""
|
|
41
|
+
base = Path(path) if path else Path.cwd()
|
|
42
|
+
|
|
43
|
+
if not base.exists():
|
|
44
|
+
return f"Error: Path '{base}' does not exist"
|
|
45
|
+
|
|
46
|
+
if not base.is_dir():
|
|
47
|
+
return f"Error: Path '{base}' is not a directory"
|
|
48
|
+
|
|
49
|
+
matches = []
|
|
50
|
+
for p in base.glob(pattern):
|
|
51
|
+
if p.is_file() and not _should_ignore(p):
|
|
52
|
+
matches.append(p)
|
|
53
|
+
|
|
54
|
+
if not matches:
|
|
55
|
+
return f"No files found matching '{pattern}'"
|
|
56
|
+
|
|
57
|
+
# Sort by modification time (newest first)
|
|
58
|
+
matches.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
59
|
+
|
|
60
|
+
# Format output
|
|
61
|
+
results = []
|
|
62
|
+
for p in matches[:100]: # Limit to 100 results
|
|
63
|
+
rel_path = p.relative_to(base) if path else p.relative_to(Path.cwd())
|
|
64
|
+
results.append(str(rel_path))
|
|
65
|
+
|
|
66
|
+
output = "\n".join(results)
|
|
67
|
+
|
|
68
|
+
if len(matches) > 100:
|
|
69
|
+
output += f"\n\n... and {len(matches) - 100} more files"
|
|
70
|
+
|
|
71
|
+
return output
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _should_ignore(path: Path) -> bool:
|
|
75
|
+
"""Check if path should be ignored."""
|
|
76
|
+
parts = path.parts
|
|
77
|
+
for part in parts:
|
|
78
|
+
if part in IGNORE_DIRS:
|
|
79
|
+
return True
|
|
80
|
+
# Handle patterns like *.egg-info
|
|
81
|
+
for ignore in IGNORE_DIRS:
|
|
82
|
+
if "*" in ignore and Path(part).match(ignore):
|
|
83
|
+
return True
|
|
84
|
+
return False
|