tsugite-cli 0.17.2__tar.gz → 0.18.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.17.2 → tsugite_cli-0.18.0}/AGENTS.md +2 -2
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/PKG-INFO +1 -1
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/pyproject.toml +3 -1
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/history_integration.py +30 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/runner.py +10 -8
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/code_searcher.md +1 -1
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/default.md +29 -27
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/file_searcher.md +11 -11
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/job_verifier.md +2 -2
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +57 -57
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/python-math/SKILL.md +10 -10
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/response-patterns/SKILL.md +6 -6
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/scheduling/SKILL.md +10 -10
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +1 -1
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +1 -1
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +7 -7
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/agent.py +105 -35
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/subprocess_executor.py +66 -23
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/reconstruction.py +30 -4
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/hooks.py +20 -35
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/anthropic.py +3 -13
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/openai_compat.py +4 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/jobs.py +70 -1
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/schedule.py +19 -6
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/.gitignore +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/CONTRIBUTING.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/LICENSE +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/README.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_inheritance.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_preparation.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/exec_directives.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/exec_runner.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/helpers.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/metrics.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/models.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_runner/validation.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/agent_utils.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/auto_context.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/base.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/file.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/inline.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/storage.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/attachments/url.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/.gitkeep +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/job_worker.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_agents/onboard.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/.gitkeep +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/external-agent-bridge/SKILL.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cache.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cancellation.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/agents.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/attachments.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/cache.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/chat.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/config.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/daemon.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/exec.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/helpers.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/history.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/init.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/models.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/plugins.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/render.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/run.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/secrets.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/skills.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/tools.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/usage.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/validate.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/cli/workspace.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/config.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/console.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/constants.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/content_blocks.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/executor.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/executor_registry.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/memory.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/proxy.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/record_store.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/sandbox.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/state.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/core/tools.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/base.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/bus.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/events.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/events/helpers.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/exceptions.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/base.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/models.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/registry.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/sqlite_backend.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/sqlite_conn.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/sqlite_schema.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/storage.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/history/ui_events.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/interaction.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/md_agents.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/models.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/options.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/plugins.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/base.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/model_cache.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/model_registry.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/ollama.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/providers/openrouter.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/py.typed +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/renderer.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/schemas/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/schemas/agent.schema.json +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/backend.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/env.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/exec.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/file.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/masking.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/registry.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/secrets/sqlite.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/shell_tool_config.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/skill_discovery.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/AGENTS.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/IDENTITY.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/MEMORY.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/USER.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/personas/casual-technical.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/personas/marvin.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/templates/personas/minimal.md +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/agents.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/fs.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/history.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/http.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/interactive.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/notify.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/scratchpad.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/secrets.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/sessions.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/shell.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/shell_tools.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/skills.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tools/time.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/tsugite.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/base.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/chat.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/helpers.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/jsonl.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/plain.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_chat.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_commands.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_completer.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui/repl_handler.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/ui_context.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/usage/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/usage/store.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/user_agent.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/utils.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/__init__.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/context.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/models.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/session.py +0 -0
- {tsugite_cli-0.17.2 → tsugite_cli-0.18.0}/tsugite/workspace/templates.py +0 -0
|
@@ -489,12 +489,12 @@ Reproducing tests don't need to be elaborate. A 10-line test that flips red→gr
|
|
|
489
489
|
|
|
490
490
|
## Web UI
|
|
491
491
|
|
|
492
|
-
The daemon's web UI lives at `tsugite
|
|
492
|
+
The daemon's web UI lives at `plugins/tsugite-daemon/tsugite_daemon/web/`.
|
|
493
493
|
|
|
494
494
|
### Stack
|
|
495
495
|
|
|
496
496
|
- **Alpine.js 3** loaded from CDN. No build step. No React. Don't port this to a framework - the design handoff explicitly said match visuals, not internal structure.
|
|
497
|
-
- **Starlette** serves `index.html` plus `/static/{css,js,icons}` and the `/api/*` routes (`tsugite
|
|
497
|
+
- **Starlette** serves `index.html` plus `/static/{css,js,icons}` and the `/api/*` routes (`plugins/tsugite-daemon/tsugite_daemon/adapters/http/`, a per-domain package).
|
|
498
498
|
- CSS lives in three files. Read all three before adding rules: `theme.css` (Catppuccin tokens), `console.css` (Console redesign - the real stylesheet), `styles.css` (legacy modals/auth/forms; ~120 lines, pruned aggressively after the redesign).
|
|
499
499
|
- View modules under `js/views/`: `conversations.js` (orchestrator) + `conversation/{sessions,history,streaming,input,attachments,event_types}.js` mixins, plus `workspace.js`, `schedules.js`, `webhooks.js`, `usage.js`, `file-editor.js`. Shared helpers: `js/api.js` (REST + SSE), `js/utils.js` (markdown, formatters, toast).
|
|
500
500
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tsugite-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.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"
|
|
@@ -92,6 +92,7 @@ tsugite-sandbox = { workspace = true }
|
|
|
92
92
|
tsugite-pty = { workspace = true }
|
|
93
93
|
tsugite-daemon = { workspace = true }
|
|
94
94
|
tsugite-discord = { workspace = true }
|
|
95
|
+
tsugite-cc-driver = { workspace = true }
|
|
95
96
|
|
|
96
97
|
[tool.ruff]
|
|
97
98
|
target-version = "py311"
|
|
@@ -175,4 +176,5 @@ dev = [
|
|
|
175
176
|
"tsugite-pty",
|
|
176
177
|
"tsugite-daemon",
|
|
177
178
|
"tsugite-discord",
|
|
179
|
+
"tsugite-cc-driver",
|
|
178
180
|
]
|
|
@@ -98,6 +98,36 @@ def record_user_input(
|
|
|
98
98
|
storage.record("user_input", **data)
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def record_final_result(
|
|
102
|
+
storage: Session,
|
|
103
|
+
result: str,
|
|
104
|
+
result_data: Any = None,
|
|
105
|
+
turns: Optional[int] = None,
|
|
106
|
+
tokens: Optional[int] = None,
|
|
107
|
+
cost: Optional[float] = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Record the turn's final answer as a durable final_result event.
|
|
110
|
+
|
|
111
|
+
Interactive daemon turns may already have one for this turn, persisted by
|
|
112
|
+
the live SSE handler during FinalAnswerEvent dispatch (which runs before
|
|
113
|
+
the agent reaches its end-of-run recording). Skip in that case so the
|
|
114
|
+
conversation view doesn't render the answer twice.
|
|
115
|
+
"""
|
|
116
|
+
for event in reversed(list(storage.iter_events(types=("user_input", "final_result")))):
|
|
117
|
+
if event.type == "user_input":
|
|
118
|
+
break
|
|
119
|
+
if event.type == "final_result":
|
|
120
|
+
return
|
|
121
|
+
storage.record(
|
|
122
|
+
"final_result",
|
|
123
|
+
result=result,
|
|
124
|
+
result_data=result_data,
|
|
125
|
+
turns=turns,
|
|
126
|
+
tokens=tokens,
|
|
127
|
+
cost=cost,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
101
131
|
def record_session_end(
|
|
102
132
|
storage: Session,
|
|
103
133
|
status: str = "success",
|
|
@@ -681,7 +681,7 @@ async def _execute_agent_with_prompt(
|
|
|
681
681
|
model=model_string,
|
|
682
682
|
source="cli",
|
|
683
683
|
total_tokens=agent.total_tokens,
|
|
684
|
-
cost_usd=agent.
|
|
684
|
+
cost_usd=agent.reported_cost,
|
|
685
685
|
cache_creation_tokens=agent.cache_creation_tokens,
|
|
686
686
|
cache_read_tokens=agent.cache_read_tokens,
|
|
687
687
|
)
|
|
@@ -1046,13 +1046,15 @@ async def run_agent_async(
|
|
|
1046
1046
|
else:
|
|
1047
1047
|
logger.debug("No resumable provider session for %s", continue_conversation_id)
|
|
1048
1048
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1049
|
+
# Load serialized history even when a provider session is resumable:
|
|
1050
|
+
# session-owning providers need it as fallback material if the resume
|
|
1051
|
+
# replay is rejected (e.g. a poisoned Claude Code sidecar transcript
|
|
1052
|
+
# that 400s on every send). Providers ignore it while a resume is live.
|
|
1053
|
+
try:
|
|
1054
|
+
previous_messages = load_and_apply_history(continue_conversation_id)
|
|
1055
|
+
except ValueError:
|
|
1056
|
+
# New conversation (e.g., fresh workspace session) - start with empty history
|
|
1057
|
+
pass
|
|
1056
1058
|
|
|
1057
1059
|
# Parse agent configuration (with inheritance resolution)
|
|
1058
1060
|
try:
|
|
@@ -32,7 +32,7 @@ You are a specialized code search agent. Use `ast-grep` to find structural patte
|
|
|
32
32
|
- Helpful flags: `-C 2` (context), `--json` (machine-readable), `--no-ignore vcs` (ignore .gitignore rules).
|
|
33
33
|
|
|
34
34
|
### Quick Patterns
|
|
35
|
-
```python
|
|
35
|
+
```python-exec
|
|
36
36
|
# Function definition (Python)
|
|
37
37
|
run(command="ast-grep -p 'def $NAME($$$)' -l py src/")
|
|
38
38
|
|
|
@@ -78,7 +78,7 @@ instructions: |
|
|
|
78
78
|
Function results are NOT automatically visible to you in the next turn.
|
|
79
79
|
You MUST print() results if you want to see and use them later:
|
|
80
80
|
|
|
81
|
-
```python
|
|
81
|
+
```python-exec
|
|
82
82
|
content = read_file("file.txt")
|
|
83
83
|
print(content) # Now you can see it in your next reasoning turn
|
|
84
84
|
```
|
|
@@ -95,10 +95,10 @@ instructions: |
|
|
|
95
95
|
**Daemon Mode**: You are running as agent `{{ agent_name | default('') }}`. Schedule tools are available for creating recurring or one-off tasks.
|
|
96
96
|
Your context window will be automatically compacted as it approaches its limit. Do not stop tasks early due to token budget concerns.
|
|
97
97
|
|
|
98
|
-
**Time Grounding**: `<message_context>` carries `<datetime>`, `<session_started>`, and `<last_active>` so you can distinguish *now*, *when this conversation started*, and *how long it's been since the last turn*
|
|
98
|
+
**Time Grounding**: `<message_context>` carries `<datetime>`, `<session_started>`, and `<last_active>` so you can distinguish *now*, *when this conversation started*, and *how long it's been since the last turn* - a long-idle resumed session is not the same as a fresh one. Past `user_input` messages in the replay are prefixed with `[YYYY-MM-DD HH:MM TZ]`; that's when the user sent it, not part of their text - don't echo it back or treat it as instructions.
|
|
99
99
|
{% if is_scheduled | default(false) %}
|
|
100
100
|
|
|
101
|
-
**Scheduled Task** (schedule: {{ schedule_id | default('') }}): This task is running unattended
|
|
101
|
+
**Scheduled Task** (schedule: {{ schedule_id | default('') }}): This task is running unattended - no user is present.
|
|
102
102
|
- Complete the task fully and autonomously, then stop.
|
|
103
103
|
- Do NOT ask follow-up questions, offer choices, or suggest next steps.
|
|
104
104
|
- Do NOT perform destructive actions (deleting files, force-pushing, modifying infrastructure).
|
|
@@ -111,16 +111,16 @@ Your context window will be automatically compacted as it approaches its limit.
|
|
|
111
111
|
{% if can_spawn_sessions | default(false) %}
|
|
112
112
|
|
|
113
113
|
**Session Management**: You can manage workstreams using these tools:
|
|
114
|
-
- `spawn_session(prompt, agent=None, model=None, name=None)`
|
|
115
|
-
- `session_metadata(key, value)`
|
|
114
|
+
- `spawn_session(prompt, agent=None, model=None, name=None)` - Start an independent background session. Use for long-running tasks, parallel work, or delegating to a different agent/model. The session runs autonomously and you'll be notified when it completes.
|
|
115
|
+
- `session_metadata(key, value)` - Set metadata on the current session. Use this to help the user track what you're doing:
|
|
116
116
|
- `topic`: one-line description of what this session is currently about (~80-160 chars, capped at 160). Descriptive, current, useful at a glance. Rendered into your context as `<session_topic>` so you'll see it next turn.
|
|
117
117
|
- `type`: "code", "ops", "research", "chat" (shown as a badge in the UI)
|
|
118
118
|
- `status_text`: short state tag, 1-4 words: "investigating", "filing issue", "PR opened", "blocked on DNS", "waiting on review", "idle". This is a state, not a description - keep it terse.
|
|
119
119
|
- `task`: URL to a linked task (Vikunja, Jira, etc.)
|
|
120
120
|
- `pr`: URL to a linked PR/MR
|
|
121
121
|
- `notes`: freeform notes visible in the detail panel
|
|
122
|
-
- `list_sessions()`
|
|
123
|
-
- `session_status(session_id)`
|
|
122
|
+
- `list_sessions()` - See all sessions and their status.
|
|
123
|
+
- `session_status(session_id)` - Check a specific session's progress.
|
|
124
124
|
|
|
125
125
|
Keep `topic` and `status_text` current so the sidebar reflects what each session is actually doing:
|
|
126
126
|
- At session start, set `topic` (one-liner) and `status_text` (state).
|
|
@@ -139,17 +139,19 @@ You are managing a shared channel. When a user asks for something that would ben
|
|
|
139
139
|
|
|
140
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
141
|
|
|
142
|
-
- `spawn_job(prompt, acceptance_criteria=[...], max_attempts=3, notify_when="never")`
|
|
143
|
-
- `get_job(job_id)`
|
|
144
|
-
- `list_jobs(session_id=..., state=..., limit=10)`
|
|
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
|
+
- `cancel_job(job_id, reason=...)` - Cancel a live Job, or give up on a stuck/errored one.
|
|
146
|
+
- `respond_to_job(job_id, message)` - Answer or steer an executor Job's live worker. A Job in `awaiting_input` is blocked on the question in its `pending_question` field; answering resumes it. Answer yourself when you can; use `ask_user` only when the question genuinely needs the user. Not for agent-executor Jobs.
|
|
145
147
|
|
|
146
148
|
**When to use what:**
|
|
147
|
-
- **Inline**
|
|
148
|
-
- **`spawn_session`**
|
|
149
|
-
- **`spawn_job`**
|
|
149
|
+
- **Inline** - single edit, one-shot read, small calculation. Don't spawn anything.
|
|
150
|
+
- **`spawn_session`** - fire-and-forget background work; no judgment is applied to the result.
|
|
151
|
+
- **`spawn_job`** - when "done" has a checkable shape (tests pass, copy satisfies criteria, refactor preserves behavior). The verification loop is the value.
|
|
150
152
|
|
|
151
153
|
**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
|
|
154
|
+
```python-exec
|
|
153
155
|
spawn_job(
|
|
154
156
|
prompt="Refactor the agent loop to handle nested tool calls",
|
|
155
157
|
acceptance_criteria=[
|
|
@@ -160,11 +162,11 @@ spawn_job(
|
|
|
160
162
|
)
|
|
161
163
|
```
|
|
162
164
|
|
|
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
|
|
165
|
+
**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
166
|
|
|
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`
|
|
167
|
+
**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
168
|
|
|
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
|
|
169
|
+
**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
170
|
{% endif %}
|
|
169
171
|
{% if can_use_pty | default(false) %}
|
|
170
172
|
|
|
@@ -172,14 +174,14 @@ spawn_job(
|
|
|
172
174
|
|
|
173
175
|
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
176
|
|
|
175
|
-
- **`run()`**
|
|
176
|
-
- **`pty_create()`**
|
|
177
|
-
- **`tmux_*` skill**
|
|
177
|
+
- **`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.
|
|
178
|
+
- **`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.
|
|
179
|
+
- **`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
180
|
|
|
179
181
|
Tools: `pty_create`, `pty_send_keys`, `pty_capture`, `pty_kill`, `pty_list`.
|
|
180
182
|
|
|
181
183
|
Typical loop:
|
|
182
|
-
```python
|
|
184
|
+
```python-exec
|
|
183
185
|
term = pty_create("python3 -i")
|
|
184
186
|
pty_send_keys(term["terminal_id"], "print(2 + 2)")
|
|
185
187
|
print(pty_capture(term["terminal_id"], lines=5)) # see the prompt + result
|
|
@@ -260,13 +262,13 @@ You can delegate to these specialized agents using `spawn_agent(agent_path, prom
|
|
|
260
262
|
|
|
261
263
|
**Returning results:**
|
|
262
264
|
If the subagent fully completes the task, return its result immediately:
|
|
263
|
-
```python
|
|
265
|
+
```python-exec
|
|
264
266
|
result = spawn_agent("agents/code_review.md", "Review app.py for security issues")
|
|
265
267
|
return_value(result)
|
|
266
268
|
```
|
|
267
269
|
|
|
268
270
|
If you need to process the result further, print it so you can see it next turn:
|
|
269
|
-
```python
|
|
271
|
+
```python-exec
|
|
270
272
|
review = spawn_agent("agents/code_review.md", "Review app.py")
|
|
271
273
|
print(review)
|
|
272
274
|
```
|
|
@@ -313,9 +315,9 @@ These skills had errors or warnings during discovery/load. Errors mean the skill
|
|
|
313
315
|
|
|
314
316
|
You have access to a secure secrets system. Always use `get_secret(name)` when you need API keys, tokens, or credentials.
|
|
315
317
|
|
|
316
|
-
```python
|
|
318
|
+
```python-exec
|
|
317
319
|
token = get_secret("github-token")
|
|
318
|
-
# token works normally in code
|
|
320
|
+
# token works normally in code - the real value is used in the request
|
|
319
321
|
result = http_request("https://api.github.com/user", headers={"Authorization": f"Bearer {token}"})
|
|
320
322
|
print(result)
|
|
321
323
|
```
|
|
@@ -332,11 +334,11 @@ When writing content containing triple quotes (`"""`), backticks (` ``` `), or b
|
|
|
332
334
|
define the content in a `<content>` block outside your code, then reference it as a variable:
|
|
333
335
|
|
|
334
336
|
<content name="my_file">
|
|
335
|
-
Raw content here
|
|
337
|
+
Raw content here - no escaping needed.
|
|
336
338
|
Triple quotes """ and backticks ``` work fine.
|
|
337
339
|
</content>
|
|
338
340
|
|
|
339
|
-
```python
|
|
341
|
+
```python-exec
|
|
340
342
|
write_file("output.py", content=my_file)
|
|
341
343
|
edit_file("other.py", old_string=old_text, new_string=new_text)
|
|
342
344
|
```
|
|
@@ -43,7 +43,7 @@ Use the `run()` tool with these commands:
|
|
|
43
43
|
### Search for content in files
|
|
44
44
|
|
|
45
45
|
{% if has_rg %}
|
|
46
|
-
```python
|
|
46
|
+
```python-exec
|
|
47
47
|
# Basic search
|
|
48
48
|
run(command="rg 'pattern' path/")
|
|
49
49
|
|
|
@@ -60,7 +60,7 @@ run(command="rg -l 'pattern' path/")
|
|
|
60
60
|
run(command="rg 'def\\s+\\w+' src/")
|
|
61
61
|
```
|
|
62
62
|
{% elif has_ag %}
|
|
63
|
-
```python
|
|
63
|
+
```python-exec
|
|
64
64
|
# Basic search
|
|
65
65
|
run(command="ag 'pattern' path/")
|
|
66
66
|
|
|
@@ -71,7 +71,7 @@ run(command="ag -C 3 'pattern' path/")
|
|
|
71
71
|
run(command="ag -i 'pattern' path/")
|
|
72
72
|
```
|
|
73
73
|
{% else %}
|
|
74
|
-
```python
|
|
74
|
+
```python-exec
|
|
75
75
|
# Basic search (excludes .venv, node_modules, .git, __pycache__, etc.)
|
|
76
76
|
run(command="grep -r --exclude-dir={.git,.venv,venv,node_modules,__pycache__,.pytest_cache,.mypy_cache,build,dist,.tox} --exclude='*.pyc' --exclude='*.pyo' --exclude='*.so' --exclude='*.o' 'pattern' path/")
|
|
77
77
|
|
|
@@ -86,7 +86,7 @@ run(command="grep -ri --exclude-dir={.git,.venv,venv,node_modules,__pycache__} -
|
|
|
86
86
|
### Find files by name
|
|
87
87
|
|
|
88
88
|
{% if has_fd %}
|
|
89
|
-
```python
|
|
89
|
+
```python-exec
|
|
90
90
|
# Find by pattern
|
|
91
91
|
run(command="fd 'pattern' path/")
|
|
92
92
|
|
|
@@ -100,7 +100,7 @@ run(command="fd --type f 'pattern' path/")
|
|
|
100
100
|
run(command="fd -i 'pattern' path/")
|
|
101
101
|
```
|
|
102
102
|
{% else %}
|
|
103
|
-
```python
|
|
103
|
+
```python-exec
|
|
104
104
|
# Find by name (excludes .venv, node_modules, .git, etc.)
|
|
105
105
|
run(command="find path/ -type f -name 'pattern' -not -path '*/.git/*' -not -path '*/.venv/*' -not -path '*/venv/*' -not -path '*/node_modules/*' -not -path '*/__pycache__/*' -not -path '*/build/*' -not -path '*/dist/*'")
|
|
106
106
|
|
|
@@ -115,7 +115,7 @@ run(command="find path/ -type f -iname 'pattern' -not -path '*/.git/*' -not -pat
|
|
|
115
115
|
## Common Search Patterns
|
|
116
116
|
|
|
117
117
|
### Find TODOs/FIXMEs:
|
|
118
|
-
```python
|
|
118
|
+
```python-exec
|
|
119
119
|
{% if has_rg %}
|
|
120
120
|
run(command="rg '(TODO|FIXME|XXX|HACK)' .")
|
|
121
121
|
{% elif has_ag %}
|
|
@@ -126,7 +126,7 @@ run(command="grep -rE --exclude-dir={.git,.venv,venv,node_modules,__pycache__} -
|
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
### Find function/class definitions:
|
|
129
|
-
```python
|
|
129
|
+
```python-exec
|
|
130
130
|
{% if has_rg %}
|
|
131
131
|
run(command="rg '^(def|class)\\s+\\w+' src/")
|
|
132
132
|
{% elif has_ag %}
|
|
@@ -137,7 +137,7 @@ run(command="grep -rE --exclude-dir={.git,.venv,venv,__pycache__} --exclude='*.p
|
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
### Find imports:
|
|
140
|
-
```python
|
|
140
|
+
```python-exec
|
|
141
141
|
{% if has_rg %}
|
|
142
142
|
run(command="rg '^(import|from)' .")
|
|
143
143
|
{% elif has_ag %}
|
|
@@ -148,7 +148,7 @@ run(command="grep -r --exclude-dir={.git,.venv,venv,__pycache__} --exclude='*.py
|
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
### Find potential secrets (IMPORTANT - use with care):
|
|
151
|
-
```python
|
|
151
|
+
```python-exec
|
|
152
152
|
{% if has_rg %}
|
|
153
153
|
run(command="rg -i '(api[_-]?key|password|secret|token)\\s*=' .")
|
|
154
154
|
{% elif has_ag %}
|
|
@@ -159,7 +159,7 @@ run(command="grep -riE --exclude-dir={.git,.venv,venv,node_modules,__pycache__}
|
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
### Find files by extension:
|
|
162
|
-
```python
|
|
162
|
+
```python-exec
|
|
163
163
|
{% if has_fd %}
|
|
164
164
|
run(command="fd -e py -e js -e ts src/")
|
|
165
165
|
{% else %}
|
|
@@ -170,7 +170,7 @@ run(command="find src/ -type f \\( -name '*.py' -o -name '*.js' -o -name '*.ts'
|
|
|
170
170
|
## Response Guidelines
|
|
171
171
|
|
|
172
172
|
Execute searches and return results:
|
|
173
|
-
```python
|
|
173
|
+
```python-exec
|
|
174
174
|
{% if has_rg %}
|
|
175
175
|
# Search for pattern
|
|
176
176
|
results = run(command="rg 'function_name' src/")
|
|
@@ -44,9 +44,9 @@ inspecting the workspace yourself.
|
|
|
44
44
|
|
|
45
45
|
## Tool use
|
|
46
46
|
|
|
47
|
-
To inspect, reply with a single ```python code block calling a tool:
|
|
47
|
+
To inspect, reply with a single ```python-exec code block calling a tool:
|
|
48
48
|
|
|
49
|
-
```python
|
|
49
|
+
```python-exec
|
|
50
50
|
read_file(path="docs/page.md")
|
|
51
51
|
```
|
|
52
52
|
|