symphony-code 0.0.1__tar.gz → 0.0.3__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.
- {symphony_code-0.0.1 → symphony_code-0.0.3}/PKG-INFO +14 -7
- {symphony_code-0.0.1 → symphony_code-0.0.3}/README.md +12 -6
- {symphony_code-0.0.1 → symphony_code-0.0.3}/pyproject.toml +5 -4
- {symphony_code-0.0.1 → symphony_code-0.0.3}/pyproject.toml.orig +6 -4
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/approvals.py +22 -2
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/learning/store.py +10 -2
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/active.py +37 -1
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/jsonl.py +16 -2
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/agent.py +7 -2
- symphony_code-0.0.3/src/coding_agent/cli.py +49 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/config/__init__.py +2 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/config/config.py +13 -0
- symphony_code-0.0.3/src/coding_agent/protocols/stdio/README.md +109 -0
- symphony_code-0.0.3/src/coding_agent/protocols/stdio/requests.py +112 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/stdio/server.py +45 -12
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/stdio/transport.py +24 -9
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/run/detach.py +1 -1
- symphony_code-0.0.3/src/coding_agent/tls.py +25 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/ask_user.py +16 -3
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/bash.py +83 -32
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/patch.py +34 -2
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/read_file.py +8 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/search.py +36 -10
- symphony_code-0.0.3/src/coding_agent/tools/summary.py +82 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/__main__.py +9 -27
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/app.py +1 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/runtime/events.py +18 -4
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/runtime/sink.py +7 -1
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/runtime/subagent.py +4 -1
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/ask.py +3 -0
- symphony_code-0.0.3/src/coding_agent/tui/screens/update.py +72 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/calls.py +143 -31
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/labels.py +10 -4
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/transcript/surface.py +6 -3
- symphony_code-0.0.3/src/coding_agent/updates.py +269 -0
- symphony_code-0.0.3/src/coding_agent/web/__init__.py +1 -0
- symphony_code-0.0.3/src/coding_agent/web/bundle.py +272 -0
- symphony_code-0.0.3/src/coding_agent/web/server.py +363 -0
- symphony_code-0.0.3/src/coding_agent/web/sessions.py +306 -0
- symphony_code-0.0.3/src/coding_agent/web/worker.py +38 -0
- symphony_code-0.0.3/src/coding_agent/widget_fields.py +134 -0
- symphony_code-0.0.1/src/coding_agent/protocols/stdio/README.md +0 -36
- {symphony_code-0.0.1 → symphony_code-0.0.3}/LICENSE +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/_lazy.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/compaction/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/compaction/addon.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/compaction/compactor.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/compaction/prompts.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/compaction/transcript.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/addon.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/evaluators.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/handlers.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/policy.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/protocol.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/evaluation/state.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/langfuse/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/langfuse/addon.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/langfuse/install.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/langfuse/serialize.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/learning/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/learning/addon.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/learning/loop.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/learning/prompts.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/learning/sanitize.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/manager/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/manager/bridge.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/manager/discovery.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/manager/report.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/manager/runtime.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/manager/tool_changes.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/artifacts.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/collection.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/compatibility.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/images.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/persistence/presentation.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/plugins/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/plugins/install.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/plugins/manager.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/plugins/models.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/addon.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/args.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/addon-authoring/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/context-budget/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/orient-first/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/security-boundary/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/skill-discipline/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/surgical-edit/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/systematic-debug/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/bundled_skills/verify-before-done/SKILL.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/frontmatter.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/models.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/registry.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/skills/tool.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/addon.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/credentials.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/playback.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/speak.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/stream.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/addons/voice/summary.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/bench/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/bench/cli.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/bench/patch.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/commands/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/commands/catalog.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/commands/jev_argument.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/config/credentials.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/config/personalities.json +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/config/personalities.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/config/prompts.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/acp/README.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/acp/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/acp/mapping.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/acp/server.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/acp/transport.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/stdio/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/stdio/catalog.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/protocols/stdio/commands.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/py.typed +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/resources/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/resources/usage.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/run/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/run/cli.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/run/interrupted.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/run/log_sink.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/base.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/bash_jobs.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/generate_image.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/memory.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/web_search.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tools/write_file.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/EFFICIENCY_PLAN.md +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/chrome/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/chrome/footer.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/chrome/topbar.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/commands/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/commands/catalog.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/commands/manager.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/composer/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/composer/input.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/composer/slash_menu.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/composer/surface.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/composer/voice.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/composer/voice_mode.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/runtime/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/runtime/state.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/runtime/turn.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/context.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/dashboard.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/diff.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/extensions.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/file_selector.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/history.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/langfuse.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/learning.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/modal.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/onboard.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/screens/resume.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/theme/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/theme/load.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/theme/style.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/theme/theme.toml +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/activity.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/code_view.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/images.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/modal.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/partial_args.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/tools/snapshots.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/transcript/__init__.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/transcript/messages.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/transcript/process.py +0 -0
- {symphony_code-0.0.1 → symphony_code-0.0.3}/src/coding_agent/tui/transcript/thinking_markdown.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: symphony-code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Symphony coding agent: workspace tools, JSONL sessions, and a Textual TUI on top of symphony-harness (import as coding_agent; run as `symphony`)
|
|
5
5
|
Author: Sarthak Chauhan
|
|
6
6
|
Author-email: Sarthak Chauhan <sarthakchauhann@gmail.com>
|
|
@@ -10,6 +10,7 @@ Requires-Dist: symphony-core
|
|
|
10
10
|
Requires-Dist: symphony-harness
|
|
11
11
|
Requires-Dist: pillow>=12.3.0
|
|
12
12
|
Requires-Dist: pydantic>=2.0.0
|
|
13
|
+
Requires-Dist: certifi>=2024.2.2
|
|
13
14
|
Requires-Dist: pyyaml>=6.0.3
|
|
14
15
|
Requires-Dist: textual>=8.2.8
|
|
15
16
|
Requires-Dist: textual-image>=0.12.0
|
|
@@ -27,15 +28,15 @@ Description-Content-Type: text/markdown
|
|
|
27
28
|
|
|
28
29
|
Run an AI coding agent in your terminal, or import the coding-agent API from the Symphony workspace. Package import name: `coding_agent`.
|
|
29
30
|
|
|
30
|
-
**Availability:**
|
|
31
|
+
**Availability:** version 0.0.1 is on PyPI.
|
|
31
32
|
|
|
32
33
|
```sh
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
uv sync
|
|
36
|
-
uv run --package symphony-code symphony --workspace /path/to/project
|
|
34
|
+
python -m pip install symphony-code
|
|
35
|
+
symphony --workspace /path/to/project
|
|
37
36
|
```
|
|
38
37
|
|
|
38
|
+
To run the current `main` branch instead of the published release, clone the repository, run `uv sync`, then `uv run --package symphony-code symphony --workspace /path/to/project`.
|
|
39
|
+
|
|
39
40
|
`symphony`, `symphony-code`, and `coding-agent-tui` are aliases for the same terminal application. Configure a provider on first launch, or set credentials beforehand. See the [installation guide](../docs/getting-started/installation.md) and [quickstart](../docs/getting-started/quickstart.md).
|
|
40
41
|
|
|
41
42
|
## Use the library
|
|
@@ -63,4 +64,10 @@ print(result.output_text)
|
|
|
63
64
|
- [Sessions and resume](../docs/user-guide/sessions.md)
|
|
64
65
|
- [Package API guide](../docs/packages/symphony-code.md)
|
|
65
66
|
|
|
66
|
-
**Safety:** tools run with the current user's permissions; approval prompts do not sandbox commands or restrict paths. Read the [security policy](../SECURITY.md) before use.
|
|
67
|
+
**Safety:** tools run with the current user's permissions; approval prompts do not sandbox commands or restrict paths. Read the [security policy](../SECURITY.md) before use.
|
|
68
|
+
|
|
69
|
+
### Browser interface
|
|
70
|
+
|
|
71
|
+
Run `symphony web --workspace /path/to/project` to launch Symphony Web. Installed
|
|
72
|
+
wheels include the React frontend; Node.js is not needed by end users. See the
|
|
73
|
+
[Web guide](../docs/developer-guide/web.md) for development and security details.
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Run an AI coding agent in your terminal, or import the coding-agent API from the Symphony workspace. Package import name: `coding_agent`.
|
|
4
4
|
|
|
5
|
-
**Availability:**
|
|
5
|
+
**Availability:** version 0.0.1 is on PyPI.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
uv sync
|
|
11
|
-
uv run --package symphony-code symphony --workspace /path/to/project
|
|
8
|
+
python -m pip install symphony-code
|
|
9
|
+
symphony --workspace /path/to/project
|
|
12
10
|
```
|
|
13
11
|
|
|
12
|
+
To run the current `main` branch instead of the published release, clone the repository, run `uv sync`, then `uv run --package symphony-code symphony --workspace /path/to/project`.
|
|
13
|
+
|
|
14
14
|
`symphony`, `symphony-code`, and `coding-agent-tui` are aliases for the same terminal application. Configure a provider on first launch, or set credentials beforehand. See the [installation guide](../docs/getting-started/installation.md) and [quickstart](../docs/getting-started/quickstart.md).
|
|
15
15
|
|
|
16
16
|
## Use the library
|
|
@@ -38,4 +38,10 @@ print(result.output_text)
|
|
|
38
38
|
- [Sessions and resume](../docs/user-guide/sessions.md)
|
|
39
39
|
- [Package API guide](../docs/packages/symphony-code.md)
|
|
40
40
|
|
|
41
|
-
**Safety:** tools run with the current user's permissions; approval prompts do not sandbox commands or restrict paths. Read the [security policy](../SECURITY.md) before use.
|
|
41
|
+
**Safety:** tools run with the current user's permissions; approval prompts do not sandbox commands or restrict paths. Read the [security policy](../SECURITY.md) before use.
|
|
42
|
+
|
|
43
|
+
### Browser interface
|
|
44
|
+
|
|
45
|
+
Run `symphony web --workspace /path/to/project` to launch Symphony Web. Installed
|
|
46
|
+
wheels include the React frontend; Node.js is not needed by end users. See the
|
|
47
|
+
[Web guide](../docs/developer-guide/web.md) for development and security details.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "symphony-code"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3"
|
|
4
4
|
description = "Symphony coding agent: workspace tools, JSONL sessions, and a Textual TUI on top of symphony-harness (import as coding_agent; run as `symphony`)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -11,6 +11,7 @@ dependencies = [
|
|
|
11
11
|
"symphony-harness",
|
|
12
12
|
"pillow>=12.3.0",
|
|
13
13
|
"pydantic>=2.0.0",
|
|
14
|
+
"certifi>=2024.2.2",
|
|
14
15
|
"pyyaml>=6.0.3",
|
|
15
16
|
"textual>=8.2.8",
|
|
16
17
|
"textual-image>=0.12.0",
|
|
@@ -31,9 +32,9 @@ Documentation = "https://github.com/Sarthakischauhan/symphony/blob/main/docs/pac
|
|
|
31
32
|
Source = "https://github.com/Sarthakischauhan/symphony/tree/main/coding_agent"
|
|
32
33
|
|
|
33
34
|
[project.scripts]
|
|
34
|
-
symphony = "coding_agent.
|
|
35
|
-
symphony-code = "coding_agent.
|
|
36
|
-
coding-agent-tui = "coding_agent.
|
|
35
|
+
symphony = "coding_agent.cli:main"
|
|
36
|
+
symphony-code = "coding_agent.cli:main"
|
|
37
|
+
coding-agent-tui = "coding_agent.cli:main"
|
|
37
38
|
|
|
38
39
|
[tool.uv.sources.symphony-core]
|
|
39
40
|
workspace = true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "symphony-code"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3"
|
|
4
4
|
description = "Symphony coding agent: workspace tools, JSONL sessions, and a Textual TUI on top of symphony-harness (import as coding_agent; run as `symphony`)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -14,6 +14,8 @@ dependencies = [
|
|
|
14
14
|
"symphony-harness",
|
|
15
15
|
"pillow>=12.3.0",
|
|
16
16
|
"pydantic>=2.0.0",
|
|
17
|
+
# coding_agent.tls: stdlib HTTPS on uv-managed Pythons without a CA store.
|
|
18
|
+
"certifi>=2024.2.2",
|
|
17
19
|
"pyyaml>=6.0.3",
|
|
18
20
|
"textual>=8.2.8",
|
|
19
21
|
"textual-image>=0.12.0",
|
|
@@ -31,9 +33,9 @@ Source = "https://github.com/Sarthakischauhan/symphony/tree/main/coding_agent"
|
|
|
31
33
|
[project.scripts]
|
|
32
34
|
# `symphony` is the primary command; the other two are aliases kept for
|
|
33
35
|
# compatibility and all resolve to the same entry point.
|
|
34
|
-
symphony = "coding_agent.
|
|
35
|
-
symphony-code = "coding_agent.
|
|
36
|
-
coding-agent-tui = "coding_agent.
|
|
36
|
+
symphony = "coding_agent.cli:main"
|
|
37
|
+
symphony-code = "coding_agent.cli:main"
|
|
38
|
+
coding-agent-tui = "coding_agent.cli:main"
|
|
37
39
|
|
|
38
40
|
[tool.uv.sources]
|
|
39
41
|
symphony-core = { workspace = true }
|
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
from typing import Any, Dict, Optional, Sequence, Union
|
|
17
17
|
|
|
18
18
|
from core_harness import Addon, EventSink
|
|
19
|
+
from core_harness.models import ToolCall
|
|
19
20
|
|
|
20
21
|
from coding_agent.config import ApprovalConfig, ensure_spawn_settings
|
|
21
22
|
|
|
@@ -52,6 +53,22 @@ def matching_rule(
|
|
|
52
53
|
return next((p for p in patterns if any(fnmatchcase(s, p) for s in subjects)), None)
|
|
53
54
|
|
|
54
55
|
|
|
56
|
+
def approval_metadata(tool_name: str, arguments: Dict[str, Any], question: str, call_id: str) -> Dict[str, Any]:
|
|
57
|
+
"""What an approval card shows: the subject, the question and the size of the write."""
|
|
58
|
+
field = RULE_SUBJECTS.get(tool_name)
|
|
59
|
+
target = str(arguments.get(field) or "").strip() if field else ""
|
|
60
|
+
written = arguments.get("content") if tool_name == "write_file" else (
|
|
61
|
+
arguments.get("new_str") if tool_name == "patch" else None
|
|
62
|
+
)
|
|
63
|
+
return {
|
|
64
|
+
"tool_name": tool_name,
|
|
65
|
+
"tool_call_id": call_id,
|
|
66
|
+
"target": target,
|
|
67
|
+
"detail": question,
|
|
68
|
+
"bytes": len(written.encode("utf-8")) if isinstance(written, str) else None,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
55
72
|
class ApprovalPolicy:
|
|
56
73
|
"""Path-aware rules. Callers pass the current ``ApprovalConfig``."""
|
|
57
74
|
|
|
@@ -160,7 +177,7 @@ class ApprovalAddon(Addon):
|
|
|
160
177
|
arguments: Dict[str, Any],
|
|
161
178
|
sink: Optional[EventSink] = None,
|
|
162
179
|
emit: Any = None,
|
|
163
|
-
tool_call:
|
|
180
|
+
tool_call: Optional[ToolCall] = None,
|
|
164
181
|
**_: Any,
|
|
165
182
|
) -> Optional[str]:
|
|
166
183
|
approvals = self.approvals
|
|
@@ -187,7 +204,9 @@ class ApprovalAddon(Addon):
|
|
|
187
204
|
choices=APPROVAL_CHOICES,
|
|
188
205
|
default=ALLOW_ONCE,
|
|
189
206
|
kind="approval",
|
|
190
|
-
metadata=
|
|
207
|
+
metadata=approval_metadata(tool_name, arguments, prompt, tool_call.id if tool_call is not None else ""),
|
|
208
|
+
# The harness emit journals the question, so history keeps target, detail and bytes.
|
|
209
|
+
emit=emit,
|
|
191
210
|
)
|
|
192
211
|
allowed, promote = self.policy.interpret(approvals, answer)
|
|
193
212
|
if promote:
|
|
@@ -219,6 +238,7 @@ __all__ = [
|
|
|
219
238
|
"AUTO_DECISION",
|
|
220
239
|
"ApprovalAddon",
|
|
221
240
|
"ApprovalPolicy",
|
|
241
|
+
"approval_metadata",
|
|
222
242
|
"DENY",
|
|
223
243
|
"matching_rule",
|
|
224
244
|
]
|
|
@@ -82,7 +82,11 @@ class LearningStore:
|
|
|
82
82
|
else:
|
|
83
83
|
self.memory_dir = safe_path(self.workspace, ".symphony/memories")
|
|
84
84
|
# Do not resolve away symlinks before validating paths.
|
|
85
|
-
|
|
85
|
+
override = os.environ.get("SYMPHONY_HOME", "").strip()
|
|
86
|
+
# Keep the unresolved path so the ancestor symlink checks below still
|
|
87
|
+
# protect memory roots, including a configured Symphony home.
|
|
88
|
+
global_home = Path(override).expanduser().absolute() if override else Path.home() / ".symphony"
|
|
89
|
+
self.global_root = Path(global_root).absolute() if global_root is not None else safe_path(global_home, "memories/global")
|
|
86
90
|
self.roots = {"workspace": self.memory_dir, "global": self.global_root}
|
|
87
91
|
self.path = safe_path(self.memory_dir, "lessons.jsonl")
|
|
88
92
|
# Markdown fact files. These are the session (and global) record.
|
|
@@ -93,8 +97,12 @@ class LearningStore:
|
|
|
93
97
|
self._lock_depth = 0
|
|
94
98
|
for root in self.roots.values():
|
|
95
99
|
# Validate every ancestor, including a supplied global root.
|
|
100
|
+
# macOS system directories such as /var may themselves be symlinks;
|
|
101
|
+
# those are outside the configured home and are not attacker-created
|
|
102
|
+
# memory roots. Reject the root and every ancestor at or below home.
|
|
103
|
+
home = Path.home().absolute()
|
|
96
104
|
for parent in (root, *root.parents):
|
|
97
|
-
if parent.is_symlink():
|
|
105
|
+
if parent.is_symlink() and (parent == root or parent == home or home in parent.parents):
|
|
98
106
|
raise ValueError("unsafe memory root")
|
|
99
107
|
for relative in ("topics", "observations/_inbox", "observations/archive", "jobs"):
|
|
100
108
|
safe_path(root, relative).mkdir(parents=True, exist_ok=True)
|
|
@@ -7,6 +7,7 @@ removes it on a clean exit. A file whose process is gone is stale and ignored.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import hashlib
|
|
10
11
|
import json
|
|
11
12
|
import os
|
|
12
13
|
from dataclasses import dataclass
|
|
@@ -16,7 +17,9 @@ from typing import Any
|
|
|
16
17
|
|
|
17
18
|
def active_dir() -> Path:
|
|
18
19
|
"""Directory that holds one registry file per live Symphony process."""
|
|
19
|
-
|
|
20
|
+
from coding_agent.config import symphony_dir
|
|
21
|
+
|
|
22
|
+
target = symphony_dir() / "active"
|
|
20
23
|
target.mkdir(parents=True, exist_ok=True)
|
|
21
24
|
return target
|
|
22
25
|
|
|
@@ -47,6 +50,32 @@ class ActiveSession:
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
# Advisory lifetime locks coordinate all local hosts, including the TUI. Registry
|
|
54
|
+
# files alone have a check-then-write race. The kernel releases locks on crashes.
|
|
55
|
+
_owned_lock = None
|
|
56
|
+
_owned_session = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _claim(session_id: str) -> None:
|
|
60
|
+
global _owned_lock, _owned_session
|
|
61
|
+
import fcntl
|
|
62
|
+
|
|
63
|
+
if _owned_session == session_id:
|
|
64
|
+
return
|
|
65
|
+
digest = hashlib.sha256(session_id.encode()).hexdigest()
|
|
66
|
+
handle = (active_dir() / f"{digest}.lock").open("a")
|
|
67
|
+
try:
|
|
68
|
+
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
69
|
+
if any(item.session_id == session_id and item.pid != os.getpid() for item in list_active()):
|
|
70
|
+
raise RuntimeError("Session is owned by another live Symphony process")
|
|
71
|
+
except (OSError, RuntimeError) as exc:
|
|
72
|
+
handle.close()
|
|
73
|
+
raise RuntimeError("Session is owned by another live Symphony process") from exc
|
|
74
|
+
if _owned_lock is not None:
|
|
75
|
+
_owned_lock.close()
|
|
76
|
+
_owned_lock, _owned_session = handle, session_id
|
|
77
|
+
|
|
78
|
+
|
|
50
79
|
def register_active(
|
|
51
80
|
session_id: str,
|
|
52
81
|
*,
|
|
@@ -57,6 +86,8 @@ def register_active(
|
|
|
57
86
|
pid: int | None = None,
|
|
58
87
|
) -> ActiveSession:
|
|
59
88
|
"""Record that this process owns ``session_id``. Replaces any prior record."""
|
|
89
|
+
if pid is None or pid == os.getpid():
|
|
90
|
+
_claim(session_id)
|
|
60
91
|
record = ActiveSession(
|
|
61
92
|
session_id=session_id,
|
|
62
93
|
pid=os.getpid() if pid is None else pid,
|
|
@@ -74,6 +105,11 @@ def register_active(
|
|
|
74
105
|
|
|
75
106
|
def release_active(pid: int | None = None) -> None:
|
|
76
107
|
"""Drop this process's registry file. Missing files are fine."""
|
|
108
|
+
global _owned_lock, _owned_session
|
|
109
|
+
if pid is None or pid == os.getpid():
|
|
110
|
+
if _owned_lock is not None:
|
|
111
|
+
_owned_lock.close()
|
|
112
|
+
_owned_lock = _owned_session = None
|
|
77
113
|
path = _path(os.getpid() if pid is None else pid)
|
|
78
114
|
try:
|
|
79
115
|
path.unlink()
|
|
@@ -19,7 +19,7 @@ from contextlib import contextmanager
|
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
from datetime import datetime, timezone
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional, Sequence, Union
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping, Optional, Sequence, Union
|
|
23
23
|
|
|
24
24
|
from coding_agent.addons.persistence.artifacts import (
|
|
25
25
|
append_bytes,
|
|
@@ -42,6 +42,10 @@ from coding_agent.addons.persistence.collection import (
|
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
_SESSION_ID = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from coding_agent.widget_fields import EventFields
|
|
48
|
+
|
|
45
49
|
_SKIP_EVENTS = frozenset({"text_delta", "reasoning_delta", "tool_call_delta"})
|
|
46
50
|
_MESSAGE_FIELDS = ("role", "content", "tool_calls", "tool_call_id", "tool_call_metadata")
|
|
47
51
|
_MESSAGE_TYPES = frozenset({"system", "user", "assistant", "tool_result", "message"})
|
|
@@ -90,7 +94,9 @@ def sessions_dir(
|
|
|
90
94
|
first use. Migration is file-by-file and only removes the legacy directory
|
|
91
95
|
after every move has succeeded, so an interrupted migration remains resumable.
|
|
92
96
|
"""
|
|
93
|
-
|
|
97
|
+
from coding_agent.config import symphony_dir
|
|
98
|
+
|
|
99
|
+
root = symphony_dir()
|
|
94
100
|
if client == "tui":
|
|
95
101
|
target = root / "tui" / "sessions"
|
|
96
102
|
shared = root / "sessions"
|
|
@@ -323,6 +329,9 @@ class JsonlPersistence:
|
|
|
323
329
|
# Product fields (goal, todo, live background jobs) merged into every
|
|
324
330
|
# checkpoint so an interrupted run can be continued later.
|
|
325
331
|
self.checkpoint_metadata: Callable[[], Dict[str, Any]] = dict
|
|
332
|
+
# Protocol-3 widget fields journaled with each event, so reloads and
|
|
333
|
+
# session/history keep them. ``build_agent`` sets it.
|
|
334
|
+
self.fields: EventFields | None = None
|
|
326
335
|
|
|
327
336
|
def session_dir(self, session_id: str) -> Path:
|
|
328
337
|
"""Return a validated session artifact directory (without creating it)."""
|
|
@@ -858,6 +867,11 @@ class JsonlPersistence:
|
|
|
858
867
|
async def append_event(self, *, event_type: str, payload: dict[str, Any]) -> None:
|
|
859
868
|
if event_type in _SKIP_EVENTS:
|
|
860
869
|
return
|
|
870
|
+
fields = self.fields
|
|
871
|
+
for name, body in fields.apply(event_type, payload) if fields is not None else [(event_type, payload)]:
|
|
872
|
+
self._journal(name, body)
|
|
873
|
+
|
|
874
|
+
def _journal(self, event_type: str, payload: dict[str, Any]) -> None:
|
|
861
875
|
session_id = str(payload.get("session_id") or "")
|
|
862
876
|
if not session_id:
|
|
863
877
|
return
|
|
@@ -53,6 +53,7 @@ from coding_agent.config.prompts import SYSTEM_PROMPT
|
|
|
53
53
|
from coding_agent.addons.skills import SkillRegistry, bundled_skills_root
|
|
54
54
|
from coding_agent.addons.manager import UserAddonRuntime, load_user_addons
|
|
55
55
|
from core_harness.extensions import EMPTY_SNAPSHOT
|
|
56
|
+
from coding_agent.widget_fields import EventFields
|
|
56
57
|
from coding_agent.tools import BashJobs, build_tools
|
|
57
58
|
|
|
58
59
|
|
|
@@ -510,6 +511,7 @@ def build_agent(
|
|
|
510
511
|
config=loaded,
|
|
511
512
|
overrides=overrides or None,
|
|
512
513
|
)
|
|
514
|
+
store = JsonlPersistence(sessions_dir(workspace, client=client))
|
|
513
515
|
agent = CodingAgent(
|
|
514
516
|
registry=registry,
|
|
515
517
|
model_id=default_model_id(registry, model_id or resolved.last_model),
|
|
@@ -517,7 +519,10 @@ def build_agent(
|
|
|
517
519
|
sink=sink,
|
|
518
520
|
session_id=session_id,
|
|
519
521
|
config=resolved,
|
|
520
|
-
persistence=
|
|
522
|
+
persistence=store,
|
|
521
523
|
)
|
|
522
|
-
|
|
524
|
+
store.client = client
|
|
525
|
+
# Journal the same widget fields the hosts add (protocol 3).
|
|
526
|
+
store.fields = EventFields()
|
|
527
|
+
store.fields.bind(agent)
|
|
523
528
|
return agent
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""The ``symphony`` command: route subcommands, else open the TUI.
|
|
2
|
+
|
|
3
|
+
Each subcommand is imported only when chosen, so ``web``, ``stdio``, ``acp``,
|
|
4
|
+
``bench`` and ``run`` never load Textual or ``coding_agent.tui``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
command = sys.argv[1] if len(sys.argv) > 1 else None
|
|
14
|
+
if command in {"--version", "-V"}:
|
|
15
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
value = version("symphony-code")
|
|
19
|
+
except PackageNotFoundError:
|
|
20
|
+
value = "unknown"
|
|
21
|
+
print(f"symphony {value}")
|
|
22
|
+
return
|
|
23
|
+
if command == "web":
|
|
24
|
+
from coding_agent.web.server import main as web_main
|
|
25
|
+
|
|
26
|
+
raise SystemExit(web_main(sys.argv[2:]))
|
|
27
|
+
if command == "stdio":
|
|
28
|
+
from coding_agent.protocols.stdio.server import main as stdio_main
|
|
29
|
+
|
|
30
|
+
raise SystemExit(stdio_main(sys.argv[2:]))
|
|
31
|
+
if command == "acp":
|
|
32
|
+
from coding_agent.protocols.acp.server import main as acp_main
|
|
33
|
+
|
|
34
|
+
raise SystemExit(acp_main(sys.argv[2:]))
|
|
35
|
+
if command == "bench":
|
|
36
|
+
from coding_agent.bench.cli import main as bench_main
|
|
37
|
+
|
|
38
|
+
raise SystemExit(bench_main(sys.argv[2:]))
|
|
39
|
+
if command == "run":
|
|
40
|
+
from coding_agent.run.cli import main as run_main
|
|
41
|
+
|
|
42
|
+
raise SystemExit(run_main(sys.argv[2:]))
|
|
43
|
+
from coding_agent.tui.__main__ import main as tui_main
|
|
44
|
+
|
|
45
|
+
tui_main()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -16,6 +16,7 @@ from coding_agent.config.config import (
|
|
|
16
16
|
SkillsConfig,
|
|
17
17
|
SettingsSource,
|
|
18
18
|
ToolsConfig,
|
|
19
|
+
UpdatesConfig,
|
|
19
20
|
WebSearchConfig,
|
|
20
21
|
default_coding_agent_harness,
|
|
21
22
|
ensure_spawn_settings,
|
|
@@ -41,6 +42,7 @@ __all__ = [
|
|
|
41
42
|
"SkillsConfig",
|
|
42
43
|
"SettingsSource",
|
|
43
44
|
"ToolsConfig",
|
|
45
|
+
"UpdatesConfig",
|
|
44
46
|
"WebSearchConfig",
|
|
45
47
|
"default_coding_agent_harness",
|
|
46
48
|
"ensure_spawn_settings",
|
|
@@ -182,6 +182,17 @@ def default_coding_agent_harness() -> HarnessConfig:
|
|
|
182
182
|
)
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
class UpdatesConfig(BaseModel):
|
|
186
|
+
"""Pre-start comparison of the installed coding agent with its PyPI release."""
|
|
187
|
+
|
|
188
|
+
model_config = ConfigDict(extra="forbid")
|
|
189
|
+
|
|
190
|
+
enabled: bool = True
|
|
191
|
+
# False asks before replacing the installed package. True installs the
|
|
192
|
+
# newer published release without a prompt.
|
|
193
|
+
auto: bool = False
|
|
194
|
+
|
|
195
|
+
|
|
185
196
|
class CodingAgentConfig(BaseModel):
|
|
186
197
|
model_config = ConfigDict(extra="forbid")
|
|
187
198
|
|
|
@@ -194,6 +205,7 @@ class CodingAgentConfig(BaseModel):
|
|
|
194
205
|
plugins: PluginsConfig = Field(default_factory=PluginsConfig)
|
|
195
206
|
langfuse: LangfuseConfig = Field(default_factory=LangfuseConfig)
|
|
196
207
|
evaluation: EvaluationConfig = Field(default_factory=EvaluationConfig)
|
|
208
|
+
updates: UpdatesConfig = Field(default_factory=UpdatesConfig)
|
|
197
209
|
last_model: str | None = None
|
|
198
210
|
# Default "direct" inserts that catalog segment. null = stock prompt only.
|
|
199
211
|
personality: str | None = "direct"
|
|
@@ -368,6 +380,7 @@ __all__ = [
|
|
|
368
380
|
"SettingsSource",
|
|
369
381
|
"SkillsConfig",
|
|
370
382
|
"ToolsConfig",
|
|
383
|
+
"UpdatesConfig",
|
|
371
384
|
"WebSearchConfig",
|
|
372
385
|
"ensure_spawn_settings",
|
|
373
386
|
"load_coding_agent_config",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Coding agent host protocols
|
|
2
|
+
|
|
3
|
+
`stdio.py` is the versioned JSONL boundary for desktop and other external
|
|
4
|
+
hosts. The coding agent owns execution and persisted checkpoints. A host owns
|
|
5
|
+
its composer queue and submits a single prompt per active turn.
|
|
6
|
+
|
|
7
|
+
The `ready` frame carries `session_id`, `model`, `workspace`, `branch`,
|
|
8
|
+
`client`, `protocol_version` (currently `3`) and `capabilities`. Protocol 2
|
|
9
|
+
advertised `runs`, `resume`, `interrupt`, `input`, `steer`, `models`, `images`,
|
|
10
|
+
`subagents`, and `commands`; protocol 3 adds `sessions`, `model_switch` and
|
|
11
|
+
`widget_fields`. A host sends `run`, `answer`, `interrupt`, or `steer`
|
|
12
|
+
commands, or a request with a `request_id` (below), and reads `event`,
|
|
13
|
+
`input_requested`, `error`, `done`, and reply frames. `answer`, `interrupt`,
|
|
14
|
+
and `steer` are passed to `CoreHarness.submit` as `Answer`, `Interrupt`, and
|
|
15
|
+
`UserInput` operations. `{"type":"steer","prompt":"..."}` adds a user message
|
|
16
|
+
to the active run before its next model call and is an error when no run is
|
|
17
|
+
active.
|
|
18
|
+
|
|
19
|
+
A question arrives as the ordinary `question_asked` event, the same as in
|
|
20
|
+
every other host, followed by the protocol 2 `input_requested` frame with the
|
|
21
|
+
same `request_id`. Reply to either with one `answer` command.
|
|
22
|
+
Unknown control-plane events can be ignored without losing the terminal
|
|
23
|
+
`done` frame. A later version must change the version number when it changes
|
|
24
|
+
required frame semantics.
|
|
25
|
+
|
|
26
|
+
After `ready`, send `{"type":"model/list","request_id":"catalog-1"}` on stdin.
|
|
27
|
+
Symphony responds with a `models` frame carrying the same `request_id`. Each
|
|
28
|
+
row has a qualified
|
|
29
|
+
`id` (`provider:model`), display `label`, `provider`, `description`,
|
|
30
|
+
`context_limit` (tokens, or null), and `reasoning_levels`. The default ID is
|
|
31
|
+
also returned as `default`; hosts should keep the qualified ID when selecting
|
|
32
|
+
a model. The provider is the API route, so gateway models identify the gateway
|
|
33
|
+
even when its upstream model has another vendor.
|
|
34
|
+
|
|
35
|
+
## Requests
|
|
36
|
+
|
|
37
|
+
Each request carries a `request_id` and gets exactly one reply frame with the
|
|
38
|
+
same `request_id`, or an `error` frame with it.
|
|
39
|
+
|
|
40
|
+
| Request | Reply | Capability |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `{"type":"model/list"}` | `models`: `models` rows and `default` | `models` |
|
|
43
|
+
| `{"type":"command/list"}` | `commands`: the slash-command catalog | `commands` |
|
|
44
|
+
| `{"type":"session/list"}` | `sessions`: this client's parent sessions, newest first, each with `session_id`, `updated_at`, `message_count`, `first_message`, `client`, `active` | `sessions` |
|
|
45
|
+
| `{"type":"session/history","session_id":"..."}` | `session_history`: `session_id`, `messages` (the transcript; assistant `tool_calls` are `{id, type: "function", function: {name, arguments}}` with `arguments` a JSON string), `events` (`[event, payload]` journal pairs) and `children` | `sessions` |
|
|
46
|
+
| `{"type":"model","model":"provider:model"}` | `model_selected`: `model`, `context_limit`; an error during an active run | `model_switch` |
|
|
47
|
+
|
|
48
|
+
## Subagents
|
|
49
|
+
|
|
50
|
+
Subagents use the ordinary `event` envelope: the parent emits
|
|
51
|
+
`agent_spawned` with `child_id` and `tool_call_id`; child events carry that
|
|
52
|
+
`child_id` as `agent_id`; the parent emits `agent_completed` or `agent_failed`
|
|
53
|
+
with `child_id` when it settles. Hosts can route child traffic into a nested
|
|
54
|
+
view using the spawning tool call ID.
|
|
55
|
+
|
|
56
|
+
## Protocol 3: widget fields
|
|
57
|
+
|
|
58
|
+
`ready` reports `"protocol_version": 3` and the `widget_fields` capability. Every
|
|
59
|
+
protocol 2 key is unchanged; hosts that ignore unknown keys keep working.
|
|
60
|
+
The stdio server and the TUI add these fields through one shared
|
|
61
|
+
`coding_agent.widget_fields.EventFields`. `parent_call_id` is the
|
|
62
|
+
`tool_call_id` of the `spawn_agent` call that started the emitting subagent,
|
|
63
|
+
and `null` for top-level events.
|
|
64
|
+
|
|
65
|
+
| Event | Added fields |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `tool_execution_started` | `call_id`, `parent_call_id` (`started_at` was already present) |
|
|
68
|
+
| `tool_execution_output` | `call_id`, `stream` (`"stdout"` or `"stderr"`), `parent_call_id` (from the call's `tool_execution_started`) |
|
|
69
|
+
| `tool_execution_completed` | `call_id`, `exit_code`, `summary`, `search_results`, `parent_call_id` (`status`, `result` and `duration_ms` were already present) |
|
|
70
|
+
| `question_asked` (approval) | `target`, `detail`, `bytes` (`tool_call_id`, `choices`, `default` were already present) |
|
|
71
|
+
| `question_asked` (`ask_user`) | `tool_call_id`: the asking call |
|
|
72
|
+
| `input_requested` (approval) | top-level `target`, `detail`, `bytes` (also kept in `metadata`) |
|
|
73
|
+
| `agent_spawned` | `parent_call_id` (same value as `tool_call_id`) |
|
|
74
|
+
| `subagent_step` (new, derived) | `session_id` (the parent session), `agent_id`, `parent_id`, `parent_call_id`, `turn`, `started_at`; follows each subagent `turn_started` |
|
|
75
|
+
|
|
76
|
+
- `exit_code` is the bash process's exit status; it is `null` for other tools,
|
|
77
|
+
background jobs, and timeouts. A non-zero exit is `status: "error"` and the
|
|
78
|
+
result starts with `exit=N`, but consumers should read `exit_code`.
|
|
79
|
+
- `summary` is `{lines, matches, added, removed, start_line}`, each an
|
|
80
|
+
integer or `null`: `lines` for bash and read_file, `matches` for search,
|
|
81
|
+
`lines` and `added` for write_file, `added`, `removed` and `start_line` for
|
|
82
|
+
patch, and `start_line` for read_file too. Failed non-bash calls report all
|
|
83
|
+
`null`.
|
|
84
|
+
- `start_line` is recorded per call by the tool, never parsed from `result`.
|
|
85
|
+
For patch it is the 1-based line, in the file as it was before the edit,
|
|
86
|
+
where `old_str` first matched. For read_file it is the number of the first
|
|
87
|
+
line of a text read (its `offset`); each result line is that number plus its
|
|
88
|
+
index, right-aligned to one width, then `|`. It is `null` for image reads
|
|
89
|
+
and reads that returned no lines.
|
|
90
|
+
- `search_results` (search only, else `null`) lists the search tool's results
|
|
91
|
+
in order: `{path, line, text}`, where `line` is `null` and `text` is empty
|
|
92
|
+
in files mode. `summary.matches` is its length.
|
|
93
|
+
- Answer a question or an approval with the `answer` command:
|
|
94
|
+
`{"type":"answer","request_id":"...","value":"<one of choices>"}`.
|
|
95
|
+
- `bytes` is the UTF-8 size of a write_file `content` or patch `new_str`, else `null`.
|
|
96
|
+
- The session journal stores the same fields: `build_agent` gives the
|
|
97
|
+
JSONL store its own `EventFields`, approval questions are emitted through
|
|
98
|
+
the harness so they are journaled, and `subagent_step` is journaled in the
|
|
99
|
+
parent session. `session/history` events and resumed sessions therefore
|
|
100
|
+
carry `call_id`, `parent_call_id`, `duration_ms`, `exit_code`, `summary`,
|
|
101
|
+
`search_results`,
|
|
102
|
+
the approval `target`/`detail`/`bytes`, and `subagent_step`.
|
|
103
|
+
- `tool_execution_output` chunks are live only. Bash emits them on the sink,
|
|
104
|
+
outside `CoreHarness.emit`, so they are not stamped or journaled; the
|
|
105
|
+
completed event's `result` is the stored output. Journaling `stream` per
|
|
106
|
+
chunk needs a core change: route chunks through `CoreHarness.emit` (or hand
|
|
107
|
+
tools a stamped emit).
|
|
108
|
+
- Sessions recorded before protocol 3 lack these fields. Clients render those
|
|
109
|
+
calls without the facts; they do not derive them from `result` text.
|