tamfis-code 0.6.7__tar.gz → 0.6.13__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.
- {tamfis_code-0.6.7/tamfis_code.egg-info → tamfis_code-0.6.13}/PKG-INFO +1 -1
- tamfis_code-0.6.13/USAGE_INSTALL_RELEASE.md +85 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/pyproject.toml +1 -1
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/__init__.py +1 -1
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/cli.py +4 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/config.py +23 -0
- tamfis_code-0.6.13/tamfis_code/github_commands.py +65 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/interactive.py +17 -6
- tamfis_code-0.6.13/tamfis_code/live_input.py +216 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/mcp.py +14 -3
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/model_registry.py +41 -4
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/orchestrator/engine.py +17 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/project.py +30 -30
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/providers.py +68 -13
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/render.py +116 -14
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/runner_local.py +351 -32
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/workspace.py +8 -1
- {tamfis_code-0.6.7 → tamfis_code-0.6.13/tamfis_code.egg-info}/PKG-INFO +1 -1
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code.egg-info/SOURCES.txt +3 -3
- tamfis_code-0.6.13/tests/test_github_command_surface.py +15 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_live_input.py +4 -4
- tamfis_code-0.6.13/tests/test_parity_contracts.py +152 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_routing.py +51 -13
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_runner_local.py +83 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_config.py +22 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_render.py +3 -1
- tamfis_code-0.6.7/USAGE_INSTALL_RELEASE.md +0 -67
- tamfis_code-0.6.7/tamfis_code/live_input.py +0 -201
- tamfis_code-0.6.7/tamfis_code/sessions.py +0 -253
- tamfis_code-0.6.7/tests/test_sessions.py +0 -140
- tamfis_code-0.6.7/tests/test_workspace_discovery_gaps.py +0 -119
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/MANIFEST.in +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/README.md +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/setup.cfg +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/__main__.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/agent_definitions.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/agents.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/api_client.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/clipboard.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/completion.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/custom_commands.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/doctor.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/enforcer.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/evidence.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/hooks.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/indexer.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/instructions.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/local_chat.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/local_tools.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/mcp_stdio_server.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/metrics.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/__init__.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/agent.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/agent_server.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/automation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/compat.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/conversation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/delegation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/events.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/leases.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/security.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/skills.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/tools.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/openhands/workspace.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/orchestrator/__init__.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/orchestrator/context.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/orchestrator/planner.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/orchestrator/protocols.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/orchestrator/validator.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/provider_protocols.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/pty.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/references.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/routing.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/runner.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/safety.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/screenshot.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/state.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/swarm.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/tasks.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code/tool_policy.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code.egg-info/dependency_links.txt +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code.egg-info/entry_points.txt +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code.egg-info/requires.txt +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tamfis_code.egg-info/top_level.txt +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_agent_definitions.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_agents.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_agents_delegation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_build_installation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_cli_commands.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_clipboard.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_completion.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_context_rollover.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_custom_commands.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_cwd_validation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_doctor_session_diagnostics.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_enforcer.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_fake_tool_detection.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_find_references.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_hooks.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_indexer.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_instructions.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_integration_new.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_interactive_standalone.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_local_pty.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_mcp.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_mcp_search_bounds.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_mcp_standalone_degradation.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_mcp_stdio_server.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_metrics.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_openhands_runtime.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_openrouter_cost_routing.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_orchestrator.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_plan_mode_gate.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_plan_steps_and_attempts.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_provider_protocols.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_reasoning_effort_capability.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_reasoning_plan.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_references.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_render_task_diagnostics.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_runner_helpers.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_safety.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_safety_manifest.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_swarm.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_api_client.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_approval.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_approval_flow.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_intent.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_repl_exit.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_tasks.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_tamfis_code_workspace.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_version_consistency.py +0 -0
- {tamfis_code-0.6.7 → tamfis_code-0.6.13}/tests/test_workspace_scope.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tamfis-code
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.13
|
|
4
4
|
Summary: TamfisGPT Code -- portable subscription-backed terminal coding agent with local tools, PTY, plans, queues, and approvals
|
|
5
5
|
Project-URL: Homepage, https://github.com/tamfitronics/tamfis-code
|
|
6
6
|
Project-URL: Repository, https://github.com/tamfitronics/tamfis-code
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Install and release TamfisGPT Code
|
|
2
|
+
|
|
3
|
+
## Install on any server
|
|
4
|
+
|
|
5
|
+
Use the isolated installer so the CLI does not depend on the host Python
|
|
6
|
+
environment or a server-specific checkout:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
./install.sh
|
|
10
|
+
# Root installation: /usr/local/lib/tamfis-code + /usr/local/bin/tamfis-code
|
|
11
|
+
# User installation: ~/.local/share/tamfis-code + ~/.local/bin/tamfis-code
|
|
12
|
+
# Custom location:
|
|
13
|
+
TAMFIS_CODE_PREFIX=/opt/tamfis-code TAMFIS_CODE_BIN_DIR=/usr/local/bin ./install.sh
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The same package is portable to macOS and Windows. On Windows, create a
|
|
17
|
+
virtual environment with `py -m venv`, install the project with `py -m pip
|
|
18
|
+
install .`, and expose the virtualenv's `Scripts` directory on PATH. No
|
|
19
|
+
machine-specific checkout path is compiled into the package.
|
|
20
|
+
|
|
21
|
+
When managing several local projects, configure only the approved roots:
|
|
22
|
+
|
|
23
|
+
```toml
|
|
24
|
+
workspace_roots = ["/path/to/project-a", "/path/to/project-b"]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Place that in the user's `config.toml`, or use the portable environment
|
|
28
|
+
variable `TAMFIS_CODE_WORKSPACE_ROOTS` (path-separated). The launch directory
|
|
29
|
+
is always discovered automatically; other roots are allowed only when listed
|
|
30
|
+
or explicitly approved with `tamfis-code workspace add <path>`.
|
|
31
|
+
|
|
32
|
+
The runtime stores session state in the platform's user configuration
|
|
33
|
+
directory. It does not require a TamfisGPT server process on the same host.
|
|
34
|
+
|
|
35
|
+
## Configure the TamfisGPT subscription API
|
|
36
|
+
|
|
37
|
+
Create an API key from the TamfisGPT account connected to the user's active
|
|
38
|
+
iOS subscription, then set:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export TAMFIS_API_KEY='tamfis_sk_live_...'
|
|
42
|
+
export TAMFIS_API_BASE='https://gpt.tamfitronics.com/api/v1/openai'
|
|
43
|
+
tamfis-code doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The API key is the user's entitlement boundary. Admin credentials are not
|
|
47
|
+
needed for ordinary subscribers. The API handles model access and billing;
|
|
48
|
+
the installed CLI keeps workspace files, commands, PTYs, approvals, and the
|
|
49
|
+
mutation ledger on the local machine.
|
|
50
|
+
|
|
51
|
+
## Interactive follow-up input
|
|
52
|
+
|
|
53
|
+
While a task is running, the `message>` editor remains available in the same
|
|
54
|
+
terminal. Type normally and press Enter; the line is echoed with a durable
|
|
55
|
+
queue ID, then applied at the next safe model-round boundary. Multiple queued
|
|
56
|
+
updates are processed in order. A second terminal can enqueue the same session
|
|
57
|
+
with:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
tamfis-code queue "also inspect the authentication flow" --classification follow_up
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`/queue` lists pending requests in the interactive REPL. `Ctrl+C` remains the
|
|
64
|
+
interrupt/exit control. No special Ctrl+Y shortcut is required.
|
|
65
|
+
|
|
66
|
+
## Build and publish a release
|
|
67
|
+
|
|
68
|
+
Run the full checks, build a clean distribution, and inspect it before
|
|
69
|
+
publishing:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python3 -m pytest -q
|
|
73
|
+
python3 -m build
|
|
74
|
+
python3 -m twine check dist/*
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Publishing requires an account token configured in the release environment:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python3 -m twine upload dist/*
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The GitHub repository should receive the same source revision and release
|
|
84
|
+
notes as the PyPI upload. Never commit API keys, subscription tokens, local
|
|
85
|
+
session state, or generated build artifacts.
|
|
@@ -17,7 +17,7 @@ See docs/REMOTE_AGENT_MASTER_SPEC.md, Phase 21, for the original --remote
|
|
|
17
17
|
architecture's spec.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
__version__ = "0.6.
|
|
20
|
+
__version__ = "0.6.13"
|
|
21
21
|
|
|
22
22
|
# Bumped whenever a CLI release requires a minimum backend Remote API
|
|
23
23
|
# contract version. Only meaningful for --remote; the standalone path has no
|
|
@@ -2381,3 +2381,7 @@ from .enforcer import TestEnforcer, run_enforcer, add_enforcer_command
|
|
|
2381
2381
|
# flags wired to _run_shell_checks/_run_type_checks, neither of which exist on
|
|
2382
2382
|
# TestEnforcer -- using those flags raised AttributeError at runtime.
|
|
2383
2383
|
add_enforcer_command(cli)
|
|
2384
|
+
|
|
2385
|
+
# Familiar GitHub CLI command surface, delegated to the installed `gh` binary.
|
|
2386
|
+
from .github_commands import register_github_commands
|
|
2387
|
+
register_github_commands(cli)
|
|
@@ -146,6 +146,11 @@ class Config:
|
|
|
146
146
|
# TamfisGPT tenant who wants that be the default, the same way Claude
|
|
147
147
|
# Code/Codex/kimi-code default to their respective hosted accounts.
|
|
148
148
|
default_backend: str = "standalone"
|
|
149
|
+
# Additional local roots that may be used by this installation. The
|
|
150
|
+
# launch directory is always included separately; these roots make a
|
|
151
|
+
# multi-project service practical without granting access to the whole
|
|
152
|
+
# filesystem.
|
|
153
|
+
workspace_roots: list[str] = field(default_factory=list)
|
|
149
154
|
sources: dict[str, str] = field(default_factory=dict) # field -> where it came from, for `doctor`/`config`
|
|
150
155
|
|
|
151
156
|
def as_dict(self) -> dict[str, Any]:
|
|
@@ -158,6 +163,7 @@ class Config:
|
|
|
158
163
|
"debug": self.debug,
|
|
159
164
|
"enable_subagent_delegation": self.enable_subagent_delegation,
|
|
160
165
|
"default_backend": self.default_backend,
|
|
166
|
+
"workspace_roots": self.workspace_roots,
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
|
|
@@ -193,6 +199,14 @@ def load_config(project_root: Optional[Path] = None) -> Config:
|
|
|
193
199
|
if data.get("default_backend") in ("standalone", "remote"):
|
|
194
200
|
cfg.default_backend = str(data["default_backend"])
|
|
195
201
|
cfg.sources["default_backend"] = source_name
|
|
202
|
+
configured_roots = data.get("workspace_roots")
|
|
203
|
+
if isinstance(configured_roots, list):
|
|
204
|
+
cfg.workspace_roots = [
|
|
205
|
+
str(Path(item).expanduser().resolve())
|
|
206
|
+
for item in configured_roots
|
|
207
|
+
if isinstance(item, str) and item.strip()
|
|
208
|
+
]
|
|
209
|
+
cfg.sources["workspace_roots"] = source_name
|
|
196
210
|
|
|
197
211
|
env_api_base = os.environ.get("TAMFIS_CODE_API_BASE")
|
|
198
212
|
if env_api_base:
|
|
@@ -214,6 +228,15 @@ def load_config(project_root: Optional[Path] = None) -> Config:
|
|
|
214
228
|
cfg.default_backend = env_backend
|
|
215
229
|
cfg.sources["default_backend"] = "env TAMFIS_CODE_DEFAULT_BACKEND"
|
|
216
230
|
|
|
231
|
+
env_roots = os.environ.get("TAMFIS_CODE_WORKSPACE_ROOTS")
|
|
232
|
+
if env_roots:
|
|
233
|
+
cfg.workspace_roots = [
|
|
234
|
+
str(Path(item.strip()).expanduser().resolve())
|
|
235
|
+
for item in env_roots.split(os.pathsep)
|
|
236
|
+
if item.strip()
|
|
237
|
+
]
|
|
238
|
+
cfg.sources["workspace_roots"] = "env TAMFIS_CODE_WORKSPACE_ROOTS"
|
|
239
|
+
|
|
217
240
|
return cfg
|
|
218
241
|
|
|
219
242
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""GitHub CLI parity commands.
|
|
2
|
+
|
|
3
|
+
Tamfis-Code exposes the familiar ``gh`` command surface without reimplementing
|
|
4
|
+
GitHub authentication or API semantics. Each command delegates directly to the
|
|
5
|
+
installed GitHub CLI, preserves the current working directory, streams PTY/TTY
|
|
6
|
+
I/O unchanged, and returns the exact child exit code.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
from typing import Iterable
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
GITHUB_COMMANDS: tuple[str, ...] = (
|
|
18
|
+
"alias", "api", "auth", "browse", "cache", "co", "codespace",
|
|
19
|
+
"completion", "config", "extension", "gist", "gpg-key", "issue",
|
|
20
|
+
"label", "org", "pr", "project", "release", "repo", "ruleset",
|
|
21
|
+
"run", "search", "secret", "ssh-key", "status", "variable", "workflow",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _gh_binary() -> str:
|
|
26
|
+
binary = shutil.which("gh")
|
|
27
|
+
if not binary:
|
|
28
|
+
raise click.ClickException(
|
|
29
|
+
"GitHub CLI (`gh`) is not installed or is not available in PATH."
|
|
30
|
+
)
|
|
31
|
+
return binary
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def run_gh(subcommand: str, args: Iterable[str]) -> None:
|
|
35
|
+
"""Execute one gh subcommand with transparent terminal behaviour."""
|
|
36
|
+
command = [_gh_binary(), subcommand, *args]
|
|
37
|
+
completed = subprocess.run(command, cwd=os.getcwd(), check=False)
|
|
38
|
+
if completed.returncode:
|
|
39
|
+
raise click.exceptions.Exit(completed.returncode)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_github_commands(root: click.Group) -> None:
|
|
43
|
+
"""Register all requested gh-compatible top-level commands."""
|
|
44
|
+
existing = set(root.commands)
|
|
45
|
+
for command_name in GITHUB_COMMANDS:
|
|
46
|
+
# Tamfis-Code owns a richer native command with the same name.
|
|
47
|
+
# Keep that native implementation rather than shadowing it.
|
|
48
|
+
if command_name in existing:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
def callback(args: tuple[str, ...], _name: str = command_name) -> None:
|
|
52
|
+
run_gh(_name, args)
|
|
53
|
+
|
|
54
|
+
command = click.Command(
|
|
55
|
+
name=command_name,
|
|
56
|
+
callback=callback,
|
|
57
|
+
params=[click.Argument(["args"], nargs=-1, type=click.UNPROCESSED)],
|
|
58
|
+
context_settings={
|
|
59
|
+
"ignore_unknown_options": True,
|
|
60
|
+
"allow_extra_args": True,
|
|
61
|
+
"help_option_names": [],
|
|
62
|
+
},
|
|
63
|
+
help=f"Delegate to `gh {command_name}` with full PTY/TTY passthrough.",
|
|
64
|
+
)
|
|
65
|
+
root.add_command(command)
|
|
@@ -111,7 +111,7 @@ $ <command> explicit shell command
|
|
|
111
111
|
/mode <name> switch mode: manual | accept-edits | auto | plan
|
|
112
112
|
Shift+Tab cycle mode without typing a command (shown in the prompt as [mode]);
|
|
113
113
|
also works while a task is already running, not just at this prompt
|
|
114
|
-
|
|
114
|
+
message> while a task is running: type a message and press Enter
|
|
115
115
|
/model show the active model route
|
|
116
116
|
/model list [route] list coding models (route: hf or openrouter)
|
|
117
117
|
/model auto restore Hugging Face -> OpenRouter automatic routing
|
|
@@ -372,10 +372,8 @@ async def run_interactive(
|
|
|
372
372
|
)
|
|
373
373
|
console.print(
|
|
374
374
|
"[dim]Type /help for commands. Paste up to 1,000,000 characters; Alt+Enter adds a newline. "
|
|
375
|
-
"
|
|
376
|
-
"
|
|
377
|
-
"your submitted text is acknowledged with a queue id and applied at the next safe round boundary. "
|
|
378
|
-
"Ctrl+D or Ctrl+C exits.[/dim]\n"
|
|
375
|
+
"While a task runs, a normal message prompt remains available: type and press Enter; "
|
|
376
|
+
"your text is queued without a special shortcut. Shift+Tab cycles mode. Ctrl+D or Ctrl+C exits.[/dim]\n"
|
|
379
377
|
)
|
|
380
378
|
|
|
381
379
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -990,7 +988,7 @@ async def run_interactive(
|
|
|
990
988
|
"/mode auto auto-approves everything, never prompts (server safeguards still apply)\n"
|
|
991
989
|
"/mode plan read-only: propose without executing\n"
|
|
992
990
|
"Shift+Tab cycle manual -> accept-edits -> auto -> plan without typing a command\n"
|
|
993
|
-
" (also works mid-task, not just here);
|
|
991
|
+
" (also works mid-task, not just here); type in the message prompt to queue work\n"
|
|
994
992
|
"Other raw policy values also work directly (--approval-only, no short alias): safe, workspace,\n"
|
|
995
993
|
"read-only, plan-only, suggest, full-auto, and 'never' -- note 'never' means DENY everything\n"
|
|
996
994
|
"(the opposite of what it sounds like; it is not a synonym for 'auto').[/dim]"
|
|
@@ -1319,6 +1317,10 @@ async def run_interactive(
|
|
|
1319
1317
|
last_response_text = outcome.summary
|
|
1320
1318
|
if outcome.status == "completed" and outcome.summary and not renderer.streamed_final_text:
|
|
1321
1319
|
console.print(Markdown(outcome.summary))
|
|
1320
|
+
if outcome.status == "exited":
|
|
1321
|
+
if local_pty is not None:
|
|
1322
|
+
local_pty.close()
|
|
1323
|
+
break
|
|
1322
1324
|
if outcome.status != "completed":
|
|
1323
1325
|
print_error(console, outcome.error or f"task {outcome.status}")
|
|
1324
1326
|
console.print()
|
|
@@ -1343,6 +1345,10 @@ async def run_interactive(
|
|
|
1343
1345
|
last_response_text = outcome.summary
|
|
1344
1346
|
if outcome.status == "completed" and outcome.summary and not renderer.streamed_final_text:
|
|
1345
1347
|
console.print(Markdown(outcome.summary))
|
|
1348
|
+
if outcome.status == "exited":
|
|
1349
|
+
if local_pty is not None:
|
|
1350
|
+
local_pty.close()
|
|
1351
|
+
break
|
|
1346
1352
|
if outcome.status != "completed":
|
|
1347
1353
|
print_error(console, outcome.error or f"task {outcome.status}")
|
|
1348
1354
|
except (AuthRequiredError, RemoteAPIError) as e:
|
|
@@ -1363,6 +1369,7 @@ async def run_interactive(
|
|
|
1363
1369
|
or reply_state.turn_checkpoint or reply_state.conversation_history
|
|
1364
1370
|
),
|
|
1365
1371
|
)
|
|
1372
|
+
submitted_text = text
|
|
1366
1373
|
intent = parse_intent(text, custom_commands=custom_commands)
|
|
1367
1374
|
try:
|
|
1368
1375
|
if intent.kind == "shell":
|
|
@@ -1392,6 +1399,10 @@ async def run_interactive(
|
|
|
1392
1399
|
continue
|
|
1393
1400
|
renderer = StreamRenderer(console, mode_label=mode_label_for_policy(config.approval_policy))
|
|
1394
1401
|
model_state = local_state.get_session_state(workspace.session_id)
|
|
1402
|
+
renderer.handle_event({
|
|
1403
|
+
"event_type": "user_message",
|
|
1404
|
+
"payload": {"content": submitted_text},
|
|
1405
|
+
})
|
|
1395
1406
|
if standalone:
|
|
1396
1407
|
live_input = LiveInputListener(session_id=workspace.session_id, renderer=renderer, cli_config=config)
|
|
1397
1408
|
live_input.start()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Non-blocking follow-up input for a running standalone task.
|
|
2
|
+
|
|
3
|
+
The old implementation put stdin into cbreak mode, discarded ordinary
|
|
4
|
+
characters, and required Ctrl+Y to open a second editor. That made the
|
|
5
|
+
terminal feel frozen and made mouse selection/scrolling fight Rich's live
|
|
6
|
+
redraw. A running task now owns a normal prompt-toolkit line editor instead:
|
|
7
|
+
the user can type at any time, press Enter to queue a follow-up, and keep
|
|
8
|
+
typing the next one while the model continues streaming.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import contextlib
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from prompt_toolkit.formatted_text import HTML
|
|
18
|
+
|
|
19
|
+
from . import state as local_state
|
|
20
|
+
from .config import Config, mode_label_for_policy, next_mode_in_cycle
|
|
21
|
+
from .render import StreamRenderer
|
|
22
|
+
|
|
23
|
+
_SHIFT_TAB = b"\x1b[Z"
|
|
24
|
+
# Retained only for backwards-compatible imports. Ctrl+Y is no longer read
|
|
25
|
+
# specially by the live listener; it is ordinary editable prompt input.
|
|
26
|
+
_CTRL_T = b"\x14"
|
|
27
|
+
_CTRL_Y = b"\x19"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LiveInputListener:
|
|
31
|
+
"""Run a persistent, asynchronous follow-up editor during a task."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *, session_id: int, renderer: StreamRenderer, cli_config: Config) -> None:
|
|
34
|
+
self.session_id = session_id
|
|
35
|
+
self.renderer = renderer
|
|
36
|
+
self.cli_config = cli_config
|
|
37
|
+
self._is_tty = bool(getattr(sys.stdin, "isatty", lambda: False)())
|
|
38
|
+
self._input_task: Optional[asyncio.Task] = None
|
|
39
|
+
self._interject_task: Optional[asyncio.Task] = None
|
|
40
|
+
self._prompt_session = None
|
|
41
|
+
self._paused = False
|
|
42
|
+
self._active = False
|
|
43
|
+
|
|
44
|
+
def start(self) -> None:
|
|
45
|
+
if not self._is_tty:
|
|
46
|
+
return
|
|
47
|
+
# Stop Rich's repainting while the prompt owns the terminal. Streamed
|
|
48
|
+
# assistant text is intentionally rendered as scrollback in this
|
|
49
|
+
# mode, so the input line and mouse scrolling never compete.
|
|
50
|
+
self.renderer.suspend_live()
|
|
51
|
+
self.renderer.live_input_listener = self
|
|
52
|
+
self._active = True
|
|
53
|
+
self._schedule_prompt()
|
|
54
|
+
|
|
55
|
+
def stop(self) -> None:
|
|
56
|
+
self._active = False
|
|
57
|
+
self._cancel_prompt()
|
|
58
|
+
if self.renderer.live_input_listener is self:
|
|
59
|
+
self.renderer.live_input_listener = None
|
|
60
|
+
self.renderer.resume_live()
|
|
61
|
+
|
|
62
|
+
def pause(self) -> None:
|
|
63
|
+
self._paused = True
|
|
64
|
+
self._cancel_prompt()
|
|
65
|
+
|
|
66
|
+
def resume(self) -> None:
|
|
67
|
+
self._paused = False
|
|
68
|
+
if self._active:
|
|
69
|
+
self._schedule_prompt()
|
|
70
|
+
|
|
71
|
+
def _dispatch(self) -> None:
|
|
72
|
+
"""Compatibility hook for older embedders; start() no longer uses it."""
|
|
73
|
+
buf = bytes(getattr(self, "_buf", b""))
|
|
74
|
+
if _SHIFT_TAB in buf:
|
|
75
|
+
self._buf = bytearray()
|
|
76
|
+
self.cli_config.approval_policy = next_mode_in_cycle(self.cli_config.approval_policy)
|
|
77
|
+
self.renderer.handle_event({
|
|
78
|
+
"event_type": "diagnostics",
|
|
79
|
+
"payload": {"content": f"◆ Mode switched to {mode_label_for_policy(self.cli_config.approval_policy)}."},
|
|
80
|
+
})
|
|
81
|
+
if hasattr(self.renderer, "set_mode_label"):
|
|
82
|
+
self.renderer.set_mode_label(mode_label_for_policy(self.cli_config.approval_policy))
|
|
83
|
+
elif buf in {b"\x1b", b"\x1b["}:
|
|
84
|
+
return
|
|
85
|
+
elif _CTRL_Y in buf:
|
|
86
|
+
self._buf = bytearray()
|
|
87
|
+
if self._interject_task is None or self._interject_task.done():
|
|
88
|
+
self._interject_task = asyncio.create_task(self._interject())
|
|
89
|
+
elif buf:
|
|
90
|
+
self._buf = bytearray()
|
|
91
|
+
|
|
92
|
+
def _schedule_prompt(self) -> None:
|
|
93
|
+
if self._input_task is None or self._input_task.done():
|
|
94
|
+
self._input_task = asyncio.create_task(self._input_loop())
|
|
95
|
+
|
|
96
|
+
def _cancel_prompt(self) -> None:
|
|
97
|
+
task = self._input_task
|
|
98
|
+
self._input_task = None
|
|
99
|
+
if task is not None and not task.done():
|
|
100
|
+
task.cancel()
|
|
101
|
+
|
|
102
|
+
def invalidate(self) -> None:
|
|
103
|
+
"""Refresh the prompt footer after a streamed phase/status update."""
|
|
104
|
+
app = getattr(self._prompt_session, "app", None)
|
|
105
|
+
if app is not None:
|
|
106
|
+
app.invalidate()
|
|
107
|
+
|
|
108
|
+
def _bottom_toolbar(self):
|
|
109
|
+
status = self.renderer.live_input_status()
|
|
110
|
+
mode = mode_label_for_policy(self.cli_config.approval_policy)
|
|
111
|
+
return HTML(
|
|
112
|
+
f"<b><ansicyan>◆</ansicyan></b> <b>{status}</b> "
|
|
113
|
+
f"<ansigray>│ {mode} │ esc interrupt │ shift+tab mode</ansigray>"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
async def _input_loop(self) -> None:
|
|
117
|
+
from prompt_toolkit import PromptSession
|
|
118
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
119
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
120
|
+
|
|
121
|
+
bindings = KeyBindings()
|
|
122
|
+
|
|
123
|
+
# Some SSH/terminal clients emit focus-in/focus-out as CSI sequences
|
|
124
|
+
# (ESC [ I / ESC [ O). Because ``escape`` is also the intentional
|
|
125
|
+
# cancel binding below, prompt_toolkit can otherwise dispatch the
|
|
126
|
+
# first byte as a cancellation and leak the trailing ``I``/``O`` into
|
|
127
|
+
# the prompt. Consume those complete sequences before the bare-Esc
|
|
128
|
+
# binding gets a chance to act.
|
|
129
|
+
@bindings.add("escape", "[", "I")
|
|
130
|
+
def _ignore_focus_in(event) -> None:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
@bindings.add("escape", "[", "O")
|
|
134
|
+
def _ignore_focus_out(event) -> None:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
@bindings.add("escape")
|
|
138
|
+
def _cancel_running_turn(event) -> None:
|
|
139
|
+
# Match the interrupt affordance offered by other terminal agents:
|
|
140
|
+
# Escape cancels the active turn, while the prompt remains usable
|
|
141
|
+
# for subsequent work once the runner reaches a safe boundary.
|
|
142
|
+
self._enqueue_control("cancel")
|
|
143
|
+
event.app.exit(result="")
|
|
144
|
+
|
|
145
|
+
session = PromptSession(key_bindings=bindings)
|
|
146
|
+
self._prompt_session = session
|
|
147
|
+
while self._active and not self._paused:
|
|
148
|
+
try:
|
|
149
|
+
# patch_stdout keeps concurrent tool/assistant output above
|
|
150
|
+
# the current line and redraws the line beneath it.
|
|
151
|
+
with patch_stdout(raw=True):
|
|
152
|
+
text = await session.prompt_async(
|
|
153
|
+
"message> ", bottom_toolbar=self._bottom_toolbar,
|
|
154
|
+
)
|
|
155
|
+
except asyncio.CancelledError:
|
|
156
|
+
return
|
|
157
|
+
except KeyboardInterrupt:
|
|
158
|
+
# Ctrl+C is the process/REPL exit affordance. The active
|
|
159
|
+
# runner's signal path remains reserved for true process
|
|
160
|
+
# interrupts; queue an explicit exit so the local runner
|
|
161
|
+
# can finish its current safe boundary and the REPL exits.
|
|
162
|
+
self._enqueue_control("exit")
|
|
163
|
+
return
|
|
164
|
+
except EOFError:
|
|
165
|
+
return
|
|
166
|
+
self._enqueue(text)
|
|
167
|
+
self._prompt_session = None
|
|
168
|
+
|
|
169
|
+
def _enqueue_control(self, classification: str) -> None:
|
|
170
|
+
item = local_state.enqueue_instruction(
|
|
171
|
+
self.session_id, "", classification=classification,
|
|
172
|
+
)
|
|
173
|
+
self.renderer.handle_event({
|
|
174
|
+
"event_type": "diagnostics",
|
|
175
|
+
"payload": {"content": f"◆ Queued {classification} for the running task ({item.id})."},
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
def _enqueue(self, text: str) -> None:
|
|
179
|
+
text = text.strip()
|
|
180
|
+
if not text:
|
|
181
|
+
if self._active and not self._paused:
|
|
182
|
+
self._schedule_prompt()
|
|
183
|
+
return
|
|
184
|
+
item = local_state.enqueue_instruction(
|
|
185
|
+
self.session_id, text, classification="follow_up",
|
|
186
|
+
)
|
|
187
|
+
self.renderer.handle_event({
|
|
188
|
+
"event_type": "user_message",
|
|
189
|
+
"payload": {"content": text},
|
|
190
|
+
})
|
|
191
|
+
self.renderer.handle_event({
|
|
192
|
+
"event_type": "diagnostics",
|
|
193
|
+
"payload": {
|
|
194
|
+
"content": (
|
|
195
|
+
f"◆ Queued next instruction {item.id}: {text} "
|
|
196
|
+
"-- applied at the next safe round boundary."
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
if self._active and not self._paused:
|
|
201
|
+
self._schedule_prompt()
|
|
202
|
+
|
|
203
|
+
async def _interject(self) -> None:
|
|
204
|
+
"""Compatibility helper for callers/tests that submit one line."""
|
|
205
|
+
from prompt_toolkit import PromptSession
|
|
206
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
with patch_stdout(raw=True):
|
|
210
|
+
text = await PromptSession().prompt_async("message> ")
|
|
211
|
+
except KeyboardInterrupt:
|
|
212
|
+
self._enqueue_control("exit")
|
|
213
|
+
return
|
|
214
|
+
except EOFError:
|
|
215
|
+
text = ""
|
|
216
|
+
self._enqueue(text)
|
|
@@ -163,6 +163,7 @@ class MCPServer:
|
|
|
163
163
|
console: Optional[Any] = None, renderer: Optional[Any] = None, interactive: bool = False,
|
|
164
164
|
transaction_id: Optional[str] = None,
|
|
165
165
|
attachment_paths: Optional[List[str]] = None,
|
|
166
|
+
allowed_workspace_roots: Optional[List[str]] = None,
|
|
166
167
|
):
|
|
167
168
|
# workspace_root/session_id are optional so existing callers that
|
|
168
169
|
# construct MCPServer() with no arguments (tests, the `tools`/
|
|
@@ -170,6 +171,13 @@ class MCPServer:
|
|
|
170
171
|
# enforcement on write_file/edit_file, no mutation-ledger recording.
|
|
171
172
|
# The standalone agent loop (runner_local.py) always supplies both.
|
|
172
173
|
self.workspace_root = workspace_root
|
|
174
|
+
self.allowed_workspace_roots = {
|
|
175
|
+
Path(item).expanduser().resolve()
|
|
176
|
+
for item in (allowed_workspace_roots or [])
|
|
177
|
+
if item
|
|
178
|
+
}
|
|
179
|
+
if workspace_root:
|
|
180
|
+
self.allowed_workspace_roots.add(Path(workspace_root).expanduser().resolve())
|
|
173
181
|
self.session_id = session_id
|
|
174
182
|
# Explicit CLI attachments are readable inputs, not extra writable
|
|
175
183
|
# workspaces. Only these exact files are admitted; their parent
|
|
@@ -664,9 +672,12 @@ class MCPServer:
|
|
|
664
672
|
p = base / p
|
|
665
673
|
resolved = p.resolve()
|
|
666
674
|
if self.workspace_root:
|
|
667
|
-
|
|
668
|
-
if resolved
|
|
669
|
-
|
|
675
|
+
roots = self.allowed_workspace_roots or {base.resolve()}
|
|
676
|
+
if not any(resolved == root or root in resolved.parents for root in roots):
|
|
677
|
+
rendered = ", ".join(str(root) for root in sorted(roots, key=str))
|
|
678
|
+
raise PermissionError(
|
|
679
|
+
f"'{path}' resolves outside the workspace; approved roots: ({rendered})"
|
|
680
|
+
)
|
|
670
681
|
return resolved
|
|
671
682
|
|
|
672
683
|
async def _write_file(self, path: str, content: str) -> str:
|
|
@@ -77,10 +77,47 @@ MODELS: dict[str, ModelRecord] = {
|
|
|
77
77
|
("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
|
|
78
78
|
"frontier", "medium",
|
|
79
79
|
),
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
ModelCapabilities(long_context=
|
|
83
|
-
("
|
|
80
|
+
"deepseek-ai/deepseek-v4-pro": ModelRecord(
|
|
81
|
+
"deepseek-ai/deepseek-v4-pro", "nvidia",
|
|
82
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 1000000,
|
|
83
|
+
("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
|
|
84
|
+
"frontier", "high",
|
|
85
|
+
),
|
|
86
|
+
"deepseek-ai/deepseek-v4-flash": ModelRecord(
|
|
87
|
+
"deepseek-ai/deepseek-v4-flash", "nvidia",
|
|
88
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 1000000,
|
|
89
|
+
("repository_search", "multi_file_edit", "debugging", "tool_heavy_execution"),
|
|
90
|
+
"frontier", "medium",
|
|
91
|
+
),
|
|
92
|
+
"Qwen/Qwen3.6-35B-A3B": ModelRecord(
|
|
93
|
+
"Qwen/Qwen3.6-35B-A3B", "hf",
|
|
94
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 262144,
|
|
95
|
+
("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
|
|
96
|
+
"frontier", "medium",
|
|
97
|
+
),
|
|
98
|
+
"Qwen/Qwen3.6-27B": ModelRecord(
|
|
99
|
+
"Qwen/Qwen3.6-27B", "hf",
|
|
100
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 262144,
|
|
101
|
+
("repository_search", "multi_file_edit", "debugging", "planning"),
|
|
102
|
+
"frontier", "medium",
|
|
103
|
+
),
|
|
104
|
+
"Qwen/Qwen3-Coder-480B-A35B-Instruct": ModelRecord(
|
|
105
|
+
"Qwen/Qwen3-Coder-480B-A35B-Instruct", "hf",
|
|
106
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 262144,
|
|
107
|
+
("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
|
|
108
|
+
"frontier", "high",
|
|
109
|
+
),
|
|
110
|
+
"deepseek-ai/DeepSeek-V4-Pro": ModelRecord(
|
|
111
|
+
"deepseek-ai/DeepSeek-V4-Pro", "hf",
|
|
112
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 1048576,
|
|
113
|
+
("repository_audit", "multi_file_edit", "debugging", "planning", "tool_heavy_execution"),
|
|
114
|
+
"frontier", "high",
|
|
115
|
+
),
|
|
116
|
+
"deepseek-ai/DeepSeek-V4-Flash": ModelRecord(
|
|
117
|
+
"deepseek-ai/DeepSeek-V4-Flash", "hf",
|
|
118
|
+
ModelCapabilities(parallel_tool_calls=True, long_context=True), 262144,
|
|
119
|
+
("repository_search", "multi_file_edit", "debugging", "tool_heavy_execution"),
|
|
120
|
+
"frontier", "medium",
|
|
84
121
|
),
|
|
85
122
|
}
|
|
86
123
|
|
|
@@ -25,6 +25,7 @@ class OrchestrationRun:
|
|
|
25
25
|
validation: ValidationReport | None = None
|
|
26
26
|
route: dict[str, Any] = field(default_factory=dict)
|
|
27
27
|
repair_attempts: int = 0
|
|
28
|
+
reasoning_plan: bool = False
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class AgentOrchestrator:
|
|
@@ -165,6 +166,22 @@ class AgentOrchestrator:
|
|
|
165
166
|
tool_records=[item.to_dict() for item in self.run.tool_records],
|
|
166
167
|
any_mutation=any_mutation, final_text=final_text,
|
|
167
168
|
)
|
|
169
|
+
if (
|
|
170
|
+
self.run.reasoning_plan
|
|
171
|
+
and self.run.profile.task_type.value == "audit"
|
|
172
|
+
and self.run.plan is not None
|
|
173
|
+
):
|
|
174
|
+
pending = [
|
|
175
|
+
step.name for step in self.run.plan.steps
|
|
176
|
+
if step.status in {"pending", "in_progress"}
|
|
177
|
+
]
|
|
178
|
+
if pending:
|
|
179
|
+
report.passed = False
|
|
180
|
+
report.unresolved.append(
|
|
181
|
+
"Execution plan incomplete; pending steps: " + "; ".join(pending)
|
|
182
|
+
)
|
|
183
|
+
if report.severity == "pass":
|
|
184
|
+
report.severity = "warning"
|
|
168
185
|
self.run.validation = report
|
|
169
186
|
state = local_state.get_session_state(self.session_id)
|
|
170
187
|
local_state.save_session_state(
|