pcode 0.1.0__py3-none-any.whl
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/__init__.py +7 -0
- pcode/_job_supervisor.py +65 -0
- pcode/agent.py +592 -0
- pcode/anthropic_oauth.py +469 -0
- pcode/app.py +3165 -0
- pcode/appearance.py +181 -0
- pcode/aside.py +541 -0
- pcode/aside_guard.py +39 -0
- pcode/aside_ui.py +683 -0
- pcode/auth.py +113 -0
- pcode/block.py +39 -0
- pcode/browser.py +393 -0
- pcode/cache_diagnostics.py +336 -0
- pcode/cache_settings.py +58 -0
- pcode/cache_warnings.py +139 -0
- pcode/claude_sdk/__init__.py +96 -0
- pcode/claude_sdk/cli.py +52 -0
- pcode/claude_sdk/errors.py +61 -0
- pcode/claude_sdk/messages.py +154 -0
- pcode/claude_sdk/model.py +179 -0
- pcode/claude_sdk/resume.py +114 -0
- pcode/claude_sdk/session.py +547 -0
- pcode/claude_sdk/session_pool.py +251 -0
- pcode/claude_sdk/workspace.py +34 -0
- pcode/cli.py +73 -0
- pcode/clipboard.py +76 -0
- pcode/code_mode.py +43 -0
- pcode/codex_login.py +189 -0
- pcode/command_transcript.py +71 -0
- pcode/commands.py +145 -0
- pcode/compaction.py +457 -0
- pcode/completion.py +263 -0
- pcode/config.py +223 -0
- pcode/context_breakdown.py +178 -0
- pcode/context_usage.py +52 -0
- pcode/controller.py +3496 -0
- pcode/conversation_ids.py +27 -0
- pcode/conversation_tree.py +160 -0
- pcode/copy_ui.py +172 -0
- pcode/delegation.py +236 -0
- pcode/delta.py +295 -0
- pcode/diagnostics.py +226 -0
- pcode/edit_preview.py +158 -0
- pcode/edit_transcript.py +287 -0
- pcode/edit_ui.py +850 -0
- pcode/edits.py +162 -0
- pcode/email_remote/__init__.py +14 -0
- pcode/email_remote/cli.py +133 -0
- pcode/email_remote/listener.py +695 -0
- pcode/email_remote/mailbox.py +336 -0
- pcode/email_remote/outbound.py +75 -0
- pcode/email_remote/parsing.py +226 -0
- pcode/email_remote/render.py +175 -0
- pcode/email_remote/state.py +79 -0
- pcode/error_report.py +38 -0
- pcode/ext.py +602 -0
- pcode/extension_guide.md +311 -0
- pcode/extensions/browser.py +284 -0
- pcode/extensions/ponytail.py +217 -0
- pcode/extensions/sandbox.py +138 -0
- pcode/extensions/session_history.py +170 -0
- pcode/extensions/web_research.py +89 -0
- pcode/file_refs.py +265 -0
- pcode/filesystem.py +138 -0
- pcode/frame.py +149 -0
- pcode/gateway.py +465 -0
- pcode/git_diff.py +497 -0
- pcode/history.py +589 -0
- pcode/history_cursor.py +147 -0
- pcode/history_embeddings.py +102 -0
- pcode/host.py +941 -0
- pcode/host_protocol.py +291 -0
- pcode/host_ui.py +225 -0
- pcode/input_keys.py +35 -0
- pcode/inspection.py +289 -0
- pcode/inspector_ui.py +448 -0
- pcode/isolated_delegation.py +425 -0
- pcode/job_notices.py +54 -0
- pcode/jobs.py +584 -0
- pcode/jobs_ui.py +265 -0
- pcode/keymap.py +215 -0
- pcode/layout_speed.py +103 -0
- pcode/links.py +90 -0
- pcode/links_ui.py +200 -0
- pcode/live.py +1301 -0
- pcode/llm_proxy.py +44 -0
- pcode/logins.py +233 -0
- pcode/mcp.py +683 -0
- pcode/mcp_notice.py +91 -0
- pcode/mcp_oauth.py +381 -0
- pcode/meridian.py +233 -0
- pcode/meridian_process.py +337 -0
- pcode/meridian_reminders.py +107 -0
- pcode/meridian_setup.py +356 -0
- pcode/model_metadata.py +451 -0
- pcode/model_ui.py +168 -0
- pcode/models.py +209 -0
- pcode/native_results.py +119 -0
- pcode/oauth_pages.py +66 -0
- pcode/output_limits.py +41 -0
- pcode/paste.py +45 -0
- pcode/path_completion.py +66 -0
- pcode/plan_preview.py +131 -0
- pcode/plan_sizes.py +35 -0
- pcode/planning.py +161 -0
- pcode/popup_ui.py +873 -0
- pcode/preferences.py +1176 -0
- pcode/prefix_keys.py +490 -0
- pcode/profile_benchmark.py +332 -0
- pcode/profiling.py +566 -0
- pcode/project_trust.py +94 -0
- pcode/prompt_keys.py +286 -0
- pcode/remote.py +717 -0
- pcode/remote_print.py +125 -0
- pcode/remote_profile.py +237 -0
- pcode/replay_benchmark.py +319 -0
- pcode/repo_context.py +158 -0
- pcode/retries.py +28 -0
- pcode/rpc.py +280 -0
- pcode/runtime.py +244 -0
- pcode/sandbox.py +454 -0
- pcode/session_naming.py +82 -0
- pcode/session_ui.py +550 -0
- pcode/sessions.py +892 -0
- pcode/shell.py +113 -0
- pcode/shell_mode.py +172 -0
- pcode/shell_tools.py +459 -0
- pcode/skills.py +161 -0
- pcode/stall_log.py +187 -0
- pcode/startup_input.py +96 -0
- pcode/steering.py +27 -0
- pcode/stream_display.py +188 -0
- pcode/stream_events.py +437 -0
- pcode/strict_tools.py +130 -0
- pcode/syntax.py +43 -0
- pcode/syntax_colors.py +206 -0
- pcode/task_prompt.py +28 -0
- pcode/task_worktrees.py +482 -0
- pcode/terminal_notify.py +383 -0
- pcode/theme.py +135 -0
- pcode/theme_gallery.py +88 -0
- pcode/thinking_markdown.py +41 -0
- pcode/token_accounting.py +67 -0
- pcode/tool_display.py +784 -0
- pcode/tool_output_limits.py +108 -0
- pcode/tool_panel.py +377 -0
- pcode/transcript_log.py +127 -0
- pcode/transcript_notice.py +103 -0
- pcode/tree_ui.py +288 -0
- pcode/turn.py +44 -0
- pcode/ui.py +3745 -0
- pcode/usage.py +293 -0
- pcode/word_wrap.py +107 -0
- pcode/worker_ui.py +235 -0
- pcode/workers.py +136 -0
- pcode/workspace.py +57 -0
- pcode/workspace_filesystem.py +72 -0
- pcode/worktree.py +650 -0
- pcode-0.1.0.dist-info/METADATA +145 -0
- pcode-0.1.0.dist-info/RECORD +163 -0
- pcode-0.1.0.dist-info/WHEEL +4 -0
- pcode-0.1.0.dist-info/entry_points.txt +3 -0
- pcode-0.1.0.dist-info/licenses/LICENSE +21 -0
pcode/plan_sizes.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Plan step sizes and the progress they weight.
|
|
2
|
+
|
|
3
|
+
Kept free of Pydantic AI so the terminal's tab bar can import it at startup.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
Size = Literal["S", "M", "L"]
|
|
10
|
+
|
|
11
|
+
# Relative effort, not time: models compare sizes far better than they guess
|
|
12
|
+
# durations, and three buckets leave nothing to agonise over. Unsized steps
|
|
13
|
+
# count as M. Roughly geometric, so one L outweighs a few quick steps.
|
|
14
|
+
SIZE_WEIGHTS: dict[str, int] = {"S": 1, "M": 3, "L": 8}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def plan_progress(items: Sequence[Mapping]) -> float | None:
|
|
18
|
+
"""How far through the plan the work is, 0..1, weighted by step size.
|
|
19
|
+
|
|
20
|
+
Cancelled steps leave the total, so a dropped step never strands the bar
|
|
21
|
+
short of full, and the running step counts half: the bar moves when work
|
|
22
|
+
starts rather than only when it lands. None when nothing counts.
|
|
23
|
+
"""
|
|
24
|
+
total = done = 0.0
|
|
25
|
+
for item in items:
|
|
26
|
+
status = item.get("status")
|
|
27
|
+
if status == "cancelled":
|
|
28
|
+
continue
|
|
29
|
+
weight = SIZE_WEIGHTS.get(item.get("size") or "M", SIZE_WEIGHTS["M"])
|
|
30
|
+
total += weight
|
|
31
|
+
if status == "completed":
|
|
32
|
+
done += weight
|
|
33
|
+
elif status == "in_progress":
|
|
34
|
+
done += weight / 2
|
|
35
|
+
return done / total if total else None
|
pcode/planning.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Harness planning, tuned for a plan the user watches as a live checklist."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic_ai import CapabilityEvent, RunContext
|
|
7
|
+
from pydantic_ai_harness.planning import PlanItem, Planning, PlanningToolset
|
|
8
|
+
|
|
9
|
+
from pcode.plan_sizes import Size
|
|
10
|
+
from pcode.tool_display import PLAN_TOOLS
|
|
11
|
+
|
|
12
|
+
# Replace Harness's "multi-step work" threshold with default use for visible
|
|
13
|
+
# progress, including small tasks and investigations. Say to create the plan
|
|
14
|
+
# early: maintenance rules alone leave creating one optional.
|
|
15
|
+
GUIDANCE = (
|
|
16
|
+
"Use `write_plan` by default when working on a request. The plan is the user's "
|
|
17
|
+
"live checklist of what you are doing, what is done, and what is left—not just "
|
|
18
|
+
"a tool for organizing complex work. Small tasks and investigations count, even "
|
|
19
|
+
"if the plan has only one step. Create it early and revise it as you learn; "
|
|
20
|
+
"you do not need to know the whole solution first. "
|
|
21
|
+
"Keep it truthful: one step `in_progress` while work is underway, "
|
|
22
|
+
"and when a step finishes, one `update_task_statuses` call that completes it "
|
|
23
|
+
"and starts the next. Use `add_task` for a step you discover midway; "
|
|
24
|
+
"use `write_plan` only to create or restructure the plan, and pass the full "
|
|
25
|
+
"plan when you do. Before your final reply, every step should be `completed` or "
|
|
26
|
+
"`cancelled`, unless you are stopping to ask the user; then leave the rest as it is. "
|
|
27
|
+
"Give a step `size` L when it holds most of the work and S when it is trivial; the "
|
|
28
|
+
"user's progress bar is weighted by it."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
ID_GUIDANCE = (
|
|
32
|
+
" Plan row numbers are display positions, not task IDs. For granular updates, use "
|
|
33
|
+
"the exact stable IDs returned by write_plan or read_plan; never guess IDs from row "
|
|
34
|
+
"numbers. Preserve those IDs when rewriting a plan."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Harness's description tells the model to call write_plan "first for multi-step
|
|
38
|
+
# work, then again as you start and finish steps", which repeats the narrow
|
|
39
|
+
# trigger GUIDANCE drops and steers routine progress away from the cheaper
|
|
40
|
+
# status tools.
|
|
41
|
+
WRITE_PLAN_DESCRIPTION = (
|
|
42
|
+
"Create or replace the entire plan. Pass the whole ordered list every time -- "
|
|
43
|
+
"including steps that are unchanged, completed, or cancelled -- so there are no "
|
|
44
|
+
"indices to track. Use it to create the plan or restructure it; report routine "
|
|
45
|
+
"progress with `update_task_statuses` instead. Keep one step `in_progress` while "
|
|
46
|
+
"work is underway."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
SIZE_DESCRIPTION = (
|
|
51
|
+
"Relative effort: S (a quick check or one-line change), M (typical; the default), "
|
|
52
|
+
"L (the bulk of the work, e.g. the main implementation or a long test run). "
|
|
53
|
+
"Omit for M."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SizedPlanItem(PlanItem):
|
|
58
|
+
"""One step in the plan.
|
|
59
|
+
|
|
60
|
+
`id` is auto-generated when omitted, so the model can create a plan without
|
|
61
|
+
ids and reference existing steps by id when restructuring. `size` weights
|
|
62
|
+
the user's progress bar.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
size: Size | None = Field(default=None, description=SIZE_DESCRIPTION)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SizedPlanningToolset(PlanningToolset):
|
|
69
|
+
"""Harness's plan tools, with `size` accepted wherever a step is created."""
|
|
70
|
+
|
|
71
|
+
async def write_plan(self, ctx: RunContext, items: list[SizedPlanItem]) -> str: # type: ignore[override]
|
|
72
|
+
"""Create or replace the whole plan.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
ctx: Framework-provided run context.
|
|
76
|
+
items: The complete ordered list of plan steps.
|
|
77
|
+
"""
|
|
78
|
+
return await super().write_plan(ctx, list(items))
|
|
79
|
+
|
|
80
|
+
async def add_task( # type: ignore[override]
|
|
81
|
+
self,
|
|
82
|
+
ctx: RunContext,
|
|
83
|
+
content: str,
|
|
84
|
+
active_form: str = "",
|
|
85
|
+
size: Size | None = None,
|
|
86
|
+
) -> str:
|
|
87
|
+
"""Add one new pending step.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
ctx: Framework-provided run context.
|
|
91
|
+
content: The step description in imperative form.
|
|
92
|
+
active_form: Optional present-continuous label, e.g. "Fix bug" -> "Fixing bug".
|
|
93
|
+
size: Relative effort: S (a quick check or one-line change), M (typical; the
|
|
94
|
+
default), L (the bulk of the work, e.g. the main implementation or a long
|
|
95
|
+
test run). Omit for M.
|
|
96
|
+
"""
|
|
97
|
+
store = self._resolve(ctx)
|
|
98
|
+
before = await store.get_items()
|
|
99
|
+
item = await store.add_item(
|
|
100
|
+
SizedPlanItem(content=content, active_form=active_form, size=size)
|
|
101
|
+
)
|
|
102
|
+
await self._emit_changes(ctx, before, await store.get_items())
|
|
103
|
+
return f"Added step '{content}' with id: {item.id}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(kw_only=True)
|
|
107
|
+
class PlanSnapshot(CapabilityEvent, namespace="pcode_planning", name="snapshot"):
|
|
108
|
+
"""The whole plan after a planning call, announced on the run's own event stream.
|
|
109
|
+
|
|
110
|
+
A sub-agent's plan lives in a store private to its run. Announcing it lets
|
|
111
|
+
the parent's display show it without reaching into, or replacing, that store.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
items: list[dict]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class IdentifiedPlanning(Planning):
|
|
119
|
+
"""Keep upstream validation/storage; change what the model is told about the plan.
|
|
120
|
+
|
|
121
|
+
GUIDANCE names the full core toolset. A caller narrowing `tools` or enabling
|
|
122
|
+
subtasks should pass its own `guidance`, as upstream's `tools` docs already advise.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
# Tool results already report changes; read_plan retrieves state when needed.
|
|
126
|
+
inject: bool = False
|
|
127
|
+
|
|
128
|
+
def __post_init__(self):
|
|
129
|
+
# An explicit `descriptions` still wins per tool.
|
|
130
|
+
self.descriptions = {"write_plan": WRITE_PLAN_DESCRIPTION, **(self.descriptions or {})}
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_spec(cls, *, inject: bool = False, **kwargs):
|
|
134
|
+
return super().from_spec(inject=inject, **kwargs)
|
|
135
|
+
|
|
136
|
+
def get_toolset(self):
|
|
137
|
+
return SizedPlanningToolset(self)
|
|
138
|
+
|
|
139
|
+
def get_instructions(self):
|
|
140
|
+
# As upstream: None means the default, "" drops it. The ID note always stays.
|
|
141
|
+
guidance = GUIDANCE if self.guidance is None else self.guidance
|
|
142
|
+
return (guidance + ID_GUIDANCE).strip()
|
|
143
|
+
|
|
144
|
+
async def after_tool_execute(self, ctx, *, call, tool_def, args, result):
|
|
145
|
+
result = await super().after_tool_execute(
|
|
146
|
+
ctx, call=call, tool_def=tool_def, args=args, result=result
|
|
147
|
+
)
|
|
148
|
+
if call.tool_name in PLAN_TOOLS:
|
|
149
|
+
items = await self.resolve_store(ctx).get_items()
|
|
150
|
+
await ctx.emit(PlanSnapshot(items=[item.model_dump(mode="json") for item in items]))
|
|
151
|
+
if (
|
|
152
|
+
call.tool_name == "write_plan"
|
|
153
|
+
and isinstance(result, str)
|
|
154
|
+
and result.startswith("Plan updated:")
|
|
155
|
+
):
|
|
156
|
+
items = await self.resolve_store(ctx).get_items()
|
|
157
|
+
if items:
|
|
158
|
+
result += "\n\nStable task IDs (not row numbers):\n" + "\n".join(
|
|
159
|
+
f"{index}. task_id={item.id}" for index, item in enumerate(items, 1)
|
|
160
|
+
)
|
|
161
|
+
return result
|