pcode 0.1.2__tar.gz → 0.1.4__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.
- {pcode-0.1.2 → pcode-0.1.4}/PKG-INFO +9 -10
- {pcode-0.1.2 → pcode-0.1.4}/README.md +3 -3
- {pcode-0.1.2 → pcode-0.1.4}/pyproject.toml +5 -6
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/agent.py +7 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/anthropic_oauth.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/app.py +55 -38
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/appearance.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/aside_ui.py +18 -27
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/auth.py +1 -1
- pcode-0.1.4/src/pcode/background_delegation.py +318 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/browser.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/cache_diagnostics.py +3 -3
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/cache_warnings.py +18 -25
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/errors.py +2 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/messages.py +22 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/model.py +10 -6
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/resume.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/session.py +135 -9
- pcode-0.1.4/src/pcode/claude_sdk/web.py +85 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/cli.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/clipboard.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/config.py +27 -3
- pcode-0.1.4/src/pcode/config_ui.py +452 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/controller.py +10 -3
- pcode-0.1.4/src/pcode/copy_ui.py +394 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/delegation.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/delta.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/diagnostics.py +15 -4
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/edit_preview.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/mailbox.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/parsing.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/ext.py +6 -4
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/extension_guide.md +3 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/extensions/browser.py +1 -1
- pcode-0.1.4/src/pcode/extensions/plan_mode.py +152 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/extensions/ponytail.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/extensions/web_research.py +11 -7
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/file_refs.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/host.py +2 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/host_protocol.py +2 -2
- pcode-0.1.4/src/pcode/input_keys.py +272 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/inspection.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/inspector_ui.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/jobs.py +5 -5
- pcode-0.1.4/src/pcode/keyboard_protocol.py +104 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/keymap.py +46 -15
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/live.py +53 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/llm_proxy.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/mcp.py +3 -3
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/mcp_oauth.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/meridian.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/meridian_process.py +5 -5
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/meridian_setup.py +6 -6
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/model_metadata.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/plan_preview.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/preferences.py +17 -5
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/prefix_keys.py +37 -6
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/proctitle.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/profiling.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/project_trust.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/prompt_keys.py +21 -4
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/remote.py +4 -4
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/replay_benchmark.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/repo_context.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/sessions.py +4 -4
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/stall_log.py +52 -4
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/steering.py +11 -6
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/stream_events.py +54 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/task_worktrees.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/terminal_notify.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/theme.py +1 -1
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/tool_display.py +7 -6
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/turn.py +2 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/ui.py +50 -43
- pcode-0.1.4/src/pcode/update_check.py +156 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/usage.py +2 -2
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/worktree.py +1 -1
- pcode-0.1.2/src/pcode/copy_ui.py +0 -172
- pcode-0.1.2/src/pcode/input_keys.py +0 -35
- {pcode-0.1.2 → pcode-0.1.4}/.gitignore +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/LICENSE +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/__init__.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/_job_supervisor.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/aside.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/aside_guard.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/block.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/cache_settings.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/__init__.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/cli.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/session_pool.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/claude_sdk/workspace.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/code_mode.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/codex_login.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/command_transcript.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/commands.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/compaction.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/completion.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/context_breakdown.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/context_usage.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/conversation_ids.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/conversation_tree.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/edit_transcript.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/edit_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/edits.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/__init__.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/cli.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/listener.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/outbound.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/render.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/email_remote/state.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/error_report.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/extensions/sandbox.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/extensions/session_history.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/filesystem.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/frame.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/gateway.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/git_diff.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/history.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/history_cursor.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/history_embeddings.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/host_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/isolated_delegation.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/job_notices.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/jobs_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/layout_speed.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/links.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/links_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/logins.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/mcp_notice.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/meridian_reminders.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/model_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/models.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/native_results.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/oauth_pages.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/output_limits.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/paste.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/path_completion.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/plan_sizes.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/planning.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/popup_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/profile_benchmark.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/remote_print.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/remote_profile.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/retries.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/rpc.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/runtime.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/sandbox.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/session_naming.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/session_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/shell.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/shell_mode.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/shell_tools.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/skills.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/startup_input.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/stream_display.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/strict_tools.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/syntax.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/syntax_colors.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/task_prompt.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/terminal_text.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/theme_gallery.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/thinking_markdown.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/token_accounting.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/tool_output_limits.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/tool_panel.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/transcript_log.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/transcript_notice.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/tree_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/word_wrap.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/worker_ui.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/workers.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/workspace.py +0 -0
- {pcode-0.1.2 → pcode-0.1.4}/src/pcode/workspace_filesystem.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pcode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Terminal-native AI coding agent with background jobs, parallel worktrees, forkable sessions, and multi-model support
|
|
5
5
|
Project-URL: Homepage, https://github.com/cruxwell/pcode
|
|
6
6
|
Project-URL: Documentation, https://cruxwell.github.io/pcode/
|
|
@@ -16,18 +16,17 @@ Classifier: Intended Audience :: Developers
|
|
|
16
16
|
Classifier: Operating System :: MacOS
|
|
17
17
|
Classifier: Operating System :: POSIX :: Linux
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
22
21
|
Classifier: Topic :: Software Development
|
|
23
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.14
|
|
24
23
|
Requires-Dist: filelock<5,>=4
|
|
25
24
|
Requires-Dist: genai-prices<0.2,>=0.1.7
|
|
26
25
|
Requires-Dist: httpx2<3,>=2.13.1
|
|
27
26
|
Requires-Dist: prompt-toolkit==3.0.53
|
|
28
27
|
Requires-Dist: psutil<8,>=6
|
|
29
|
-
Requires-Dist: pydantic-ai-harness[codemode,coder,exa,playwright]<0.
|
|
30
|
-
Requires-Dist: pydantic-ai-slim[anthropic,bedrock,duckduckgo,google,groq,mcp,openai,web-fetch,xai]<3,>=2.
|
|
28
|
+
Requires-Dist: pydantic-ai-harness[codemode,coder,exa,playwright]<0.56,>=0.55.0
|
|
29
|
+
Requires-Dist: pydantic-ai-slim[anthropic,bedrock,duckduckgo,google,groq,mcp,openai,web-fetch,xai]<3,>=2.55.0
|
|
31
30
|
Requires-Dist: rich<16,>=15
|
|
32
31
|
Requires-Dist: websockets<17,>=15.0.1
|
|
33
32
|
Requires-Dist: yappi<2,>=1.7.6
|
|
@@ -35,9 +34,9 @@ Provides-Extra: claude
|
|
|
35
34
|
Requires-Dist: claude-agent-sdk<0.3,>=0.2.164; extra == 'claude'
|
|
36
35
|
Description-Content-Type: text/markdown
|
|
37
36
|
|
|
38
|
-
<img width="1392" height="1522" alt="pcode in a dark terminal" src="docs/assets/screenshots/
|
|
37
|
+
<img width="1392" height="1522" alt="pcode in a dark terminal" src="docs/assets/screenshots/tabs-dark.png" />
|
|
39
38
|
|
|
40
|
-
<img width="1392" height="1522" alt="pcode in a light terminal" src="docs/assets/screenshots/
|
|
39
|
+
<img width="1392" height="1522" alt="pcode in a light terminal" src="docs/assets/screenshots/tabs-light.png" />
|
|
41
40
|
|
|
42
41
|
# pcode
|
|
43
42
|
|
|
@@ -77,7 +76,7 @@ brew install cruxwell/pcode/pcode
|
|
|
77
76
|
Or from [PyPI](https://pypi.org/project/pcode/) with [uv](https://docs.astral.sh/uv/):
|
|
78
77
|
|
|
79
78
|
```sh
|
|
80
|
-
uv tool install 'pcode[claude]'
|
|
79
|
+
uv tool install --python 3.14 'pcode[claude]'
|
|
81
80
|
```
|
|
82
81
|
|
|
83
82
|
`brew install --HEAD` tracks `master` instead of the latest release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<img width="1392" height="1522" alt="pcode in a dark terminal" src="docs/assets/screenshots/
|
|
1
|
+
<img width="1392" height="1522" alt="pcode in a dark terminal" src="docs/assets/screenshots/tabs-dark.png" />
|
|
2
2
|
|
|
3
|
-
<img width="1392" height="1522" alt="pcode in a light terminal" src="docs/assets/screenshots/
|
|
3
|
+
<img width="1392" height="1522" alt="pcode in a light terminal" src="docs/assets/screenshots/tabs-light.png" />
|
|
4
4
|
|
|
5
5
|
# pcode
|
|
6
6
|
|
|
@@ -40,7 +40,7 @@ brew install cruxwell/pcode/pcode
|
|
|
40
40
|
Or from [PyPI](https://pypi.org/project/pcode/) with [uv](https://docs.astral.sh/uv/):
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
uv tool install 'pcode[claude]'
|
|
43
|
+
uv tool install --python 3.14 'pcode[claude]'
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
`brew install --HEAD` tracks `master` instead of the latest release.
|
|
@@ -14,12 +14,11 @@ classifiers = [
|
|
|
14
14
|
"Operating System :: MacOS",
|
|
15
15
|
"Operating System :: POSIX :: Linux",
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
|
-
"Programming Language :: Python :: 3.
|
|
18
|
-
"Programming Language :: Python :: 3.
|
|
19
|
-
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Programming Language :: Python :: 3.15",
|
|
20
19
|
"Topic :: Software Development",
|
|
21
20
|
]
|
|
22
|
-
requires-python = ">=3.
|
|
21
|
+
requires-python = ">=3.14"
|
|
23
22
|
dependencies = [
|
|
24
23
|
"filelock>=4,<5",
|
|
25
24
|
"genai-prices>=0.1.7,<0.2",
|
|
@@ -34,11 +33,11 @@ dependencies = [
|
|
|
34
33
|
# itself is downloaded on first use, not at install.
|
|
35
34
|
# Harness ships from the pydantic-ai repo with every Pydantic AI release and
|
|
36
35
|
# pins pydantic-ai-slim to the same minor; bump both together.
|
|
37
|
-
"pydantic-ai-harness[exa,coder,codemode,playwright]>=0.
|
|
36
|
+
"pydantic-ai-harness[exa,coder,codemode,playwright]>=0.55.0,<0.56",
|
|
38
37
|
# duckduckgo and web-fetch back the bundled web_research extension when no
|
|
39
38
|
# provider-native search or Exa key is available.
|
|
40
39
|
# google covers both google and google-cloud providers; bedrock pulls boto3.
|
|
41
|
-
"pydantic-ai-slim[anthropic,bedrock,duckduckgo,google,groq,mcp,openai,web-fetch,xai]>=2.
|
|
40
|
+
"pydantic-ai-slim[anthropic,bedrock,duckduckgo,google,groq,mcp,openai,web-fetch,xai]>=2.55.0,<3",
|
|
42
41
|
"rich>=15,<16",
|
|
43
42
|
# FastMCP 4.x OAuth callback server selects Uvicorn's websockets-sansio protocol.
|
|
44
43
|
# Capped below 17 by google-genai (still <17 as of 2.29), not by pcode.
|
|
@@ -32,6 +32,7 @@ from pydantic_ai_harness.subagents import ModelOption, SubAgent
|
|
|
32
32
|
from pydantic_ai_harness.tool_output_limits import ToolOutputLimits
|
|
33
33
|
|
|
34
34
|
from pcode import remote_profile
|
|
35
|
+
from pcode.background_delegation import BackgroundDelegation
|
|
35
36
|
from pcode.cache_settings import ProviderCacheSettings, model_settings
|
|
36
37
|
from pcode.cache_warnings import CacheBustReporting
|
|
37
38
|
from pcode.claude_sdk import ClaudeWorkspace
|
|
@@ -359,8 +360,10 @@ def create_coder(
|
|
|
359
360
|
"""
|
|
360
361
|
workspace = workspace.resolve()
|
|
361
362
|
_ensure_bundled_rg()
|
|
362
|
-
# Delegation is pcode's own `WorkspaceSubAgents`, added below.
|
|
363
|
-
|
|
363
|
+
# Delegation is pcode's own `WorkspaceSubAgents`, added below. Caching is
|
|
364
|
+
# `ProviderCacheSettings`' per-provider choice: Coder's default `cache=True`
|
|
365
|
+
# would also reach Meridian and OpenAI's explicit-breakpoint models.
|
|
366
|
+
coder = Coder(sub_agents=False, caching=False)
|
|
364
367
|
output_limits = create_tool_output_limits()
|
|
365
368
|
# Keep Coder's tool selection, including its persistent shell (as a
|
|
366
369
|
# `JobShell`). File display and repository discovery remain local adapters;
|
|
@@ -402,6 +405,8 @@ def create_coder(
|
|
|
402
405
|
capabilities.append(
|
|
403
406
|
_delegation(workspace, worker, subagents, extensions, output_limits, cache_notices)
|
|
404
407
|
)
|
|
408
|
+
# Steering mid-delegation reaches the parent; the child keeps working.
|
|
409
|
+
capabilities.append(BackgroundDelegation())
|
|
405
410
|
# Summarize evidence before discarding it; pcode owns compaction.
|
|
406
411
|
return CombinedCapability([c for c in capabilities if not isinstance(c, ClearToolResults)])
|
|
407
412
|
|
|
@@ -403,7 +403,7 @@ async def _receive_code(port: int, state: str, timeout: float) -> str:
|
|
|
403
403
|
+ body
|
|
404
404
|
)
|
|
405
405
|
await writer.drain()
|
|
406
|
-
except
|
|
406
|
+
except OSError, asyncio.TimeoutError, UnicodeError:
|
|
407
407
|
code = ""
|
|
408
408
|
finally:
|
|
409
409
|
writer.close()
|
|
@@ -24,8 +24,13 @@ from pcode.cli import ask, restore_stdin
|
|
|
24
24
|
from pcode.commands import Command, CommandRegistry
|
|
25
25
|
from pcode.completion import SHELLS as COMPLETION_SHELLS
|
|
26
26
|
from pcode.completion import complete_with
|
|
27
|
+
from pcode.config import (
|
|
28
|
+
IMMEDIATE_SETTINGS,
|
|
29
|
+
config_argument_descriptions,
|
|
30
|
+
config_arguments,
|
|
31
|
+
configure,
|
|
32
|
+
)
|
|
27
33
|
from pcode.config import USAGE as CONFIG_USAGE
|
|
28
|
-
from pcode.config import config_argument_descriptions, config_arguments, configure
|
|
29
34
|
from pcode.controller import (
|
|
30
35
|
MODEL_COMMANDS,
|
|
31
36
|
TERMINAL_COMMANDS,
|
|
@@ -234,6 +239,7 @@ class PreviewApp:
|
|
|
234
239
|
self.reviewed_checkpoint = None
|
|
235
240
|
self.links_requested = False
|
|
236
241
|
self.copy_requested = False
|
|
242
|
+
self.config_requested = False
|
|
237
243
|
# Unsaved conversations have no journal to re-read, so keep their changes.
|
|
238
244
|
self.edits: list[EditCompleted] = []
|
|
239
245
|
self.session_requested = False
|
|
@@ -260,7 +266,7 @@ class PreviewApp:
|
|
|
260
266
|
),
|
|
261
267
|
Command(
|
|
262
268
|
"/config",
|
|
263
|
-
"
|
|
269
|
+
"Browse saved settings, or use list / diff / get KEY / set KEY VALUE / unset KEY",
|
|
264
270
|
self.config,
|
|
265
271
|
free_arguments=True,
|
|
266
272
|
argument_provider=config_arguments,
|
|
@@ -310,7 +316,7 @@ class PreviewApp:
|
|
|
310
316
|
),
|
|
311
317
|
Command(
|
|
312
318
|
"/copy",
|
|
313
|
-
"
|
|
319
|
+
"Choose a response to copy, or a quote or code block from it",
|
|
314
320
|
self.copy,
|
|
315
321
|
group="Inspect",
|
|
316
322
|
),
|
|
@@ -727,7 +733,29 @@ class PreviewApp:
|
|
|
727
733
|
return bindings.complete(argument) if bindings is not None else ()
|
|
728
734
|
|
|
729
735
|
def config(self, argument: str) -> None:
|
|
730
|
-
|
|
736
|
+
if not argument.strip():
|
|
737
|
+
self.config_requested = True
|
|
738
|
+
return
|
|
739
|
+
self.transcript.note(self.apply_config(shlex.split(argument)))
|
|
740
|
+
|
|
741
|
+
async def browse_config(self, output: TerminalOutput, session) -> None:
|
|
742
|
+
from pcode.config_ui import ConfigBrowser
|
|
743
|
+
|
|
744
|
+
self.config_requested = False
|
|
745
|
+
try:
|
|
746
|
+
async with self.popup(output, session) as modal_input:
|
|
747
|
+
browser = ConfigBrowser(
|
|
748
|
+
save=self.apply_config,
|
|
749
|
+
input=modal_input,
|
|
750
|
+
output=session.app.output,
|
|
751
|
+
style=session.app.style,
|
|
752
|
+
)
|
|
753
|
+
await browser.run()
|
|
754
|
+
except (ValueError, OSError) as error:
|
|
755
|
+
self.transcript.warning(f"Could not open configuration: {error}")
|
|
756
|
+
|
|
757
|
+
def apply_config(self, args: list[str]) -> str:
|
|
758
|
+
"""One write and immediate-layout path for commands and the browser."""
|
|
731
759
|
try:
|
|
732
760
|
result = configure(args)
|
|
733
761
|
except OSError as error:
|
|
@@ -751,25 +779,9 @@ class PreviewApp:
|
|
|
751
779
|
if self.transcript.output is not None:
|
|
752
780
|
self.transcript.output.app.invalidate()
|
|
753
781
|
edits = args[1:] if args[:1] == ["project"] else args
|
|
754
|
-
if (
|
|
755
|
-
len(edits) >= 2
|
|
756
|
-
and edits[0] in ("set", "unset")
|
|
757
|
-
and edits[1]
|
|
758
|
-
in (
|
|
759
|
-
"attach_tasks",
|
|
760
|
-
"tasks_max_height",
|
|
761
|
-
"tasks_min_rows",
|
|
762
|
-
"tasks_min_columns",
|
|
763
|
-
"task_style",
|
|
764
|
-
"tool_glyphs",
|
|
765
|
-
"tool_max_lines",
|
|
766
|
-
"tool_linger_seconds",
|
|
767
|
-
"show_hints",
|
|
768
|
-
"live_edits",
|
|
769
|
-
)
|
|
770
|
-
):
|
|
782
|
+
if len(edits) >= 2 and edits[0] in ("set", "unset") and edits[1] in IMMEDIATE_SETTINGS:
|
|
771
783
|
result = result.replace("Applies on next launch.", "Layout settings apply immediately.")
|
|
772
|
-
|
|
784
|
+
return result
|
|
773
785
|
|
|
774
786
|
def set_show_tasks(self, shown: bool) -> None:
|
|
775
787
|
self.activity.show_tasks = shown
|
|
@@ -825,7 +837,8 @@ class PreviewApp:
|
|
|
825
837
|
if self.transcript.output is not None:
|
|
826
838
|
self.transcript.output.app.invalidate()
|
|
827
839
|
self.transcript.flash(
|
|
828
|
-
f"Show edits: {'on' if shown else 'off'}. Usage: /show-edits [on|off]"
|
|
840
|
+
f"Show edits: {'on' if shown else 'off'}. Usage: /show-edits [on|off] "
|
|
841
|
+
f"({self.shortcut('v')})"
|
|
829
842
|
)
|
|
830
843
|
|
|
831
844
|
def group_tools(self, argument: str) -> None:
|
|
@@ -836,7 +849,8 @@ class PreviewApp:
|
|
|
836
849
|
if self.transcript.output is not None:
|
|
837
850
|
self.transcript.output.app.invalidate()
|
|
838
851
|
self.transcript.flash(
|
|
839
|
-
f"Group tools: {'on' if grouped else 'off'}. Usage: /group-tools [on|off]"
|
|
852
|
+
f"Group tools: {'on' if grouped else 'off'}. Usage: /group-tools [on|off] "
|
|
853
|
+
f"({self.shortcut(']')})"
|
|
840
854
|
)
|
|
841
855
|
|
|
842
856
|
def set_thinking_mode(self, mode: str) -> None:
|
|
@@ -924,7 +938,7 @@ class PreviewApp:
|
|
|
924
938
|
def persist_defaults(self, **updates: str) -> None:
|
|
925
939
|
try:
|
|
926
940
|
save_preferences(**updates)
|
|
927
|
-
except
|
|
941
|
+
except OSError, ValueError:
|
|
928
942
|
self.transcript.warning("Could not save defaults; this selection applies only here.")
|
|
929
943
|
|
|
930
944
|
def forget_defaults(self, *keys: str) -> None:
|
|
@@ -932,7 +946,7 @@ class PreviewApp:
|
|
|
932
946
|
|
|
933
947
|
try:
|
|
934
948
|
update_preferences({}, remove=keys)
|
|
935
|
-
except
|
|
949
|
+
except OSError, ValueError:
|
|
936
950
|
self.transcript.warning("Could not update defaults; this change applies only here.")
|
|
937
951
|
|
|
938
952
|
@asynccontextmanager
|
|
@@ -1056,21 +1070,20 @@ class PreviewApp:
|
|
|
1056
1070
|
|
|
1057
1071
|
async def choose_copy(self, output: TerminalOutput, session) -> None:
|
|
1058
1072
|
from pcode.clipboard import copy as copy_to_clipboard
|
|
1059
|
-
from pcode.copy_ui import
|
|
1060
|
-
from pcode.session_ui import literal
|
|
1073
|
+
from pcode.copy_ui import answers, copy_dialog, snippets
|
|
1061
1074
|
|
|
1062
1075
|
self.copy_requested = False
|
|
1063
1076
|
tree = getattr(self.runtime, "tree", None)
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
if not choices:
|
|
1077
|
+
history = answers(tree) if tree is not None else []
|
|
1078
|
+
if not history:
|
|
1067
1079
|
self.transcript.note("No response to copy yet.")
|
|
1068
1080
|
return
|
|
1081
|
+
choices = snippets(history[0].text)
|
|
1069
1082
|
choice = choices[0]
|
|
1070
|
-
if len(choices) > 1:
|
|
1083
|
+
if len(history) > 1 or len(choices) > 1:
|
|
1071
1084
|
async with self.popup(output, session) as modal_input:
|
|
1072
|
-
dialog =
|
|
1073
|
-
|
|
1085
|
+
dialog = copy_dialog(
|
|
1086
|
+
history,
|
|
1074
1087
|
input=modal_input,
|
|
1075
1088
|
output=session.app.output,
|
|
1076
1089
|
style=session.app.style,
|
|
@@ -1941,7 +1954,7 @@ class PreviewApp:
|
|
|
1941
1954
|
timeout=1,
|
|
1942
1955
|
)
|
|
1943
1956
|
self.branch = plain(result.stdout.strip(), limit=None) if result.returncode == 0 else ""
|
|
1944
|
-
except
|
|
1957
|
+
except OSError, subprocess.TimeoutExpired:
|
|
1945
1958
|
self.branch = ""
|
|
1946
1959
|
return self.branch != previous
|
|
1947
1960
|
|
|
@@ -2063,6 +2076,8 @@ class PreviewApp:
|
|
|
2063
2076
|
self._command_popup_generation = tag
|
|
2064
2077
|
try:
|
|
2065
2078
|
self.handle(text)
|
|
2079
|
+
if self.config_requested:
|
|
2080
|
+
await self.browse_config(output, session)
|
|
2066
2081
|
if self.worker_view_requested:
|
|
2067
2082
|
await self.read_workers(output, session)
|
|
2068
2083
|
if self.tree_requested:
|
|
@@ -2168,6 +2183,7 @@ class PreviewApp:
|
|
|
2168
2183
|
self.activity.busy = False
|
|
2169
2184
|
else:
|
|
2170
2185
|
session.app.create_background_task(refresh_metadata())
|
|
2186
|
+
session.app.create_background_task(controller.warn_outdated())
|
|
2171
2187
|
controller.start_mcp_defaults()
|
|
2172
2188
|
finally:
|
|
2173
2189
|
self._startup_pending = False
|
|
@@ -2251,7 +2267,7 @@ class PreviewApp:
|
|
|
2251
2267
|
self.transcript.note(f"Cleared {prompts} queued message(s).")
|
|
2252
2268
|
self.activity.busy = False
|
|
2253
2269
|
|
|
2254
|
-
def submit(text):
|
|
2270
|
+
def submit(text, *, mode_override: str | None = None):
|
|
2255
2271
|
text = text.strip()
|
|
2256
2272
|
ready = early is None and getattr(self.controller, "ready", None)
|
|
2257
2273
|
if (early is not None or (ready and not ready.is_set())) and text in {"/quit", "/exit"}:
|
|
@@ -2273,7 +2289,7 @@ class PreviewApp:
|
|
|
2273
2289
|
mode = "shell"
|
|
2274
2290
|
elif text:
|
|
2275
2291
|
# One send consumes a Ctrl+S pick; the saved default returns.
|
|
2276
|
-
mode = self.next_send_mode
|
|
2292
|
+
mode = mode_override or self.next_send_mode
|
|
2277
2293
|
self.send_mode_once = None
|
|
2278
2294
|
else:
|
|
2279
2295
|
return
|
|
@@ -2290,6 +2306,7 @@ class PreviewApp:
|
|
|
2290
2306
|
transcript=self.transcript,
|
|
2291
2307
|
workspace=lambda: self.workspace,
|
|
2292
2308
|
on_submit=submit,
|
|
2309
|
+
on_interrupt_submit=lambda text: submit(text, mode_override="interrupt"),
|
|
2293
2310
|
on_command=submit,
|
|
2294
2311
|
on_cancel=cancel,
|
|
2295
2312
|
on_tasks=self.set_show_tasks,
|
|
@@ -2319,7 +2336,7 @@ class PreviewApp:
|
|
|
2319
2336
|
)
|
|
2320
2337
|
try:
|
|
2321
2338
|
terminal = session.app.output.fileno()
|
|
2322
|
-
except
|
|
2339
|
+
except NotImplementedError, OSError, ValueError:
|
|
2323
2340
|
terminal = None
|
|
2324
2341
|
if terminal is not None:
|
|
2325
2342
|
mode = load_preferences().get("terminal_progress", "auto")
|
|
@@ -88,7 +88,7 @@ def _gnome_read() -> str | None:
|
|
|
88
88
|
text=True,
|
|
89
89
|
timeout=2,
|
|
90
90
|
)
|
|
91
|
-
except
|
|
91
|
+
except OSError, subprocess.SubprocessError:
|
|
92
92
|
return None
|
|
93
93
|
if result.returncode != 0:
|
|
94
94
|
return None
|
|
@@ -102,7 +102,7 @@ def desktop_reader():
|
|
|
102
102
|
try:
|
|
103
103
|
if sys.platform == "darwin":
|
|
104
104
|
return _macos_reader()
|
|
105
|
-
except
|
|
105
|
+
except OSError, AttributeError, ValueError:
|
|
106
106
|
return None
|
|
107
107
|
if sys.platform.startswith("linux") and shutil.which("gsettings"):
|
|
108
108
|
if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
|
|
@@ -33,7 +33,7 @@ from rich.theme import Theme
|
|
|
33
33
|
|
|
34
34
|
from pcode.aside import Aside, Asides, Bridge
|
|
35
35
|
from pcode.clipboard import copy as copy_to_clipboard
|
|
36
|
-
from pcode.copy_ui import
|
|
36
|
+
from pcode.copy_ui import Answer, CopyPicker, Snippet, snippets
|
|
37
37
|
from pcode.frame import Frame
|
|
38
38
|
from pcode.links import Link, extract_links, open_link, remember_link
|
|
39
39
|
from pcode.links_ui import LinkPicker
|
|
@@ -113,7 +113,7 @@ class AsideBrowser:
|
|
|
113
113
|
returning a `Bridge` from `run`; `check_bridge(thread)` raises `ValueError`
|
|
114
114
|
when it cannot yet.
|
|
115
115
|
|
|
116
|
-
Prefix `y`
|
|
116
|
+
Prefix `y` chooses an answer to copy and prefix `o` opens a link from the
|
|
117
117
|
thread, with the same pickers as `/copy` and `/links`, over the viewer.
|
|
118
118
|
The editor takes each action as a slash command too: see `commands`.
|
|
119
119
|
|
|
@@ -169,7 +169,7 @@ class AsideBrowser:
|
|
|
169
169
|
self._ids: list[str] = []
|
|
170
170
|
self.notice = ""
|
|
171
171
|
# The copy or link picker over the viewer, while one is open.
|
|
172
|
-
self.picker:
|
|
172
|
+
self.picker: CopyPicker | LinkPicker | None = None
|
|
173
173
|
self.list = TextArea(read_only=True, wrap_lines=False, scrollbar=True)
|
|
174
174
|
self.list.window.cursorline = Always()
|
|
175
175
|
self.detail = RichPane(theme=rich_theme, color_system=color_system)
|
|
@@ -305,7 +305,7 @@ class AsideBrowser:
|
|
|
305
305
|
)
|
|
306
306
|
picker = Frame(
|
|
307
307
|
DynamicContainer(lambda: self.picker.container if self.picker else HSplit([])),
|
|
308
|
-
title=lambda:
|
|
308
|
+
title=lambda: self.picker.title if isinstance(self.picker, CopyPicker) else "Links",
|
|
309
309
|
)
|
|
310
310
|
overlaid = FloatContainer(
|
|
311
311
|
root_container,
|
|
@@ -386,16 +386,8 @@ class AsideBrowser:
|
|
|
386
386
|
|
|
387
387
|
def help(self) -> list[tuple[str, str]]:
|
|
388
388
|
"""Describe the active picker, editor, or reader, including its current focus."""
|
|
389
|
-
if isinstance(self.picker, LinkPicker):
|
|
390
|
-
return self.picker.help()
|
|
391
389
|
if self.picker is not None:
|
|
392
|
-
return
|
|
393
|
-
("↑/↓", "Select snippet"),
|
|
394
|
-
("PgUp/PgDn", "Page"),
|
|
395
|
-
("Ctrl+U/D", "Half page"),
|
|
396
|
-
("Enter", "Copy selected snippet"),
|
|
397
|
-
("Esc/Ctrl+C", "Cancel"),
|
|
398
|
-
]
|
|
390
|
+
return self.picker.help()
|
|
399
391
|
interrupt = ("Ctrl+C", "Stop running answers" if self.asides.running else "Close")
|
|
400
392
|
if self.editing():
|
|
401
393
|
prompting = self.input.prompting
|
|
@@ -444,7 +436,7 @@ class AsideBrowser:
|
|
|
444
436
|
self.finish(Bridge(self.selected, kind, focus))
|
|
445
437
|
|
|
446
438
|
commands = [
|
|
447
|
-
PopupCommand("/copy", "
|
|
439
|
+
PopupCommand("/copy", "Choose an answer to copy", lambda _: self.copy(self.app.output)),
|
|
448
440
|
PopupCommand("/links", "Open a link from this thread", lambda _: self.choose_link()),
|
|
449
441
|
]
|
|
450
442
|
if self.check_bridge is not None:
|
|
@@ -550,28 +542,27 @@ class AsideBrowser:
|
|
|
550
542
|
self.refresh()
|
|
551
543
|
|
|
552
544
|
def copy(self, output=None) -> None:
|
|
553
|
-
"""
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
answered = [answer for answer in answered if answer]
|
|
545
|
+
"""Choose an answer from this thread, newest first, including partial text."""
|
|
546
|
+
answered = [
|
|
547
|
+
Answer(aside.question, aside.answer)
|
|
548
|
+
for aside in reversed(self.current())
|
|
549
|
+
if aside.answer.strip()
|
|
550
|
+
]
|
|
551
|
+
answered = [answer for answer in answered if answer.text.strip()]
|
|
560
552
|
if not answered:
|
|
561
553
|
self.notice = "No answer to copy"
|
|
562
554
|
return
|
|
563
|
-
|
|
564
|
-
if len(choices) > 1:
|
|
555
|
+
if len(answered) > 1 or len(snippets(answered[0].text)) > 1:
|
|
565
556
|
self.open_picker(
|
|
566
|
-
lambda close:
|
|
567
|
-
|
|
557
|
+
lambda close: CopyPicker(
|
|
558
|
+
answered,
|
|
568
559
|
on_pick=lambda snippet: self.copy_snippet(close, snippet, output),
|
|
569
560
|
on_cancel=close,
|
|
570
561
|
shortcuts=self.prefix_keys,
|
|
571
562
|
)
|
|
572
563
|
)
|
|
573
564
|
return
|
|
574
|
-
self.copy_text("answer", answered[
|
|
565
|
+
self.copy_text("answer", answered[0].text, output)
|
|
575
566
|
|
|
576
567
|
def copy_snippet(self, close, snippet: Snippet, output=None) -> None:
|
|
577
568
|
close()
|
|
@@ -631,7 +622,7 @@ class AsideBrowser:
|
|
|
631
622
|
self.app.layout.focus(previous)
|
|
632
623
|
|
|
633
624
|
self.picker = build(close)
|
|
634
|
-
#
|
|
625
|
+
# Searchable pickers open in their search line, so typing filters at once.
|
|
635
626
|
focus_overlay(self.app, getattr(self.picker, "query", None) or self.picker.list)
|
|
636
627
|
|
|
637
628
|
def details(self, thread: list[Aside]) -> tuple[list, int]:
|