lcode-cli 0.37.0__tar.gz → 0.39.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.
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/CHANGELOG.md +56 -1
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/PKG-INFO +1 -1
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/configuration.md +2 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/how-it-works.md +4 -2
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/mcp.md +5 -81
- lcode_cli-0.39.0/docs/modes.md +58 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/usage.md +28 -4
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/mkdocs.yml +1 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/agent.py +37 -4
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/api.py +3 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/checks.py +95 -17
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/cli.py +12 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/config.py +5 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/catalog.toml +0 -48
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/commands.py +6 -0
- lcode_cli-0.39.0/src/lcode/modes.py +102 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/repeats.py +11 -2
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/repl.py +57 -10
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/screenshots.py +237 -10
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/tools.py +15 -1
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_checks.py +30 -0
- lcode_cli-0.39.0/tests/test_look.py +227 -0
- lcode_cli-0.39.0/tests/test_modes.py +154 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_stuck.py +13 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_xai.py +0 -11
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.editorconfig +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.gitignore +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/AGENTS.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/LICENSE +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/README.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/SECURITY.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/action.yml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/agents.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/automation.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/bench.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/changelog.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/commands.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/contributing.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/editors.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/git.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/hooks.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/index.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/installation.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/lsp.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/memory.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/models.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/search.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/servers.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/README.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/api/fix_tests.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/commands/review.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/install.sh +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/overrides/main.html +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/pyproject.toml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/scripts/make_logos.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/acp.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/action.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/backends.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/bench_backend.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/bench_runtime.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/clipboard.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/cloud.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/codesearch.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/context.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/escalate.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/gitflow.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/hooks.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/jobs.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/lsp.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/matching.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/server.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/memory.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/notify.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/planning.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/repomap.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/residency.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/review.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/secretscan.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/__init__.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/encord.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/valohai.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/servers/xai.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/steer.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/unreachable.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/update.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/conftest.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/fake_lsp_server.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_acp.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_action.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_api.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_backends.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_clipboard.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_cloud.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_codesearch.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_config.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_context.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_escalate.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_extensions.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_gitflow.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_hooks.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_jobs.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_lsp.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_matching.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_memory.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_model_picker.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_repeats.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_repomap.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_residency.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_review.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_robustness.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_screenshots.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_secretscan.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_servers.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_steer.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_subagents.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_update.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/tests/test_web.py +0 -0
- {lcode_cli-0.37.0 → lcode_cli-0.39.0}/uv.lock +0 -0
|
@@ -6,6 +6,59 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.39.0] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Modes: code, image and video** (#148). `/mode image`, `/mode video` and `/mode code` switch
|
|
14
|
+
what lcode makes. `lcode --mode` starts in one, and the `mode` setting is the default. The
|
|
15
|
+
banner, status bar and prompt show the mode, and sessions keep theirs.
|
|
16
|
+
- **The GPU goes to one model at a time:** switching to image or video mode unloads the coding
|
|
17
|
+
model, and switching back asks ComfyUI to free its memory.
|
|
18
|
+
- **Image and video mode don't generate yet:** they say what's coming (#149–#157) instead of
|
|
19
|
+
sending your words to the coding model. See [Modes](https://nasser1941.github.io/lcode/modes/).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Code mode makes code only.** The MCP servers that generated images and videos (`comfyui`,
|
|
24
|
+
`xai`) are out of the MCP catalog, and lcode doesn't load them in code mode even if they're
|
|
25
|
+
still in `mcp.json` (it says so once). Image and video generation are moving to their own modes.
|
|
26
|
+
- **`/mode` switches code, image and video.** The permission mode is set with `/permissions` now;
|
|
27
|
+
`/mode yolo` and the like still work, with a note. Shift+Tab cycles the permission mode as
|
|
28
|
+
before.
|
|
29
|
+
|
|
30
|
+
## [0.38.0] - 2026-10-21
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- The screenshot tool can click first. For a Python Qt program, `actions` run before the picture,
|
|
35
|
+
the way a user would: `double_click Camera 1` (a widget by its text) or `double_click 300,200`,
|
|
36
|
+
`click`, `right_click`, `key Escape`, `key ctrl+s`, `type …`, `wait 2`. The result says what each
|
|
37
|
+
step hit. Bugs that only show after a click were out of reach before.
|
|
38
|
+
- Qt's warnings are listed at the top of a screenshot's report, taken from everything the program
|
|
39
|
+
printed. In a real run the cause (`setLayout: … which already has a layout`) was there, among 30
|
|
40
|
+
lines of the program's logs, and the model looked past it.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
From a real session that "fixed" a double-click feature four times without once looking at the
|
|
45
|
+
window, while the user saw a blank screen each time:
|
|
46
|
+
|
|
47
|
+
- **Windows it never looked at** are sent back once before the answer counts. When a request
|
|
48
|
+
changed a Python file that draws a window (PyQt, PySide, tkinter, Kivy, wx, GTK) and the model
|
|
49
|
+
never took a screenshot or looked at an image, it's told to look (with clicks first, if that's
|
|
50
|
+
where the change shows) or to say plainly that it's unchecked. The real cause was in what Qt
|
|
51
|
+
printed: `setLayout: … which already has a layout`.
|
|
52
|
+
- **A rerun that only took a different time** counts as a repeat: the model ran the same tests four
|
|
53
|
+
times in a row (`75 passed in 0.14s`, then `0.12s`…) without the repeat check noticing.
|
|
54
|
+
- **A reply lcode drops** for going round in circles is saved, and the message says where, so you
|
|
55
|
+
can read what the model was thinking instead of taking the stop on trust.
|
|
56
|
+
- **The checks use the project's own Python.** They ran `python -m …` from PATH, which doesn't have
|
|
57
|
+
the project's dependencies, so a project with a `.venv` "failed" its checks on imports. Now
|
|
58
|
+
`.venv/bin/python` (or `venv`, `env`) runs them.
|
|
59
|
+
- **Tests written for pytest get pytest** when the project has no pytest configuration: the tests
|
|
60
|
+
import it, or a requirements file lists it. They used to get `unittest`, which can't run them.
|
|
61
|
+
|
|
9
62
|
## [0.37.0] - 2026-10-20
|
|
10
63
|
|
|
11
64
|
### Added
|
|
@@ -823,7 +876,9 @@ First public release.
|
|
|
823
876
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
824
877
|
- One-line installer for Linux and macOS.
|
|
825
878
|
|
|
826
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
879
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.39.0...HEAD
|
|
880
|
+
[0.39.0]: https://github.com/nasser1941/lcode/compare/v0.38.0...v0.39.0
|
|
881
|
+
[0.38.0]: https://github.com/nasser1941/lcode/compare/v0.37.0...v0.38.0
|
|
827
882
|
[0.37.0]: https://github.com/nasser1941/lcode/compare/v0.36.0...v0.37.0
|
|
828
883
|
[0.36.0]: https://github.com/nasser1941/lcode/compare/v0.35.1...v0.36.0
|
|
829
884
|
[0.35.1]: https://github.com/nasser1941/lcode/compare/v0.35.0...v0.35.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.39.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -23,6 +23,8 @@ lcode config path # print the file location
|
|
|
23
23
|
| `backend` | `ollama` | The model server: `ollama`, `lmstudio`, `llama.cpp`, `vllm`, `mlx` or `openai` (any OpenAI-compatible API); see [Other model servers](servers.md) |
|
|
24
24
|
| `base_url` | the backend's | The server's OpenAI-compatible address, e.g. `http://localhost:1234/v1` |
|
|
25
25
|
| `permission_mode` | `ask` | `ask`, `plan`, `auto-edit` or `yolo` |
|
|
26
|
+
| `mode` | `code` | What lcode makes when it starts: `code`, `image` or `video` ([modes](modes.md)) |
|
|
27
|
+
| `comfyui_url` | `http://127.0.0.1:8188` | ComfyUI's address, for image and video mode |
|
|
26
28
|
| `think` | `true` | Let the model reason before answering |
|
|
27
29
|
| `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
|
|
28
30
|
| `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
|
|
@@ -56,7 +56,8 @@ same tool, the same arguments and the same result.
|
|
|
56
56
|
`loop`.
|
|
57
57
|
|
|
58
58
|
A result that changes isn't a repeat, so a test that's run again after a fix, or a background job
|
|
59
|
-
that's checked while it runs, never adds up.
|
|
59
|
+
that's checked while it runs, never adds up. Timings don't count as a change: `75 passed in 0.12s`
|
|
60
|
+
and then `in 0.14s` is the same result. A call that may have changed something, like an edit or
|
|
60
61
|
a shell command that isn't read-only, starts the count again for the other calls: checking the build
|
|
61
62
|
after each edit is fine. Change the threshold with `lcode config set repeat_limit 5`, or turn it off
|
|
62
63
|
with `0` (that also turns off the checks for unreachable sites and for thinking in circles, below).
|
|
@@ -85,7 +86,8 @@ aren't tracked.
|
|
|
85
86
|
Small models sometimes think in circles: a few paragraphs, over and over, until the context is full.
|
|
86
87
|
That takes minutes behind the spinner and ends without an answer. lcode watches the thinking as it
|
|
87
88
|
streams, and when its sentences keep repeating, it stops the model, drops that reply and asks for a
|
|
88
|
-
short one: the next tool call, or what's blocking it.
|
|
89
|
+
short one: the next tool call, or what's blocking it. The dropped reply is saved, and the message
|
|
90
|
+
says where, so you can read what went round in circles. An empty reply, or one that filled the context
|
|
89
91
|
before it said anything, gets the same second chance. The second time in a request, lcode stops it
|
|
90
92
|
(status `loop`), offering the [stronger model](usage.md#a-stronger-model-when-stuck) first when one
|
|
91
93
|
is set. An answer the context cut off midway is kept, with a warning to `/compact` or use a larger
|
|
@@ -28,8 +28,6 @@ and find the code for the top one."*
|
|
|
28
28
|
| `gcp` | `gcloud` commands on your Google Cloud projects | `npx` and the `gcloud` CLI, signed in |
|
|
29
29
|
| `github` | Repositories, issues, pull requests, Actions | a GitHub token (or the GitHub CLI) |
|
|
30
30
|
| `playwright` | A real (headless) browser: open pages, click, fill forms | `npx` |
|
|
31
|
-
| `comfyui` | Generate and edit images with local models (FLUX, SDXL, SD 1.5, Qwen-Image) | `uvx` and [ComfyUI](#images-with-comfyui) running locally |
|
|
32
|
-
| `xai` | Generate and edit images, and generate videos and speech, with xAI's Grok Imagine (paid, in the cloud) | `uvx`, an xAI API key ([below](#images-videos-and-speech-with-xai)) |
|
|
33
31
|
| `context7` | Up-to-date docs and examples for thousands of libraries | nothing (an API key is optional) |
|
|
34
32
|
| `sentry` | Errors, issues, traces and releases | a Sentry account (browser sign-in) |
|
|
35
33
|
| `postgres` | Schemas, read-only queries, query performance | `uvx`, a connection URL |
|
|
@@ -87,86 +85,12 @@ Google Cloud project:
|
|
|
87
85
|
See [Google's guide](https://developers.google.com/workspace/guides/configure-mcp-servers) for
|
|
88
86
|
details.
|
|
89
87
|
|
|
90
|
-
### Images
|
|
88
|
+
### Images and videos
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
uv tool install comfy-cli && comfy install # ComfyUI, once
|
|
98
|
-
comfy launch --background -- --disable-smart-memory # start it (http://127.0.0.1:8188)
|
|
99
|
-
lcode mcp add comfyui
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Then add models in ComfyUI (its model manager, or ask lcode: *"download FLUX.2 Klein 4B in
|
|
103
|
-
ComfyUI"*), and ask for images: *"make a 512×512 app icon of a paper plane and save it in
|
|
104
|
-
assets/"*. To edit an image, ask lcode to upload it and run an editing workflow (Kontext or
|
|
105
|
-
Qwen-Image-Edit).
|
|
106
|
-
|
|
107
|
-
| Model | Good for | On a 12 GB GPU |
|
|
108
|
-
|---|---|---|
|
|
109
|
-
| FLUX.2 Klein 4B | Generation and editing, fast | Fits (about 8 GB) |
|
|
110
|
-
| SDXL and its community models | Generation, inpainting | Fits |
|
|
111
|
-
| Stable Diffusion 1.5 and its community models | Light generation, inpainting | Fits easily |
|
|
112
|
-
| FLUX.1 Dev and finetunes | High-quality generation | Needs an fp8 or GGUF version; slower |
|
|
113
|
-
| FLUX.1 Kontext Dev | Editing an image from instructions | Needs an fp8 or GGUF version; slower |
|
|
114
|
-
| Qwen-Image / Qwen-Image-Edit | Generation and editing, good text in images | Heavy: a GGUF version and RAM offloading |
|
|
115
|
-
|
|
116
|
-
Only ComfyUI's local tools are turned on; Comfy Cloud's partner tools are left out, so prompts and
|
|
117
|
-
images stay on your machine.
|
|
118
|
-
|
|
119
|
-
**Sharing one GPU.** A GPU rarely holds a coding model and an image model at the same time, so the
|
|
120
|
-
two take turns: before ComfyUI generates, lcode unloads its own model (the `free_gpu` setting), and
|
|
121
|
-
ComfyUI started with `--disable-smart-memory` gives the GPU back after each image. lcode's model
|
|
122
|
-
then reloads for the next step, which adds a few seconds to half a minute per image request. With
|
|
123
|
-
enough GPU memory for both (or on a Mac with plenty of memory), remove `free_gpu` from the server's
|
|
124
|
-
settings in `mcp.json`. lcode can also look at the results ([Images](usage.md#images)).
|
|
125
|
-
|
|
126
|
-
Measured on an RTX 4080 Laptop GPU (12 GB) with qwen3.6-35b at 64K context and FLUX.2 Klein Base 4B
|
|
127
|
-
(fp8, 512×512, 20 steps):
|
|
128
|
-
|
|
129
|
-
| | |
|
|
130
|
-
|---|---|
|
|
131
|
-
| Generating while lcode's model is loaded | Fails: ComfyUI runs out of GPU memory |
|
|
132
|
-
| Freeing the GPU (lcode unloads its model) | 0.2 s |
|
|
133
|
-
| Generating one image | about 12 s |
|
|
134
|
-
| Reloading lcode's model afterwards | about 20 s |
|
|
135
|
-
|
|
136
|
-
The first time, the model has to find the right template and fill in its settings (model file names,
|
|
137
|
-
size, prompt), which can take several minutes of trial and error. Once an image comes out right, ask
|
|
138
|
-
lcode to save that workflow in your project (for example `assets/icon.workflow.json`) and reuse it:
|
|
139
|
-
later images are a single `run_workflow` call.
|
|
140
|
-
|
|
141
|
-
### Images, videos and speech with xAI
|
|
142
|
-
|
|
143
|
-
xAI's [Grok Imagine](https://docs.x.ai/developers/model-capabilities/imagine) and
|
|
144
|
-
[text to speech](https://docs.x.ai/developers/models/text-to-speech) APIs make images, videos and
|
|
145
|
-
speech in the cloud: no GPU needed, but each call is billed to your xAI account. lcode ships an MCP
|
|
146
|
-
server for them, `lcode-mcp-xai`.
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
lcode mcp add xai # asks for your xAI API key (create one at https://console.x.ai)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
The key is typed hidden and kept in `~/.config/lcode/mcp.json`, readable only by you. If
|
|
153
|
-
`XAI_API_KEY` is already set in your environment, lcode uses that and doesn't store the key.
|
|
154
|
-
|
|
155
|
-
| Tool | | Costs |
|
|
156
|
-
|---|---|---|
|
|
157
|
-
| `generate_image` | Images from a description: 1–10 at a time, aspect ratio, 1k or 2k | $0.02–0.05 an image |
|
|
158
|
-
| `edit_image` | Change a local image as described | the same |
|
|
159
|
-
| `generate_video` | A 1–15 second MP4 from a description, optionally starting from a local image, up to 1080p. Waits for it (a minute or more) | $0.02–0.08 a second |
|
|
160
|
-
| `video_status` | Fetch a video that wasn't ready within 10 minutes | |
|
|
161
|
-
| `text_to_speech` | Speech in one of 26 voices and 20 languages, as MP3 or WAV | per character |
|
|
162
|
-
|
|
163
|
-
Files are saved in `generated/` in the project, or where you ask (*"generate an app icon and save
|
|
164
|
-
it as assets/icon.png"*). xAI has no music generation API.
|
|
165
|
-
|
|
166
|
-
Your prompts, and the images you edit or animate, go to xAI. lcode asks before each call, since each
|
|
167
|
-
one costs money. To stop asking for some tools, list them under `"allow"` for the server in
|
|
168
|
-
`mcp.json`, for example `"allow": ["generate_image", "text_to_speech"]`. xAI applies its own usage
|
|
169
|
-
policies to what it generates; when it refuses a prompt, lcode passes its message to the model.
|
|
90
|
+
Code mode makes code only, so the servers that generated images and videos in earlier versions
|
|
91
|
+
(`comfyui`, `xai`) aren't in the catalog any more, and lcode doesn't load them in code mode if
|
|
92
|
+
they're still in your `mcp.json`. Image and video generation get their own [modes](modes.md),
|
|
93
|
+
with their own models and controls.
|
|
170
94
|
|
|
171
95
|
### Metabase
|
|
172
96
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Modes: code, image and video
|
|
2
|
+
|
|
3
|
+
lcode makes three kinds of things, each in its own mode with its own models:
|
|
4
|
+
|
|
5
|
+
| Mode | What it makes | The model doing the work |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| **code** (default) | Code: it reads, writes, runs and checks your project | A coding language model in Ollama (or another server) |
|
|
8
|
+
| **image** | Images from a description and reference images | An image model, run by ComfyUI on your GPU |
|
|
9
|
+
| **video** | Videos from a description, a start image or references | A video model, run by ComfyUI on your GPU |
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
❯ /mode image
|
|
13
|
+
Mode: image (local image generation).
|
|
14
|
+
Unloaded qwen3.6-35b to free the GPU for images.
|
|
15
|
+
image ❯
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **Switching:** `/mode code`, `/mode image` and `/mode video` switch in a session. `lcode --mode image`
|
|
19
|
+
starts in one, and `lcode config set mode video` makes it the default. `/mode` alone shows
|
|
20
|
+
the current one.
|
|
21
|
+
- **Where it shows:** the banner, the status bar and the prompt (`image ❯`) show the mode.
|
|
22
|
+
- **Sessions:** a session keeps the mode it was made in, and resuming it brings that mode back.
|
|
23
|
+
|
|
24
|
+
## Code mode makes code only
|
|
25
|
+
|
|
26
|
+
Code mode is the coding agent, as before, and it no longer generates images or videos. The MCP
|
|
27
|
+
servers that did (`comfyui` and `xai`) are gone from the MCP catalog, and lcode doesn't load them
|
|
28
|
+
in code mode even if they're still in your `mcp.json`. It says so once.
|
|
29
|
+
|
|
30
|
+
## One model on the GPU at a time
|
|
31
|
+
|
|
32
|
+
Image and video modes don't need the big coding model: the image or video model does the work. A
|
|
33
|
+
small optional model will help with prompts later. Image and video models need the GPU to
|
|
34
|
+
themselves:
|
|
35
|
+
|
|
36
|
+
- **Into image or video mode:** lcode unloads its coding model from Ollama.
|
|
37
|
+
- **Back to code mode:** lcode asks ComfyUI to free its memory (at `comfyui_url`, by default
|
|
38
|
+
`http://127.0.0.1:8188`), then loads the coding model again in the background.
|
|
39
|
+
|
|
40
|
+
## Coming next
|
|
41
|
+
|
|
42
|
+
Image and video modes don't generate yet. Until the items below arrive, a request in those modes
|
|
43
|
+
says what's coming instead of going anywhere.
|
|
44
|
+
|
|
45
|
+
- **#149 Local generation engine:** lcode drives ComfyUI itself: finding or installing it, jobs,
|
|
46
|
+
progress, cancelling, and saving outputs with their settings.
|
|
47
|
+
- **#150 Media model catalog:** image and video models that fit your machine, installed on
|
|
48
|
+
demand.
|
|
49
|
+
- **#151, #152 Image mode:** generating, variations, sizes and seeds, a gallery, editing,
|
|
50
|
+
references, inpainting, upscaling.
|
|
51
|
+
- **#153, #154 Video mode:** from text or a start image, first and last frames, several
|
|
52
|
+
references, camera moves, longer clips, sound.
|
|
53
|
+
- **#155 Movies:** storyboards with a consistent cast, shots stitched into one film.
|
|
54
|
+
- **#156 Prompt assistant:** a small local model for prompts and follow-ups.
|
|
55
|
+
- **#157 Cloud generation**, with costs shown (Grok Imagine first).
|
|
56
|
+
|
|
57
|
+
The permission mode (`ask`, `plan`, `auto-edit`, `yolo`) is a different setting: `/permissions`
|
|
58
|
+
sets it, and Shift+Tab cycles it as before.
|
|
@@ -79,7 +79,8 @@ reasoning is on.
|
|
|
79
79
|
| `/models`, `/model [number or name]` | List the models and how they fit this machine, numbered: type a number to switch, or `/model 2`, `/model qwen3.5-9b` |
|
|
80
80
|
| `/think [on\|off]` | Toggle reasoning |
|
|
81
81
|
| `/verbose` | Toggle showing the reasoning text |
|
|
82
|
-
| `/mode [
|
|
82
|
+
| `/mode [code\|image\|video]` | Switch what lcode makes: [code, images or videos](modes.md) |
|
|
83
|
+
| `/permissions [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
|
|
83
84
|
| `/cd DIR` | Change the working directory |
|
|
84
85
|
| `/todos` | Show the model's task list |
|
|
85
86
|
| `/status` | The task sheet: your latest requests, the plan and the model's notes, files changed, the last check |
|
|
@@ -170,7 +171,9 @@ runs the project's checks itself before the answer is final:
|
|
|
170
171
|
- **Programs** using `lcode -p --output json` see the runs in the result's `checks`.
|
|
171
172
|
|
|
172
173
|
**Which checks.** lcode looks at the project: pytest when the configuration mentions it (`uv run
|
|
173
|
-
pytest -q` with a `uv.lock`),
|
|
174
|
+
pytest -q` with a `uv.lock`), when the tests import it, or when a requirements file lists it;
|
|
175
|
+
otherwise `python -m unittest` for `test_*.py` files. Python checks run with the project's own
|
|
176
|
+
environment when it has one (`.venv/bin/python`, `venv`, `env`), where its dependencies are. Then
|
|
174
177
|
`npm test` (or pnpm, yarn, bun) when `package.json` has a test script, `cargo test`, `go test ./...`
|
|
175
178
|
and `make test`. To choose the command yourself, set it for the repository in
|
|
176
179
|
`.lcode/settings.toml`. That file is used once you trust the repository, like its hooks:
|
|
@@ -183,6 +186,13 @@ command = "uv run pytest -q && uv run ruff check ."
|
|
|
183
186
|
Or set it for yourself with `lcode config set checks "make check"`; `lcode config set checks off`
|
|
184
187
|
turns checks off.
|
|
185
188
|
|
|
189
|
+
**Windows it never looked at.** A model that changes a window checks it with an import and the
|
|
190
|
+
project's tests, which don't show what's on the screen, and calls it done: you find a blank
|
|
191
|
+
window. When a request changed a Python file that draws one (it imports PyQt, PySide, tkinter,
|
|
192
|
+
Kivy, wx or GTK) and the model never took a [screenshot](#screenshots) or looked at an image,
|
|
193
|
+
lcode sends it back once: look at it, with clicks first if that's where the change shows, or say
|
|
194
|
+
plainly that it's unchecked. `checks = "off"` turns this off.
|
|
195
|
+
|
|
186
196
|
**Scripts it never ran.** A model that writes a build or install script tends to call it done
|
|
187
197
|
without running it once, and the first thing you see is its first error. When a request wrote a
|
|
188
198
|
shell script (a `.sh` file, or a file with a shell shebang) and no command in it ran the script,
|
|
@@ -306,7 +316,7 @@ stays loaded:
|
|
|
306
316
|
| The model was loaded | `◆ Loaded qwen3.6-35b in 15s: 22.3 GB, 45% on the GPU and the rest in system RAM.`, and a warning when less of it is on the GPU than usual, because another program or model is using GPU memory |
|
|
307
317
|
| A dense model doesn't fit on the GPU (at startup and on `/model`) | `⚠ qwen3.8-27b is a dense model: all 27B parameters work on every token. It's capable, but slow here. Only about 61% of it fits in the 12 GB of GPU memory…`, with what to do instead |
|
|
308
318
|
| The model that looks at images needs the memory | `Loading the vision model … to look at bug.png; qwen3.6-35b makes room and reloads afterwards`, then `◆ qwen3.6-35b was unloaded to make room…`, and the next step says `Reloading qwen3.6-35b into memory (it made room for the vision model …)` |
|
|
309
|
-
| A tool needs the GPU to itself
|
|
319
|
+
| A tool needs the GPU to itself (an MCP server with `free_gpu`) | `◆ Unloaded qwen3.6-35b so the server can use the GPU; it reloads afterwards.` |
|
|
310
320
|
| Ollama ran out of memory | `⚠` what ran out (the prompt batch, or the context next to the model), what lcode retries with, and what it remembers for next time |
|
|
311
321
|
|
|
312
322
|
While the model loads in the background at startup, the status bar says
|
|
@@ -427,6 +437,20 @@ a screen and sees what you would, measured:
|
|
|
427
437
|
|
|
428
438
|
- For a web app, the model gives the command that starts the server and the page's URL: lcode
|
|
429
439
|
starts the server, waits for the page to answer, takes the shot and stops the server.
|
|
440
|
+
- **Clicks and keys first.** For something that only shows after a click, a Python Qt program can
|
|
441
|
+
take `actions` before the picture, the way you would: `double_click 300,200` (a point in the
|
|
442
|
+
window), `double_click Camera 1` (the widget showing that text), `click Save`, `right_click …`,
|
|
443
|
+
`key Escape`, `key ctrl+s`, `type some text`, `wait 2`. The result says what each step hit.
|
|
444
|
+
- **Qt's warnings come first.** Qt names many layout mistakes in what the program prints, often
|
|
445
|
+
among the program's own logs. lcode lists them at the top of the report:
|
|
446
|
+
|
|
447
|
+
```text
|
|
448
|
+
● screenshot(python camera_wall.py) actions: double_click Camera 1
|
|
449
|
+
Qt printed warnings (they often name the cause):
|
|
450
|
+
- QWidget::setLayout: Attempting to set QLayout "" on FocusedCamera "", which already has a layout
|
|
451
|
+
Steps, before the picture:
|
|
452
|
+
- double_click Camera 1: on QLabel "Camera 1 — Connected" at (207, 317)
|
|
453
|
+
```
|
|
430
454
|
- A model that can see describes the picture with the model's question in mind (see
|
|
431
455
|
[Which model looks](#images)). Without one, the measurements still come back.
|
|
432
456
|
- Shots are saved in `.lcode/screenshots/` in the project, which keeps itself out of git, so you
|
|
@@ -447,7 +471,7 @@ a screen and sees what you would, measured:
|
|
|
447
471
|
| `auto-edit` | automatic | ask, except read-only commands |
|
|
448
472
|
| `yolo` | automatic | automatic |
|
|
449
473
|
|
|
450
|
-
++shift+tab++ cycles through them in this order, `/
|
|
474
|
+
++shift+tab++ cycles through them in this order, `/permissions` sets one, and `--plan`, `--auto-edit` and
|
|
451
475
|
`--yolo` start in one.
|
|
452
476
|
|
|
453
477
|
Read-only commands run without asking: `ls`, `cat`, `head`, `tail`, `grep`, `rg`, `find`, `tree`,
|
|
@@ -99,7 +99,9 @@ def stall_note(trouble: str) -> str:
|
|
|
99
99
|
}[trouble]
|
|
100
100
|
return (
|
|
101
101
|
f"[lcode] {first} Don't deliberate at length now. Either make your next tool call, or, if you're stuck, "
|
|
102
|
-
"tell the user in a few lines what you did, what fails, and what they could do."
|
|
102
|
+
"tell the user in a few lines what you did, what fails, and what they could do. If you were about to check "
|
|
103
|
+
"something by running it, run it now instead of reasoning about it: for a window, take a screenshot (with "
|
|
104
|
+
"actions for clicks or keys)."
|
|
103
105
|
)
|
|
104
106
|
|
|
105
107
|
|
|
@@ -302,6 +304,8 @@ class Progress:
|
|
|
302
304
|
class Settings:
|
|
303
305
|
model: str # Ollama model name to call
|
|
304
306
|
context: int
|
|
307
|
+
mode: str = "code" # what lcode makes: code, image or video (lcode.modes)
|
|
308
|
+
comfyui_url: str = "http://127.0.0.1:8188"
|
|
305
309
|
num_batch: int | None = None
|
|
306
310
|
keep_alive: str = "30m"
|
|
307
311
|
think: bool = True
|
|
@@ -678,6 +682,7 @@ class Agent:
|
|
|
678
682
|
data = {
|
|
679
683
|
"cwd": str(self.workspace), # where `lcode -c` there finds it, wherever the shell moved
|
|
680
684
|
"model": self.settings.model,
|
|
685
|
+
"mode": self.settings.mode,
|
|
681
686
|
"name": self.session_name,
|
|
682
687
|
"title": self.session_title,
|
|
683
688
|
"messages": self.messages,
|
|
@@ -724,6 +729,8 @@ class Agent:
|
|
|
724
729
|
self.session_id = info.id
|
|
725
730
|
self.checkpoints.load(info.id, data.get("checkpoints") or [])
|
|
726
731
|
self.session_name = data.get("name", "")
|
|
732
|
+
if data.get("mode") in ("code", "image", "video"):
|
|
733
|
+
self.settings.mode = data["mode"] # a session belongs to the mode it was made in
|
|
727
734
|
self.session_title = data.get("title") or sessions.title_from(self.messages)
|
|
728
735
|
self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
|
|
729
736
|
self.tools.todos = list(data.get("todos") or [])
|
|
@@ -961,6 +968,12 @@ class Agent:
|
|
|
961
968
|
return []
|
|
962
969
|
return checks.unrun_scripts(project_root_or_cwd(self.cwd), list(self.tools.before), commands)
|
|
963
970
|
|
|
971
|
+
def unseen_windows(self, tools_used: set[str]) -> dict[str, str]:
|
|
972
|
+
"""Window code the model changed in this request and never looked at (only where the checks would run)."""
|
|
973
|
+
if self.allowed_tools is not None or self.planning() or self.no_changes or self.settings.checks == "off":
|
|
974
|
+
return {}
|
|
975
|
+
return checks.unseen_windows(project_root_or_cwd(self.cwd), list(self.tools.before), tools_used)
|
|
976
|
+
|
|
964
977
|
def project_check(self) -> checks.Check | None:
|
|
965
978
|
"""The project's check command, for the main agent outside plan mode and reviews (None otherwise)."""
|
|
966
979
|
if self.allowed_tools is not None or self.planning() or self.no_changes or self.settings.checks == "off":
|
|
@@ -1164,6 +1177,8 @@ class Agent:
|
|
|
1164
1177
|
stalls = 0 # replies that went round in circles or ended without a word
|
|
1165
1178
|
commands: list[str] = [] # shell commands run in this request
|
|
1166
1179
|
scripts_noted = False # the model was told about scripts it wrote and never ran
|
|
1180
|
+
tools_used: set[str] = set() # tools called in this request
|
|
1181
|
+
windows_noted = False # the model was told about windows it changed and never looked at
|
|
1167
1182
|
for step in range(self.max_steps):
|
|
1168
1183
|
if self.cancel is not None and self.cancel.is_set():
|
|
1169
1184
|
raise KeyboardInterrupt
|
|
@@ -1215,10 +1230,11 @@ class Agent:
|
|
|
1215
1230
|
continue
|
|
1216
1231
|
if not calls and (self.reply_trouble == "circles" or not (reply.get("content") or "").strip()):
|
|
1217
1232
|
stalls += 1
|
|
1218
|
-
self.messages.pop() #
|
|
1233
|
+
dropped = self.messages.pop() # no answer, and circles would only fill the context
|
|
1234
|
+
kept = self.keep_dropped(dropped)
|
|
1219
1235
|
if stalls < MAX_STALLS:
|
|
1220
1236
|
self.console.print(
|
|
1221
|
-
Text(f" ⎿ {STALLED[self.reply_trouble]}: asking for a short answer", style="yellow")
|
|
1237
|
+
Text(f" ⎿ {STALLED[self.reply_trouble]}: asking for a short answer{kept}", style="yellow")
|
|
1222
1238
|
)
|
|
1223
1239
|
self.messages.append({"role": "user", "content": stall_note(self.reply_trouble)})
|
|
1224
1240
|
continue
|
|
@@ -1234,7 +1250,7 @@ class Agent:
|
|
|
1234
1250
|
self.console.print(
|
|
1235
1251
|
Text(
|
|
1236
1252
|
"Stopped: the model's replies went round in circles or ended without an answer, twice in "
|
|
1237
|
-
"this request. Try again with a smaller step, or tell it what to do differently.",
|
|
1253
|
+
f"this request{kept}. Try again with a smaller step, or tell it what to do differently.",
|
|
1238
1254
|
style="yellow",
|
|
1239
1255
|
)
|
|
1240
1256
|
)
|
|
@@ -1253,6 +1269,14 @@ class Agent:
|
|
|
1253
1269
|
self.console.print(Text(f" ⎿ never ran {', '.join(unrun)}: asking the model to", style="yellow"))
|
|
1254
1270
|
self.messages.append({"role": "user", "content": checks.unrun_note(unrun)})
|
|
1255
1271
|
continue
|
|
1272
|
+
unseen = {} if windows_noted else self.unseen_windows(tools_used)
|
|
1273
|
+
if unseen:
|
|
1274
|
+
windows_noted = True
|
|
1275
|
+
self.console.print(
|
|
1276
|
+
Text(f" ⎿ never looked at {', '.join(unseen)}: asking the model to", style="yellow")
|
|
1277
|
+
)
|
|
1278
|
+
self.messages.append({"role": "user", "content": checks.unseen_note(unseen)})
|
|
1279
|
+
continue
|
|
1256
1280
|
check = self.project_check() if changed and not verified else None
|
|
1257
1281
|
if check is not None and check_rounds < checks.MAX_ROUNDS:
|
|
1258
1282
|
check_rounds += 1
|
|
@@ -1345,6 +1369,7 @@ class Agent:
|
|
|
1345
1369
|
)
|
|
1346
1370
|
if out_of_reach.stop:
|
|
1347
1371
|
blocked = out_of_reach
|
|
1372
|
+
tools_used.add(name)
|
|
1348
1373
|
if name == "bash":
|
|
1349
1374
|
commands.append(str(args.get("command", "")))
|
|
1350
1375
|
if name in ("edit_file", "write_file", "apply_patch") and not result.startswith(REFUSED):
|
|
@@ -1412,6 +1437,14 @@ class Agent:
|
|
|
1412
1437
|
self.turn_status = "success"
|
|
1413
1438
|
self.print_stats()
|
|
1414
1439
|
|
|
1440
|
+
def keep_dropped(self, reply: dict) -> str:
|
|
1441
|
+
"""Save a reply lcode dropped (its thinking too), so the user can see what went round in circles."""
|
|
1442
|
+
text = "\n\n".join(part for part in (reply.get("thinking"), reply.get("content")) if part)
|
|
1443
|
+
if not text.strip():
|
|
1444
|
+
return ""
|
|
1445
|
+
path = context_tools.save_output(text, "dropped reply")
|
|
1446
|
+
return f" (the dropped reply is in {path})" if path else ""
|
|
1447
|
+
|
|
1415
1448
|
def can_escalate(self) -> bool:
|
|
1416
1449
|
"""Whether a stronger model may be asked here: one is set, and this isn't a subagent or plan mode."""
|
|
1417
1450
|
model = (self.settings.escalate_model or "").strip().lower()
|
|
@@ -54,6 +54,7 @@ class Options:
|
|
|
54
54
|
model: str | None = None
|
|
55
55
|
context: int | str | None = None
|
|
56
56
|
permission_mode: str | None = None # ask, plan, auto-edit or yolo
|
|
57
|
+
mode: str | None = None # code, image or video
|
|
57
58
|
allowed_tools: list[str] | None = None # only these tools (default: all)
|
|
58
59
|
max_steps: int | None = None # model steps per request
|
|
59
60
|
think: bool | None = None
|
|
@@ -179,6 +180,8 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
179
180
|
think=cfg["think"] if options.think is None else options.think,
|
|
180
181
|
show_thinking=options.show_thinking,
|
|
181
182
|
permission_mode=options.permission_mode or cfg["permission_mode"],
|
|
183
|
+
mode=options.mode or cfg["mode"],
|
|
184
|
+
comfyui_url=cfg["comfyui_url"],
|
|
182
185
|
web=switch(options.web, cfg["web"], "on"),
|
|
183
186
|
search_backend=cfg["search_backend"],
|
|
184
187
|
searxng_url=cfg["searxng_url"],
|