tsugite-cli 0.14.2__tar.gz → 0.15.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.2 → tsugite_cli-0.15.0}/AGENTS.md +3 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/PKG-INFO +1 -1
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/pyproject.toml +3 -1
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_inheritance.py +140 -38
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/runner.py +36 -8
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_utils.py +36 -22
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_agents/default.md +55 -0
- tsugite_cli-0.15.0/tsugite/builtin_agents/job_verifier.md +58 -0
- tsugite_cli-0.15.0/tsugite/builtin_agents/job_worker.md +93 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/daemon.py +16 -11
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/run.py +50 -46
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/agent.py +73 -5
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/claude_code.py +38 -9
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/executor.py +21 -2
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/adapters/base.py +131 -24
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/adapters/discord.py +1 -1
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/adapters/http.py +475 -38
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/adapters/scheduler_adapter.py +9 -0
- tsugite_cli-0.15.0/tsugite/daemon/commands.py +435 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/compaction_scheduler.py +54 -12
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/config.py +38 -2
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/gateway.py +82 -2
- tsugite_cli-0.15.0/tsugite/daemon/job_store.py +242 -0
- tsugite_cli-0.15.0/tsugite/daemon/jobs_orchestrator.py +1410 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/memory.py +36 -0
- tsugite_cli-0.15.0/tsugite/daemon/pty_manager.py +401 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/push.py +4 -1
- tsugite_cli-0.15.0/tsugite/daemon/record_store.py +146 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/scheduler.py +127 -57
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/session_runner.py +40 -18
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/session_store.py +130 -29
- tsugite_cli-0.15.0/tsugite/daemon/terminal_runtime.py +176 -0
- tsugite_cli-0.15.0/tsugite/daemon/terminal_store.py +132 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/css/console.css +18 -62
- tsugite_cli-0.15.0/tsugite/daemon/web/css/job-flows.css +91 -0
- tsugite_cli-0.15.0/tsugite/daemon/web/css/jobs-tab.css +158 -0
- tsugite_cli-0.15.0/tsugite/daemon/web/css/jobs.css +203 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/css/responsive.css +45 -11
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/css/styles.css +6 -68
- tsugite_cli-0.15.0/tsugite/daemon/web/css/terminal.css +335 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/css/theme.css +4 -0
- tsugite_cli-0.15.0/tsugite/daemon/web/css/tsu-modal.css +298 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/index.html +961 -107
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/app.js +51 -2
- tsugite_cli-0.15.0/tsugite/daemon/web/js/utils/tsu-modal.js +66 -0
- tsugite_cli-0.15.0/tsugite/daemon/web/js/utils/xterm-loader.js +183 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/utils.js +28 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversation/attachments.js +2 -3
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversation/history.js +102 -1
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversation/input.js +79 -11
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversation/sessions.js +56 -10
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversations.js +292 -32
- tsugite_cli-0.15.0/tsugite/daemon/web/js/views/jobs.js +367 -0
- tsugite_cli-0.15.0/tsugite/daemon/web/js/views/terminals.js +734 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/md_agents.py +1 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/models.py +22 -11
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/anthropic.py +1 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/claude_code.py +14 -1
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/schemas/agent.schema.json +5 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/__init__.py +19 -1
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/agents.py +14 -18
- tsugite_cli-0.15.0/tsugite/tools/jobs.py +190 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/schedule.py +9 -14
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/sessions.py +2 -11
- tsugite_cli-0.15.0/tsugite/tools/terminal.py +319 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/repl_completer.py +5 -20
- tsugite_cli-0.14.2/tsugite/daemon/commands.py +0 -206
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/.gitignore +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/CONTRIBUTING.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/LICENSE +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/README.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_preparation.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/exec_directives.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/exec_runner.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/helpers.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/history_integration.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/metrics.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/models.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/agent_runner/validation.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/auto_context.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/base.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/file.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/inline.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/storage.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/url.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/attachments/youtube.py +0 -0
- {tsugite_cli-0.14.2/tsugite/builtin_skills → tsugite_cli-0.15.0/tsugite/builtin_agents}/.gitkeep +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_agents/code_searcher.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_agents/file_searcher.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_agents/onboard.md +0 -0
- {tsugite_cli-0.14.2/tsugite/builtin_agents → tsugite_cli-0.15.0/tsugite/builtin_skills}/.gitkeep +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cache.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/agents.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/attachments.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/cache.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/chat.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/config.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/helpers.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/history.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/init.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/models.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/plugins.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/render.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/secrets.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/skills.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/tools.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/usage.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/validate.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/cli/workspace.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/config.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/console.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/constants.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/content_blocks.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/memory.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/proxy.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/sandbox.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/state.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/subprocess_executor.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/core/tools.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/adapters/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/auth.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/icons/icon-192.png +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/icons/icon-512-maskable.png +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/icons/icon-512.png +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/icons/screenshot-narrow.png +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/icons/screenshot-wide.png +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/api.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/vendor/marked.LICENSE.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/vendor/marked.esm.min.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversation/event_types.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/conversation/streaming.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/file-editor.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/schedules.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/usage.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/webhooks.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/js/views/workspace.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/manifest.json +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/web/sw.js +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/daemon/webhook_store.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/events/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/events/base.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/events/bus.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/events/events.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/events/helpers.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/exceptions.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/history/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/history/models.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/history/reconstruction.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/history/storage.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/hooks.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/interaction.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/options.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/plugins.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/base.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/model_cache.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/model_registry.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/ollama.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/openai_compat.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/providers/openrouter.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/renderer.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/schemas/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/backend.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/env.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/exec.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/file.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/masking.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/registry.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/secrets/sqlite.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/shell_tool_config.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/skill_discovery.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/AGENTS.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/IDENTITY.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/MEMORY.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/USER.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/personas/casual-technical.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/personas/marvin.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/templates/personas/minimal.md +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/fs.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/history.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/http.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/interactive.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/notify.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/scratchpad.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/secrets.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/shell.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/shell_tools.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/skills.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tools/time.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/tsugite.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/base.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/chat.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/helpers.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/jsonl.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/live.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/plain.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/repl_chat.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/repl_commands.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui/repl_handler.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/ui_context.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/usage/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/usage/store.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/user_agent.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/utils.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/workspace/__init__.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/workspace/context.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/workspace/models.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/workspace/session.py +0 -0
- {tsugite_cli-0.14.2 → tsugite_cli-0.15.0}/tsugite/workspace/templates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tsugite-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.15.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
|
|
|
@@ -14,7 +14,7 @@ from tsugite.core.agent import TsugiteAgent # noqa: E402
|
|
|
14
14
|
from tsugite.core.executor import LocalExecutor # noqa: E402
|
|
15
15
|
from tsugite.exceptions import AgentExecutionError, is_prompt_too_long_error # noqa: E402
|
|
16
16
|
from tsugite.md_agents import AgentConfig, parse_agent_file # noqa: E402
|
|
17
|
-
from tsugite.models import resolve_effective_model # noqa: E402
|
|
17
|
+
from tsugite.models import resolve_effective_model, strip_reserved_model_kwargs # noqa: E402
|
|
18
18
|
from tsugite.options import ExecutionOptions # noqa: E402
|
|
19
19
|
from tsugite.renderer import AgentRenderer # noqa: E402
|
|
20
20
|
from tsugite.utils import is_interactive # noqa: E402
|
|
@@ -411,6 +411,7 @@ async def _execute_agent_with_prompt(
|
|
|
411
411
|
hook_vars: Optional[Dict[str, str]] = None,
|
|
412
412
|
continue_conversation_id: Optional[str] = None,
|
|
413
413
|
user_input_for_history: Optional[str] = None,
|
|
414
|
+
channel_metadata: Optional[Dict[str, Any]] = None,
|
|
414
415
|
) -> str | AgentExecutionResult:
|
|
415
416
|
"""Execute agent with a prepared agent.
|
|
416
417
|
|
|
@@ -491,13 +492,29 @@ async def _execute_agent_with_prompt(
|
|
|
491
492
|
# Get model string
|
|
492
493
|
model_string = _get_model_string(exec_options.model_override, agent_config)
|
|
493
494
|
|
|
494
|
-
# Merge
|
|
495
|
-
|
|
495
|
+
# Merge model_kwargs from the agent frontmatter first (lowest precedence), then
|
|
496
|
+
# explicit caller kwargs override. This lets agents declare e.g.
|
|
497
|
+
# `model_kwargs: {response_format: {type: json_object}}` once and have every
|
|
498
|
+
# invocation get structured output without each caller threading it through.
|
|
499
|
+
final_model_kwargs = {}
|
|
500
|
+
if hasattr(agent_config, "model_kwargs") and agent_config.model_kwargs:
|
|
501
|
+
final_model_kwargs.update(agent_config.model_kwargs)
|
|
502
|
+
final_model_kwargs.update(model_kwargs or {})
|
|
503
|
+
# Reject provider-call args (messages/model/stream) that would collide with the
|
|
504
|
+
# explicit keywords splatted alongside **model_kwargs in core/agent.py.
|
|
505
|
+
final_model_kwargs = strip_reserved_model_kwargs(final_model_kwargs)
|
|
506
|
+
|
|
507
|
+
# Resolve reasoning_effort. Precedence (highest wins): exec_options override >
|
|
508
|
+
# caller-supplied model_kwargs > agent.model_kwargs > agent.reasoning_effort field.
|
|
509
|
+
# The override is a user-facing CLI/daemon knob - it MUST beat any agent default,
|
|
510
|
+
# including one baked into agent.model_kwargs (which would otherwise shadow it).
|
|
511
|
+
# Caller kwargs and agent.model_kwargs are already in final_model_kwargs from the merge
|
|
512
|
+
# above, so only the override and the agent.reasoning_effort fallback resolve here.
|
|
496
513
|
effort_override = getattr(exec_options, "reasoning_effort_override", None)
|
|
497
|
-
if
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
514
|
+
if effort_override:
|
|
515
|
+
final_model_kwargs["reasoning_effort"] = effort_override
|
|
516
|
+
elif "reasoning_effort" not in final_model_kwargs:
|
|
517
|
+
if hasattr(agent_config, "reasoning_effort") and agent_config.reasoning_effort:
|
|
501
518
|
final_model_kwargs["reasoning_effort"] = agent_config.reasoning_effort
|
|
502
519
|
|
|
503
520
|
from tsugite.models import resolve_reasoning_effort
|
|
@@ -573,7 +590,12 @@ async def _execute_agent_with_prompt(
|
|
|
573
590
|
# model_request event; replaying the whole thing here would clog the
|
|
574
591
|
# chat bubble with noise the user never wrote.
|
|
575
592
|
display_prompt = user_input_for_history or prepared.original_prompt or prepared.rendered_prompt
|
|
576
|
-
record_user_input(
|
|
593
|
+
record_user_input(
|
|
594
|
+
session_storage,
|
|
595
|
+
display_prompt,
|
|
596
|
+
attachments=prepared.attachments,
|
|
597
|
+
channel_metadata=channel_metadata,
|
|
598
|
+
)
|
|
577
599
|
except Exception as e:
|
|
578
600
|
logger.debug("Could not open session storage for live event recording: %s", e)
|
|
579
601
|
session_storage = None
|
|
@@ -743,6 +765,7 @@ def run_agent(
|
|
|
743
765
|
attachments: Optional[List[Any]] = None,
|
|
744
766
|
path_context: Optional[Any] = None,
|
|
745
767
|
user_input_for_history: Optional[str] = None,
|
|
768
|
+
channel_metadata: Optional[Dict[str, Any]] = None,
|
|
746
769
|
) -> str | AgentExecutionResult:
|
|
747
770
|
"""Run a Tsugite agent (sync wrapper around run_agent_async).
|
|
748
771
|
|
|
@@ -793,6 +816,7 @@ def run_agent(
|
|
|
793
816
|
attachments=attachments,
|
|
794
817
|
path_context=path_context,
|
|
795
818
|
user_input_for_history=user_input_for_history,
|
|
819
|
+
channel_metadata=channel_metadata,
|
|
796
820
|
)
|
|
797
821
|
)
|
|
798
822
|
|
|
@@ -976,6 +1000,7 @@ async def run_agent_async(
|
|
|
976
1000
|
hook_vars=hook_vars,
|
|
977
1001
|
continue_conversation_id=continue_conversation_id,
|
|
978
1002
|
user_input_for_history=user_input_for_history,
|
|
1003
|
+
channel_metadata=channel_metadata,
|
|
979
1004
|
)
|
|
980
1005
|
except (RuntimeError, AgentExecutionError) as e:
|
|
981
1006
|
err_str = str(e).lower()
|
|
@@ -1001,6 +1026,7 @@ async def run_agent_async(
|
|
|
1001
1026
|
hook_vars=hook_vars,
|
|
1002
1027
|
continue_conversation_id=continue_conversation_id,
|
|
1003
1028
|
user_input_for_history=user_input_for_history,
|
|
1029
|
+
channel_metadata=channel_metadata,
|
|
1004
1030
|
)
|
|
1005
1031
|
raise
|
|
1006
1032
|
finally:
|
|
@@ -1026,6 +1052,8 @@ _MULTISTEP_FRAMEWORK_FLAG_DEFAULTS: Dict[str, Any] = {
|
|
|
1026
1052
|
"has_notify_tool": False,
|
|
1027
1053
|
"agent_name": "",
|
|
1028
1054
|
"can_spawn_sessions": False,
|
|
1055
|
+
"can_spawn_jobs": False,
|
|
1056
|
+
"can_use_pty": False,
|
|
1029
1057
|
"is_channel_session": False,
|
|
1030
1058
|
"active_sessions": [],
|
|
1031
1059
|
"recent_completions": [],
|
|
@@ -134,34 +134,48 @@ def list_local_agents(base_path: Path = None) -> dict[str, List[Path]]:
|
|
|
134
134
|
base_path: Base directory to search from (defaults to cwd)
|
|
135
135
|
|
|
136
136
|
Returns:
|
|
137
|
-
Dictionary mapping location names to list of agent paths
|
|
137
|
+
Dictionary mapping location names to list of agent paths,
|
|
138
|
+
ordered: Built-in, Plugins, Current directory, .tsugite/, agents/.
|
|
138
139
|
"""
|
|
139
|
-
from .agent_inheritance import
|
|
140
|
+
from .agent_inheritance import AgentDirSource, iter_agent_search_paths
|
|
140
141
|
|
|
141
142
|
if base_path is None:
|
|
142
143
|
base_path = Path.cwd()
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
145
|
+
project_labels = {
|
|
146
|
+
(base_path / ".tsugite").resolve(): ".tsugite/",
|
|
147
|
+
(base_path / ".tsugite" / "agents").resolve(): ".tsugite/agents/",
|
|
148
|
+
(base_path / "agents").resolve(): "agents/",
|
|
149
|
+
base_path.resolve(): "Current directory",
|
|
150
|
+
}
|
|
151
|
+
source_labels = {
|
|
152
|
+
AgentDirSource.BUILTIN: "Built-in",
|
|
153
|
+
AgentDirSource.PLUGIN: "Plugins",
|
|
154
|
+
}
|
|
155
|
+
grouped: dict[str, List[Path]] = {}
|
|
156
|
+
|
|
157
|
+
for entry in iter_agent_search_paths(current_agent_dir=base_path):
|
|
158
|
+
if entry.source == AgentDirSource.GLOBAL:
|
|
159
|
+
continue
|
|
160
|
+
if not entry.path.exists() or not entry.path.is_dir():
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
if entry.source == AgentDirSource.PROJECT:
|
|
164
|
+
label = project_labels.get(entry.path.resolve())
|
|
165
|
+
else:
|
|
166
|
+
label = source_labels.get(entry.source)
|
|
167
|
+
if label is None:
|
|
168
|
+
continue
|
|
158
169
|
|
|
159
|
-
|
|
160
|
-
if
|
|
161
|
-
all_md_files = sorted(location_path.glob("*.md"))
|
|
170
|
+
all_md_files = sorted(entry.path.glob("*.md"))
|
|
171
|
+
if entry.source == AgentDirSource.PROJECT:
|
|
162
172
|
agent_files = [f for f in all_md_files if _is_valid_agent_file(f)]
|
|
173
|
+
else:
|
|
174
|
+
agent_files = all_md_files
|
|
163
175
|
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
if agent_files:
|
|
177
|
+
grouped.setdefault(label, []).extend(agent_files)
|
|
166
178
|
|
|
167
|
-
|
|
179
|
+
# Preserve historical display order: Built-in, Plugins, then project labels.
|
|
180
|
+
label_order = ["Built-in", "Plugins", "Current directory", ".tsugite/", ".tsugite/agents/", "agents/"]
|
|
181
|
+
return {label: grouped[label] for label in label_order if label in grouped}
|
|
@@ -33,6 +33,8 @@ tools:
|
|
|
33
33
|
- "@schedule"
|
|
34
34
|
- "@scratchpad"
|
|
35
35
|
- "@sessions"
|
|
36
|
+
- "@jobs"
|
|
37
|
+
- "@terminal"
|
|
36
38
|
- "@tmux"
|
|
37
39
|
auto_load_skills:
|
|
38
40
|
- response-patterns
|
|
@@ -133,6 +135,59 @@ Avoid prose in `status_text` - something like "topic and status now being update
|
|
|
133
135
|
You are managing a shared channel. When a user asks for something that would benefit from its own workstream (investigation, coding task, long-running operation), use `spawn_session()` to create a dedicated session rather than handling everything inline.
|
|
134
136
|
{% endif %}
|
|
135
137
|
{% endif %}
|
|
138
|
+
{% if can_spawn_jobs | default(false) %}
|
|
139
|
+
|
|
140
|
+
**Background Jobs**: A Job is a background sub-session with a verification loop. Use it when you want the work *checked*, not just done.
|
|
141
|
+
|
|
142
|
+
- `spawn_job(prompt, acceptance_criteria=[...], max_attempts=3, notify_when="never")` — Spawn a verified Job. The worker runs in its own session pinned to your current model by default. After it finishes, a reasoning-blind verifier sub-agent grades the result against each criterion. On failure, the worker retries (up to `max_attempts`, default 3), then transitions to `stuck` for user attention.
|
|
143
|
+
- `get_job(job_id)` — Snapshot any Job's state, including per-criterion verdicts (`result.ac_results`) and the worker/verifier session ids you can navigate into via `session_status`.
|
|
144
|
+
- `list_jobs(session_id=..., state=..., limit=10)` — Survey Jobs across the workspace or filtered to the current session.
|
|
145
|
+
|
|
146
|
+
**When to use what:**
|
|
147
|
+
- **Inline** — single edit, one-shot read, small calculation. Don't spawn anything.
|
|
148
|
+
- **`spawn_session`** — fire-and-forget background work; no judgment is applied to the result.
|
|
149
|
+
- **`spawn_job`** — when "done" has a checkable shape (tests pass, copy satisfies criteria, refactor preserves behavior). The verification loop is the value.
|
|
150
|
+
|
|
151
|
+
**Acceptance criteria**: pass each criterion as a plain string. The verifier reads them verbatim and returns per-criterion pass/fail in `result.ac_results`:
|
|
152
|
+
```python
|
|
153
|
+
spawn_job(
|
|
154
|
+
prompt="Refactor the agent loop to handle nested tool calls",
|
|
155
|
+
acceptance_criteria=[
|
|
156
|
+
"All existing tests still pass",
|
|
157
|
+
"Adds at least 2 new tests for nested calls",
|
|
158
|
+
"No new dependencies",
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Notification cost**: `notify_when` defaults to `"never"`. Set it to `"stuck"` for a wake-up only when the Job needs user intervention, or `"terminal"` for any final state. Every notification adds a turn to this conversation — prefer polling `get_job` over `notify_when="terminal"` for routine tracking.
|
|
164
|
+
|
|
165
|
+
**When a Job goes `stuck`** (max_attempts verifier rejections), the user can retry-with-hint or mark-done-anyway from the UI; you usually don't need to act. If asked to diagnose, `get_job(job_id)` returns the worker session id and each `ac_results[].reason` — read those, suggest a different approach, or open the worker session via `session_status` to inspect what went wrong.
|
|
166
|
+
|
|
167
|
+
**A Job spawned by the user via `/job`** appears in your conversation's job list once it resolves. You don't create the Job in that case — the user did. Your role is to make use of its result when relevant.
|
|
168
|
+
{% endif %}
|
|
169
|
+
{% if can_use_pty | default(false) %}
|
|
170
|
+
|
|
171
|
+
## Driving PTYs
|
|
172
|
+
|
|
173
|
+
A PTY is a live, interactive terminal you can spawn and drive. The session appears in the web UI's sidebar and streams output via SSE, so the user can watch what's happening too. Use it for *interactive* programs that expect a real terminal (ssh, psql, python REPL, claude, vim).
|
|
174
|
+
|
|
175
|
+
- **`run()`** — one-shot commands that exit on their own (`ls`, `git status`, `python script.py`). Captures stdout/stderr/exit code in a single call. Use this 99% of the time.
|
|
176
|
+
- **`pty_create()`** — interactive programs that need stdin keystrokes over time (REPLs, ssh sessions, `claude` itself). The PTY stays alive across turns until you `pty_kill` it.
|
|
177
|
+
- **`tmux_*` skill** — same niche as PTY but a separate tmux server; prefer `pty_*` for new work since it has a live web UI surface. `tmux` is still appropriate for multi-pane / persistent-window workflows.
|
|
178
|
+
|
|
179
|
+
Tools: `pty_create`, `pty_send_keys`, `pty_capture`, `pty_kill`, `pty_list`.
|
|
180
|
+
|
|
181
|
+
Typical loop:
|
|
182
|
+
```python
|
|
183
|
+
term = pty_create("python3 -i")
|
|
184
|
+
pty_send_keys(term["terminal_id"], "print(2 + 2)")
|
|
185
|
+
print(pty_capture(term["terminal_id"], lines=5)) # see the prompt + result
|
|
186
|
+
pty_kill(term["terminal_id"])
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For escape sequences pass raw bytes with `enter=False`: `pty_send_keys(id, "\x03", enter=False)` sends Ctrl+C.
|
|
190
|
+
{% endif %}
|
|
136
191
|
{% if active_sessions | default([]) %}
|
|
137
192
|
|
|
138
193
|
**Active Sessions:**
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: job_verifier
|
|
3
|
+
description: Reasoning-blind verifier — judges a Job's structured summary against its acceptance criteria. Returns strict JSON. Spawned fresh per round with no parent context.
|
|
4
|
+
extends: none
|
|
5
|
+
max_turns: 5
|
|
6
|
+
tools: [read_file, run]
|
|
7
|
+
model_kwargs:
|
|
8
|
+
response_format:
|
|
9
|
+
type: json_object
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Job Verifier
|
|
13
|
+
|
|
14
|
+
You are a reasoning-blind verifier. You receive a list of acceptance criteria
|
|
15
|
+
and a work-output blob (the worker's structured summary, optionally with a
|
|
16
|
+
`git diff`). You evaluate each criterion strictly: addressed by the visible
|
|
17
|
+
artifacts, or not.
|
|
18
|
+
|
|
19
|
+
You do not see the worker's reasoning. You do not see the parent chat. If the
|
|
20
|
+
worker claims something but the artifacts don't show it, mark that criterion as
|
|
21
|
+
failed.
|
|
22
|
+
|
|
23
|
+
## Task
|
|
24
|
+
|
|
25
|
+
{{ user_prompt }}
|
|
26
|
+
|
|
27
|
+
## Output contract
|
|
28
|
+
|
|
29
|
+
Your final reply MUST be strictly-valid JSON matching this schema. NO prose
|
|
30
|
+
outside the JSON. NO markdown fences.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
{
|
|
34
|
+
"ac_results": [
|
|
35
|
+
{"ac_text": "<verbatim AC>", "pass": true|false, "reason": "<one sentence>"}
|
|
36
|
+
],
|
|
37
|
+
"overall_pass": true|false
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`overall_pass` is `true` only if every `ac_results[i].pass` is `true`.
|
|
42
|
+
|
|
43
|
+
## How to verify
|
|
44
|
+
|
|
45
|
+
- Read the worker's `## Summary`, `## Output` (if present), `## Acceptance
|
|
46
|
+
criteria`, and `## Artifacts` sections.
|
|
47
|
+
- When the deliverable is inline text (poem, snippet, written answer), it
|
|
48
|
+
lives in `## Output`. Judge ACs about the content (length, format, contains
|
|
49
|
+
word X, syllable count, etc.) against the verbatim Output text.
|
|
50
|
+
- If a PR URL or commit SHA is provided, you may inspect it via `run` (e.g.
|
|
51
|
+
`git show <sha>`, `gh pr view <url>`) when that materially affects the
|
|
52
|
+
verdict.
|
|
53
|
+
- If a file path is mentioned and you doubt the change, use `read_file` to
|
|
54
|
+
confirm.
|
|
55
|
+
- Do not run long-running commands or perform setup; you have a turn budget.
|
|
56
|
+
- Be skeptical, not pedantic. A criterion like "tests pass" is met if the
|
|
57
|
+
worker reports a passing test run; you don't need to re-run unless evidence
|
|
58
|
+
is contradictory.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: job_worker
|
|
3
|
+
description: Executes a Job spawned from a chat session. Drives work to a structured summary that a separate verifier grades against the Job's acceptance criteria.
|
|
4
|
+
max_turns: 40
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Job Worker
|
|
8
|
+
|
|
9
|
+
You are executing a **Job** spawned from a chat session. The user's prompt is the
|
|
10
|
+
goal. A separate verifier sub-agent will judge your work against the listed
|
|
11
|
+
acceptance criteria using ONLY the structured summary you produce in your final
|
|
12
|
+
reply — it does not see your reasoning or this conversation. Be honest in your
|
|
13
|
+
summary; the verifier sees the same artifacts you do.
|
|
14
|
+
|
|
15
|
+
## Do the work BEFORE writing the summary
|
|
16
|
+
|
|
17
|
+
The structured-summary contract below describes the FORMAT of your final reply,
|
|
18
|
+
not a substitute for doing the work. Before composing any Summary text:
|
|
19
|
+
|
|
20
|
+
- If the task requires reading, listing, executing, fetching, or modifying
|
|
21
|
+
anything, **call tools to do those things**. Do not describe work you
|
|
22
|
+
haven't actually performed.
|
|
23
|
+
- If the task is purely generative (write a haiku, draft an email, answer a
|
|
24
|
+
question from existing knowledge), no tool calls are needed — the work IS
|
|
25
|
+
the text you produce, and it goes in the `## Output` section.
|
|
26
|
+
- If you write a Summary like "I listed the files…" but you never called
|
|
27
|
+
`list_files`/`run`, you have fabricated the work. The verifier will catch
|
|
28
|
+
this and stuck you. For jobs with no AC the verifier is skipped, but the
|
|
29
|
+
user reads your transcript and will notice.
|
|
30
|
+
|
|
31
|
+
When in doubt: use a tool. A short tool call beats a fabricated claim.
|
|
32
|
+
|
|
33
|
+
## Task
|
|
34
|
+
|
|
35
|
+
{{ user_prompt }}
|
|
36
|
+
|
|
37
|
+
## Final-reply contract
|
|
38
|
+
|
|
39
|
+
Your final reply MUST be a markdown document with these sections, in this
|
|
40
|
+
order. Do not include anything outside them.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
## Summary
|
|
44
|
+
|
|
45
|
+
<2-4 lines: what you did and the outcome.>
|
|
46
|
+
|
|
47
|
+
## Output
|
|
48
|
+
|
|
49
|
+
<Only when the deliverable IS the content of your reply (a haiku, a written
|
|
50
|
+
answer, a snippet, an analysis) rather than a file/PR/commit. Include the
|
|
51
|
+
verbatim deliverable here — verbatim text, code in fenced blocks, etc.
|
|
52
|
+
Omit this section entirely if the work was a file change / PR / commit.>
|
|
53
|
+
|
|
54
|
+
## Acceptance criteria
|
|
55
|
+
|
|
56
|
+
- <verbatim AC text>: <addressed | not addressed> — <one-line evidence (file changed, command output, link)>
|
|
57
|
+
- <next AC>: ...
|
|
58
|
+
|
|
59
|
+
## Artifacts
|
|
60
|
+
|
|
61
|
+
- PR: <url or "none">
|
|
62
|
+
- Commits: <short SHAs or "none">
|
|
63
|
+
- Files changed: <comma-separated paths or "none">
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If no acceptance criteria were provided, write the literal text `- (none)`
|
|
67
|
+
under that section and nothing else. **Do NOT invent AC entries by treating
|
|
68
|
+
the user's prompt as an AC** — the prompt is the goal, not a criterion.
|
|
69
|
+
|
|
70
|
+
## When to use `## Output` vs `## Artifacts`
|
|
71
|
+
|
|
72
|
+
- **`## Output`** — the deliverable is your written text itself. Examples:
|
|
73
|
+
poem, haiku, short story, written answer to a question, generated code
|
|
74
|
+
snippet that the user wants to read inline, summary / analysis text.
|
|
75
|
+
Include the deliverable verbatim — fenced code blocks for code, plain
|
|
76
|
+
text otherwise. The verifier evaluates this as the work product.
|
|
77
|
+
- **`## Artifacts`** — the deliverable is a file change, a PR, or a commit
|
|
78
|
+
on disk. List the URLs / SHAs / paths so the verifier can find them.
|
|
79
|
+
|
|
80
|
+
If a job produces both (e.g. wrote a poem AND committed it to a file),
|
|
81
|
+
include both sections.
|
|
82
|
+
|
|
83
|
+
## Discipline
|
|
84
|
+
|
|
85
|
+
- **Do the work first, summarize second.** Never write a Summary describing
|
|
86
|
+
actions you did not actually take.
|
|
87
|
+
- Do not declare an AC `addressed` you did not actually address. The verifier
|
|
88
|
+
will catch you and loop you back, wasting turns.
|
|
89
|
+
- Do not invent artifacts (PR URLs, commit SHAs). If you did not commit, say so.
|
|
90
|
+
- Do not invent AC entries from the prompt. Write `- (none)` when none given.
|
|
91
|
+
- Keep the Summary tight — verifier reads it byte-for-byte.
|
|
92
|
+
- If your deliverable is text, put it in `## Output` — the Summary alone is
|
|
93
|
+
too tight a slot for the work product, and dropping it loses information.
|