tsugite-cli 0.14.3__tar.gz → 0.16.0__tar.gz
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.
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/AGENTS.md +17 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/PKG-INFO +3 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/README.md +2 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/pyproject.toml +3 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_inheritance.py +140 -38
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/__init__.py +12 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/helpers.py +116 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/runner.py +149 -22
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_utils.py +36 -22
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/default.md +55 -0
- tsugite_cli-0.16.0/tsugite/builtin_agents/job_verifier.md +58 -0
- tsugite_cli-0.16.0/tsugite/builtin_agents/job_worker.md +93 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +1 -0
- tsugite_cli-0.16.0/tsugite/builtin_skills/external-agent-bridge/SKILL.md +73 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/scheduling/SKILL.md +1 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +1 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +1 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +1 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +4 -2
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/__init__.py +2 -0
- tsugite_cli-0.16.0/tsugite/cli/exec.py +176 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/__init__.py +2 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/agent.py +75 -9
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/claude_code.py +53 -11
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/executor.py +58 -4
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/subprocess_executor.py +4 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/base.py +125 -13
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/http.py +470 -35
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/scheduler_adapter.py +9 -0
- tsugite_cli-0.16.0/tsugite/daemon/commands.py +435 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/compaction_scheduler.py +15 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/config.py +57 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/gateway.py +147 -2
- tsugite_cli-0.16.0/tsugite/daemon/job_store.py +245 -0
- tsugite_cli-0.16.0/tsugite/daemon/jobs_orchestrator.py +1467 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/memory.py +36 -0
- tsugite_cli-0.16.0/tsugite/daemon/pty_manager.py +401 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/push.py +4 -1
- tsugite_cli-0.16.0/tsugite/daemon/record_store.py +146 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/scheduler.py +184 -56
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/session_runner.py +43 -17
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/session_store.py +66 -8
- tsugite_cli-0.16.0/tsugite/daemon/terminal_runtime.py +238 -0
- tsugite_cli-0.16.0/tsugite/daemon/terminal_store.py +132 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/console.css +18 -62
- tsugite_cli-0.16.0/tsugite/daemon/web/css/job-flows.css +91 -0
- tsugite_cli-0.16.0/tsugite/daemon/web/css/jobs-tab.css +158 -0
- tsugite_cli-0.16.0/tsugite/daemon/web/css/jobs.css +203 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/responsive.css +45 -11
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/styles.css +6 -68
- tsugite_cli-0.16.0/tsugite/daemon/web/css/terminal.css +335 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/theme.css +4 -0
- tsugite_cli-0.16.0/tsugite/daemon/web/css/tsu-modal.css +298 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/index.html +963 -109
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/app.js +51 -2
- tsugite_cli-0.16.0/tsugite/daemon/web/js/utils/tsu-modal.js +66 -0
- tsugite_cli-0.16.0/tsugite/daemon/web/js/utils/xterm-loader.js +183 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/utils.js +28 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/attachments.js +2 -3
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/history.js +121 -6
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/input.js +79 -11
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/sessions.js +35 -10
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversations.js +296 -26
- tsugite_cli-0.16.0/tsugite/daemon/web/js/views/jobs.js +367 -0
- tsugite_cli-0.16.0/tsugite/daemon/web/js/views/terminals.js +740 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/reconstruction.py +13 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/md_agents.py +5 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/models.py +22 -11
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/options.py +2 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/anthropic.py +21 -5
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/claude_code.py +14 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/model_registry.py +10 -3
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/openai_compat.py +4 -3
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/schemas/agent.schema.json +18 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/__init__.py +48 -1
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/agents.py +37 -23
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/fs.py +16 -14
- tsugite_cli-0.16.0/tsugite/tools/jobs.py +196 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/schedule.py +14 -14
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/sessions.py +22 -11
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/skills.py +25 -7
- tsugite_cli-0.16.0/tsugite/tools/terminal.py +319 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_completer.py +5 -20
- tsugite_cli-0.14.3/tsugite/daemon/commands.py +0 -206
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/.gitignore +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/CONTRIBUTING.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/LICENSE +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_preparation.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/exec_directives.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/exec_runner.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/history_integration.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/metrics.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/models.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/validation.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/auto_context.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/base.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/file.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/inline.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/storage.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/url.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/youtube.py +0 -0
- {tsugite_cli-0.14.3/tsugite/builtin_skills → tsugite_cli-0.16.0/tsugite/builtin_agents}/.gitkeep +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/code_searcher.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/file_searcher.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/onboard.md +0 -0
- {tsugite_cli-0.14.3/tsugite/builtin_agents → tsugite_cli-0.16.0/tsugite/builtin_skills}/.gitkeep +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cache.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/agents.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/attachments.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/cache.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/chat.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/config.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/daemon.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/helpers.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/history.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/init.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/models.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/plugins.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/render.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/run.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/secrets.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/skills.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/tools.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/usage.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/validate.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/workspace.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/config.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/console.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/constants.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/content_blocks.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/memory.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/proxy.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/sandbox.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/state.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/tools.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/discord.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/auth.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/icon-192.png +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/icon-512-maskable.png +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/icon-512.png +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/screenshot-narrow.png +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/screenshot-wide.png +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/api.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/vendor/marked.LICENSE.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/vendor/marked.esm.min.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/event_types.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/streaming.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/file-editor.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/schedules.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/usage.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/webhooks.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/workspace.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/manifest.json +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/sw.js +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/webhook_store.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/base.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/bus.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/events.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/helpers.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/exceptions.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/models.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/storage.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/hooks.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/interaction.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/plugins.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/base.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/model_cache.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/ollama.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/openrouter.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/renderer.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/schemas/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/backend.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/env.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/exec.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/file.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/masking.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/registry.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/sqlite.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/shell_tool_config.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/skill_discovery.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/AGENTS.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/IDENTITY.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/MEMORY.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/USER.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/personas/casual-technical.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/personas/marvin.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/personas/minimal.md +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/history.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/http.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/interactive.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/notify.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/scratchpad.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/secrets.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/shell.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/shell_tools.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/time.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tsugite.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/base.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/chat.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/helpers.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/jsonl.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/live.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/plain.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_chat.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_commands.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_handler.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui_context.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/usage/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/usage/store.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/user_agent.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/utils.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/__init__.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/context.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/models.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/session.py +0 -0
- {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/templates.py +0 -0
|
@@ -24,6 +24,9 @@ uv run pytest --cov=tsugite --cov-report=html
|
|
|
24
24
|
uv run black .
|
|
25
25
|
uv run ruff check .
|
|
26
26
|
uv run pylint tsugite
|
|
27
|
+
|
|
28
|
+
# One-time: enable repo pre-commit hook (lint + format on commit)
|
|
29
|
+
git config core.hooksPath .githooks
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
### Testing Agents
|
|
@@ -41,6 +44,20 @@ uv run tsu render agent.md "task"
|
|
|
41
44
|
uv run tsu validate agents/*.md
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
### Reusing tsugite from external coding agents
|
|
48
|
+
|
|
49
|
+
`tsu exec` runs a Python snippet in tsugite's tool namespace, so a tsugite skill's code
|
|
50
|
+
(which calls `read_file`, `http_request`, `get_secret`, ...) runs without the full agent
|
|
51
|
+
loop. Secrets are allowlisted + masked; `--no-network` / `--sandbox` isolate the run. See
|
|
52
|
+
[docs/external-agent-integration.md](docs/external-agent-integration.md) for reusing skills
|
|
53
|
+
and agents from Claude Code, Cursor, or any agent that runs shell commands.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
echo 'read_file(path="README.md")' | tsu exec -
|
|
57
|
+
tsu exec snippet.py --tools @fs,@http --allow-secret gh-token
|
|
58
|
+
tsu run +<agent> "task" # or reuse a whole agent
|
|
59
|
+
```
|
|
60
|
+
|
|
44
61
|
### Schema Management
|
|
45
62
|
```bash
|
|
46
63
|
# Regenerate JSON schema after modifying AgentConfig
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsugite-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: Micro-agent runner for task automation using markdown definitions
|
|
5
5
|
Author: Justyn Shull
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -400,6 +400,8 @@ tsu run +default "task" --sandbox --no-network
|
|
|
400
400
|
|
|
401
401
|
Filesystem access is limited to the workspace. Network goes through a filtering proxy that only allows domains you specify.
|
|
402
402
|
|
|
403
|
+
The daemon can also sandbox its agents (off by default, configured in `daemon.yaml`). See [docs/sandbox.md](docs/sandbox.md).
|
|
404
|
+
|
|
403
405
|
## Config and Data Directories
|
|
404
406
|
|
|
405
407
|
All paths follow [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) conventions and can be overridden with the standard environment variables.
|
|
@@ -129,6 +129,8 @@ tsu run +default "task" --sandbox --no-network
|
|
|
129
129
|
|
|
130
130
|
Filesystem access is limited to the workspace. Network goes through a filtering proxy that only allows domains you specify.
|
|
131
131
|
|
|
132
|
+
The daemon can also sandbox its agents (off by default, configured in `daemon.yaml`). See [docs/sandbox.md](docs/sandbox.md).
|
|
133
|
+
|
|
132
134
|
## Config and Data Directories
|
|
133
135
|
|
|
134
136
|
All paths follow [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) conventions and can be overridden with the standard environment variables.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tsugite-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.0"
|
|
4
4
|
description = "Micro-agent runner for task automation using markdown definitions"
|
|
5
5
|
authors = [{ name = "Justyn Shull" }]
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -67,6 +67,7 @@ members = ["plugins/*"]
|
|
|
67
67
|
tsugite-cli = { workspace = true }
|
|
68
68
|
tsugite-tmux = { workspace = true }
|
|
69
69
|
tsugite-acp = { workspace = true }
|
|
70
|
+
tsugite-codex-cli = { workspace = true }
|
|
70
71
|
|
|
71
72
|
[tool.ruff]
|
|
72
73
|
target-version = "py311"
|
|
@@ -142,4 +143,5 @@ dev = [
|
|
|
142
143
|
"vulture>=2.14",
|
|
143
144
|
"tsugite-tmux",
|
|
144
145
|
"tsugite-acp",
|
|
146
|
+
"tsugite-codex-cli",
|
|
145
147
|
]
|
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
"""Agent inheritance resolution for Tsugite."""
|
|
2
2
|
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import importlib.util
|
|
5
|
+
import logging
|
|
3
6
|
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from functools import lru_cache
|
|
4
10
|
from pathlib import Path
|
|
5
11
|
from typing import Any, Dict, List, Optional, Set
|
|
6
12
|
|
|
7
13
|
from tsugite.utils import ensure_file_exists
|
|
8
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgentDirSource(Enum):
|
|
19
|
+
"""Origin of an agent search directory."""
|
|
20
|
+
|
|
21
|
+
WORKSPACE = "workspace" # caller-passed workspace agents/ (mutable)
|
|
22
|
+
PROJECT = "project" # caller-passed project agents/ (mutable; today only http.py)
|
|
23
|
+
BUILTIN = "builtin" # tsugite/builtin_agents/ (read-only)
|
|
24
|
+
PLUGIN = "plugin" # plugin <pkg>/agents/ via tsugite.plugins entry-points (read-only)
|
|
25
|
+
GLOBAL = "global" # XDG global agents/ (mutable)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class AgentDir:
|
|
30
|
+
"""A directory that may contain agent .md files."""
|
|
31
|
+
|
|
32
|
+
path: Path
|
|
33
|
+
source: AgentDirSource
|
|
34
|
+
readonly: bool
|
|
35
|
+
|
|
9
36
|
|
|
10
37
|
def get_builtin_agents_path() -> Path:
|
|
11
38
|
"""Get the built-in agents directory path.
|
|
@@ -37,6 +64,113 @@ def get_global_agents_paths() -> List[Path]:
|
|
|
37
64
|
return paths
|
|
38
65
|
|
|
39
66
|
|
|
67
|
+
@lru_cache(maxsize=1)
|
|
68
|
+
def get_plugin_agents_paths() -> List[Path]:
|
|
69
|
+
"""Get agent directory paths contributed by installed plugins.
|
|
70
|
+
|
|
71
|
+
Scans the ``tsugite.plugins`` entry-point group; each plugin's package
|
|
72
|
+
may ship an ``agents/`` directory next to its top-level module. Plugin
|
|
73
|
+
agent dirs are treated as read-only.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List of plugin-supplied agent directories (deduped, only existing dirs)
|
|
77
|
+
"""
|
|
78
|
+
from tsugite.plugins import GROUP_PLUGINS
|
|
79
|
+
|
|
80
|
+
paths: List[Path] = []
|
|
81
|
+
seen: set[Path] = set()
|
|
82
|
+
try:
|
|
83
|
+
eps = importlib.metadata.entry_points(group=GROUP_PLUGINS)
|
|
84
|
+
except Exception as e: # pragma: no cover - defensive
|
|
85
|
+
logger.debug("Failed to scan plugin entry points: %s", e)
|
|
86
|
+
return paths
|
|
87
|
+
|
|
88
|
+
for ep in eps:
|
|
89
|
+
module_name = ep.value.split(":")[0].strip()
|
|
90
|
+
top_level = module_name.split(".")[0]
|
|
91
|
+
try:
|
|
92
|
+
spec = importlib.util.find_spec(top_level)
|
|
93
|
+
except Exception:
|
|
94
|
+
spec = None
|
|
95
|
+
if spec is None or not spec.origin:
|
|
96
|
+
continue
|
|
97
|
+
agents_dir = Path(spec.origin).parent / "agents"
|
|
98
|
+
resolved = agents_dir.resolve()
|
|
99
|
+
if resolved in seen:
|
|
100
|
+
continue
|
|
101
|
+
if not agents_dir.is_dir():
|
|
102
|
+
continue
|
|
103
|
+
seen.add(resolved)
|
|
104
|
+
paths.append(agents_dir)
|
|
105
|
+
return paths
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def iter_agent_search_paths(
|
|
109
|
+
*,
|
|
110
|
+
current_agent_dir: Optional[Path] = None,
|
|
111
|
+
workspace: Optional[Any] = None,
|
|
112
|
+
extra_project_dirs: Optional[List[Path]] = None,
|
|
113
|
+
include_local_roots: bool = True,
|
|
114
|
+
) -> List[AgentDir]:
|
|
115
|
+
"""Yield the canonical agent-search order, deduped by resolved path.
|
|
116
|
+
|
|
117
|
+
Order:
|
|
118
|
+
|
|
119
|
+
1. workspace.agents_dir (if workspace given)
|
|
120
|
+
2. current_agent_dir / ".tsugite"
|
|
121
|
+
3. current_agent_dir / ".tsugite" / "agents"
|
|
122
|
+
4. current_agent_dir / "agents"
|
|
123
|
+
5. current_agent_dir itself (for `<name>.md` directly in cwd)
|
|
124
|
+
6. caller-supplied extra project dirs (HTTP adapter feeds per-agent dirs here)
|
|
125
|
+
7. builtin agents
|
|
126
|
+
8. XDG global agents (user agents beat plugin agents of the same name)
|
|
127
|
+
9. plugin-supplied agents
|
|
128
|
+
|
|
129
|
+
include_local_roots: when False, skip entries 2 and 5 (the bare .tsugite/
|
|
130
|
+
and cwd roots). Name-resolution callers want them so `extends: foo` finds a
|
|
131
|
+
sibling foo.md; discovery callers that glob *.md must skip them, otherwise
|
|
132
|
+
every frontmattered note in a docs/notes workspace lists as an agent.
|
|
133
|
+
|
|
134
|
+
Callers that only care about a subset filter on ``AgentDir.source``.
|
|
135
|
+
"""
|
|
136
|
+
results: List[AgentDir] = []
|
|
137
|
+
seen: set[Path] = set()
|
|
138
|
+
|
|
139
|
+
def _add(path: Path, source: AgentDirSource, readonly: bool) -> None:
|
|
140
|
+
try:
|
|
141
|
+
resolved = path.resolve()
|
|
142
|
+
except (OSError, RuntimeError):
|
|
143
|
+
return
|
|
144
|
+
if resolved in seen:
|
|
145
|
+
return
|
|
146
|
+
seen.add(resolved)
|
|
147
|
+
results.append(AgentDir(path=path, source=source, readonly=readonly))
|
|
148
|
+
|
|
149
|
+
if workspace is not None and hasattr(workspace, "agents_dir"):
|
|
150
|
+
_add(workspace.agents_dir, AgentDirSource.WORKSPACE, readonly=False)
|
|
151
|
+
|
|
152
|
+
if current_agent_dir is not None:
|
|
153
|
+
if include_local_roots:
|
|
154
|
+
_add(current_agent_dir / ".tsugite", AgentDirSource.PROJECT, readonly=False)
|
|
155
|
+
_add(current_agent_dir / ".tsugite" / "agents", AgentDirSource.PROJECT, readonly=False)
|
|
156
|
+
_add(current_agent_dir / "agents", AgentDirSource.PROJECT, readonly=False)
|
|
157
|
+
if include_local_roots:
|
|
158
|
+
_add(current_agent_dir, AgentDirSource.PROJECT, readonly=False)
|
|
159
|
+
|
|
160
|
+
for extra in extra_project_dirs or []:
|
|
161
|
+
_add(extra, AgentDirSource.PROJECT, readonly=False)
|
|
162
|
+
|
|
163
|
+
_add(get_builtin_agents_path(), AgentDirSource.BUILTIN, readonly=True)
|
|
164
|
+
|
|
165
|
+
for global_dir in get_global_agents_paths():
|
|
166
|
+
_add(global_dir, AgentDirSource.GLOBAL, readonly=False)
|
|
167
|
+
|
|
168
|
+
for plugin_path in get_plugin_agents_paths():
|
|
169
|
+
_add(plugin_path, AgentDirSource.PLUGIN, readonly=True)
|
|
170
|
+
|
|
171
|
+
return results
|
|
172
|
+
|
|
173
|
+
|
|
40
174
|
def find_agent_file(
|
|
41
175
|
agent_ref: str,
|
|
42
176
|
current_agent_dir: Path,
|
|
@@ -52,14 +186,8 @@ def find_agent_file(
|
|
|
52
186
|
Returns:
|
|
53
187
|
Path to agent file if found, None otherwise
|
|
54
188
|
|
|
55
|
-
Search order
|
|
56
|
-
|
|
57
|
-
2. Workspace agents directory (if workspace provided)
|
|
58
|
-
3. .tsugite/{name}.md (project-local shared)
|
|
59
|
-
4. ./agents/{name}.md (project convention)
|
|
60
|
-
5. ./{name}.md (current directory)
|
|
61
|
-
6. Built-in agents directory (tsugite/builtin_agents/)
|
|
62
|
-
7. Global agent directories (XDG order)
|
|
189
|
+
Search order matches `iter_agent_search_paths`: workspace agents,
|
|
190
|
+
project-local (.tsugite/, agents/, current dir), builtin, global, plugin.
|
|
63
191
|
"""
|
|
64
192
|
# If it looks like a path, resolve it relative to current agent
|
|
65
193
|
if "/" in agent_ref or agent_ref.endswith(".md"):
|
|
@@ -71,38 +199,12 @@ def find_agent_file(
|
|
|
71
199
|
return abs_path.resolve()
|
|
72
200
|
return None
|
|
73
201
|
|
|
74
|
-
# Ensure .md extension for name-based lookup
|
|
75
202
|
agent_name = agent_ref if agent_ref.endswith(".md") else f"{agent_ref}.md"
|
|
76
203
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
workspace_agents = workspace.agents_dir
|
|
82
|
-
if workspace_agents.exists():
|
|
83
|
-
search_paths.append(workspace_agents / agent_name)
|
|
84
|
-
|
|
85
|
-
# Project-local locations
|
|
86
|
-
search_paths.extend(
|
|
87
|
-
[
|
|
88
|
-
current_agent_dir / ".tsugite" / agent_name,
|
|
89
|
-
current_agent_dir / "agents" / agent_name,
|
|
90
|
-
current_agent_dir / agent_name,
|
|
91
|
-
]
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Built-in agents directory
|
|
95
|
-
builtin_path = get_builtin_agents_path() / agent_name
|
|
96
|
-
search_paths.append(builtin_path)
|
|
97
|
-
|
|
98
|
-
# Global locations
|
|
99
|
-
for global_dir in get_global_agents_paths():
|
|
100
|
-
search_paths.append(global_dir / agent_name)
|
|
101
|
-
|
|
102
|
-
# Return first existing path
|
|
103
|
-
for path in search_paths:
|
|
104
|
-
if path.exists():
|
|
105
|
-
return path.resolve()
|
|
204
|
+
for entry in iter_agent_search_paths(current_agent_dir=current_agent_dir, workspace=workspace):
|
|
205
|
+
candidate = entry.path / agent_name
|
|
206
|
+
if candidate.exists():
|
|
207
|
+
return candidate.resolve()
|
|
106
208
|
|
|
107
209
|
return None
|
|
108
210
|
|
|
@@ -3,16 +3,22 @@
|
|
|
3
3
|
from tsugite.agent_runner.exec_directives import execute_exec_directives # noqa: F401
|
|
4
4
|
from tsugite.agent_runner.exec_runner import ExecBlockResult, run_python_block # noqa: F401
|
|
5
5
|
from tsugite.agent_runner.helpers import ( # noqa: F401
|
|
6
|
+
SandboxContext,
|
|
7
|
+
SandboxToolDeniedError,
|
|
6
8
|
clear_allowed_agents,
|
|
7
9
|
clear_current_agent,
|
|
10
|
+
clear_sandbox_context,
|
|
8
11
|
get_allowed_agents,
|
|
9
12
|
get_allowed_secrets,
|
|
10
13
|
get_current_agent,
|
|
11
14
|
get_display_console,
|
|
15
|
+
get_sandbox_context,
|
|
12
16
|
get_ui_handler,
|
|
17
|
+
sandbox_context_to_override,
|
|
13
18
|
set_allowed_agents,
|
|
14
19
|
set_allowed_secrets,
|
|
15
20
|
set_current_agent,
|
|
21
|
+
set_sandbox_context,
|
|
16
22
|
)
|
|
17
23
|
from tsugite.agent_runner.metrics import StepMetrics, display_step_metrics # noqa: F401
|
|
18
24
|
from tsugite.agent_runner.models import AgentExecutionResult # noqa: F401
|
|
@@ -57,4 +63,10 @@ __all__ = [
|
|
|
57
63
|
"set_allowed_secrets",
|
|
58
64
|
"get_display_console",
|
|
59
65
|
"get_ui_handler",
|
|
66
|
+
"SandboxContext",
|
|
67
|
+
"SandboxToolDeniedError",
|
|
68
|
+
"get_sandbox_context",
|
|
69
|
+
"set_sandbox_context",
|
|
70
|
+
"clear_sandbox_context",
|
|
71
|
+
"sandbox_context_to_override",
|
|
60
72
|
]
|
|
@@ -1,18 +1,133 @@
|
|
|
1
1
|
"""Shared helper functions for agent execution."""
|
|
2
2
|
|
|
3
3
|
import threading
|
|
4
|
-
from
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any, List, Optional
|
|
5
7
|
|
|
6
8
|
from rich.console import Console
|
|
7
9
|
|
|
8
10
|
from tsugite.console import get_stderr_console
|
|
9
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from tsugite.options import ExecutionOptions
|
|
14
|
+
|
|
10
15
|
# Console for warnings and debug output (stderr)
|
|
11
16
|
_stderr_console = get_stderr_console()
|
|
12
17
|
|
|
13
18
|
# Thread-local storage for tracking currently executing agent
|
|
14
19
|
_current_agent_context = threading.local()
|
|
15
20
|
|
|
21
|
+
# Thread-local storage for the active sandbox policy. Set per-run (in the same
|
|
22
|
+
# thread as the agent loop and its parent-only tool dispatch) when an agent runs
|
|
23
|
+
# sandboxed; read by host-exec/spawn tools to inherit the sandbox or be denied.
|
|
24
|
+
# Thread-local (like _current_agent_context) so concurrent daemon sessions don't
|
|
25
|
+
# clobber each other.
|
|
26
|
+
_sandbox_context = threading.local()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class SandboxContext:
|
|
31
|
+
"""Effective sandbox policy for the currently executing agent.
|
|
32
|
+
|
|
33
|
+
Presence of a SandboxContext means the agent is running sandboxed; tools read
|
|
34
|
+
it to propagate the same isolation to anything they spawn.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
allow_domains: List[str] = field(default_factory=list)
|
|
38
|
+
no_network: bool = False
|
|
39
|
+
extra_ro_binds: List[Path] = field(default_factory=list)
|
|
40
|
+
extra_rw_binds: List[Path] = field(default_factory=list)
|
|
41
|
+
workspace_dir: Optional[Path] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SandboxToolDeniedError(RuntimeError):
|
|
45
|
+
"""Raised when a host-exec tool is refused because the agent runs sandboxed
|
|
46
|
+
(see the deny_when_sandboxed decorator)."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def set_sandbox_context(ctx: Optional["SandboxContext"]) -> None:
|
|
50
|
+
"""Set (or clear, with None) the active sandbox policy for this thread."""
|
|
51
|
+
_sandbox_context.value = ctx
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_sandbox_context() -> Optional["SandboxContext"]:
|
|
55
|
+
"""Return the active sandbox policy, or None when not running sandboxed."""
|
|
56
|
+
return getattr(_sandbox_context, "value", None)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def clear_sandbox_context() -> None:
|
|
60
|
+
"""Clear the active sandbox policy from thread-local storage."""
|
|
61
|
+
if hasattr(_sandbox_context, "value"):
|
|
62
|
+
delattr(_sandbox_context, "value")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def sandbox_context_to_override() -> Optional[dict]:
|
|
66
|
+
"""Serialize the active sandbox policy as a metadata override dict, or None.
|
|
67
|
+
|
|
68
|
+
Spawn tools stamp this onto the records they create (sessions, jobs,
|
|
69
|
+
schedules) so the spawned daemon run inherits the same sandbox when it later
|
|
70
|
+
reaches the adapter chokepoint. The shape matches SandboxSettings so it can
|
|
71
|
+
be validated back there; paths are stringified to survive JSON metadata.
|
|
72
|
+
"""
|
|
73
|
+
ctx = get_sandbox_context()
|
|
74
|
+
if ctx is None:
|
|
75
|
+
return None
|
|
76
|
+
return {
|
|
77
|
+
"enabled": True,
|
|
78
|
+
"no_network": ctx.no_network,
|
|
79
|
+
"allow_domains": list(ctx.allow_domains),
|
|
80
|
+
"extra_ro_binds": [str(p) for p in ctx.extra_ro_binds],
|
|
81
|
+
"extra_rw_binds": [str(p) for p in ctx.extra_rw_binds],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_sandbox_policy(
|
|
86
|
+
exec_options: "ExecutionOptions",
|
|
87
|
+
*,
|
|
88
|
+
workspace_dir: Optional[Path] = None,
|
|
89
|
+
agent_config: Any = None,
|
|
90
|
+
):
|
|
91
|
+
"""Resolve the effective sandbox policy into (SandboxConfig, SandboxContext).
|
|
92
|
+
|
|
93
|
+
Returns (None, None) when the sandbox is off. Shared by the agent runner and
|
|
94
|
+
`tsu exec` so the two never drift. Agent frontmatter (network/sandbox) can only
|
|
95
|
+
tighten the CLI/daemon ceiling, never loosen it.
|
|
96
|
+
|
|
97
|
+
Raises RuntimeError if the sandbox is requested but bwrap is unavailable.
|
|
98
|
+
"""
|
|
99
|
+
from tsugite.agent_runner.runner import resolve_effective_sandbox
|
|
100
|
+
from tsugite.core.sandbox import BubblewrapSandbox, SandboxConfig
|
|
101
|
+
|
|
102
|
+
sandbox_on, allow_domains, no_network = resolve_effective_sandbox(
|
|
103
|
+
daemon_enabled=exec_options.sandbox,
|
|
104
|
+
daemon_domains=list(exec_options.allow_domains),
|
|
105
|
+
daemon_no_network=exec_options.no_network,
|
|
106
|
+
fm_network=getattr(agent_config, "network", None),
|
|
107
|
+
fm_sandbox=getattr(agent_config, "sandbox", None),
|
|
108
|
+
)
|
|
109
|
+
if not sandbox_on:
|
|
110
|
+
return None, None
|
|
111
|
+
|
|
112
|
+
if not BubblewrapSandbox.check_available():
|
|
113
|
+
raise RuntimeError("bwrap not found. Install bubblewrap or use --no-sandbox.")
|
|
114
|
+
|
|
115
|
+
ctx = SandboxContext(
|
|
116
|
+
allow_domains=allow_domains,
|
|
117
|
+
no_network=no_network,
|
|
118
|
+
extra_ro_binds=list(exec_options.extra_ro_binds),
|
|
119
|
+
extra_rw_binds=list(exec_options.extra_rw_binds),
|
|
120
|
+
workspace_dir=workspace_dir,
|
|
121
|
+
)
|
|
122
|
+
config = SandboxConfig(
|
|
123
|
+
allowed_domains=ctx.allow_domains,
|
|
124
|
+
no_network=ctx.no_network,
|
|
125
|
+
extra_ro_binds=ctx.extra_ro_binds,
|
|
126
|
+
extra_rw_binds=ctx.extra_rw_binds,
|
|
127
|
+
)
|
|
128
|
+
return config, ctx
|
|
129
|
+
|
|
130
|
+
|
|
16
131
|
# Module-level storage for allowed agents (single-threaded CLI execution)
|
|
17
132
|
# Subagents run in separate processes, so this doesn't need to be thread-local
|
|
18
133
|
_allowed_agents: Optional[List[str]] = None
|