pcli-agent 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.
- pcli/__init__.py +1 -0
- pcli/__main__.py +4 -0
- pcli/agent/__init__.py +0 -0
- pcli/agent/activity.py +116 -0
- pcli/agent/compaction.py +205 -0
- pcli/agent/context_pruning.py +88 -0
- pcli/agent/headless.py +209 -0
- pcli/agent/loop.py +442 -0
- pcli/agent/prompt.py +371 -0
- pcli/agent/runtime.py +240 -0
- pcli/browser/__init__.py +0 -0
- pcli/browser/session.py +135 -0
- pcli/cli.py +757 -0
- pcli/config/__init__.py +0 -0
- pcli/config/paths.py +95 -0
- pcli/config/settings.py +435 -0
- pcli/cost/__init__.py +0 -0
- pcli/cost/context.py +275 -0
- pcli/cost/context_detect.py +183 -0
- pcli/cost/pricing_table.py +141 -0
- pcli/cost/tracker.py +126 -0
- pcli/llm/__init__.py +0 -0
- pcli/llm/client.py +285 -0
- pcli/llm/errors.py +37 -0
- pcli/llm/models.py +100 -0
- pcli/llm/streaming.py +108 -0
- pcli/memory/__init__.py +0 -0
- pcli/memory/extraction.py +106 -0
- pcli/memory/models.py +103 -0
- pcli/memory/store.py +88 -0
- pcli/permissions/__init__.py +0 -0
- pcli/permissions/guardrails.py +219 -0
- pcli/permissions/manager.py +215 -0
- pcli/permissions/policy.py +70 -0
- pcli/sandbox/__init__.py +0 -0
- pcli/sandbox/base.py +50 -0
- pcli/sandbox/docker_backend.py +107 -0
- pcli/sandbox/limits.py +63 -0
- pcli/sandbox/null_backend.py +92 -0
- pcli/sandbox/selector.py +75 -0
- pcli/sandbox/subprocess_backend.py +376 -0
- pcli/scheduler/__init__.py +0 -0
- pcli/scheduler/daemon.py +194 -0
- pcli/scheduler/models.py +97 -0
- pcli/scheduler/runner.py +84 -0
- pcli/scheduler/store.py +75 -0
- pcli/scheduler/triggers.py +84 -0
- pcli/session/__init__.py +0 -0
- pcli/session/audit.py +122 -0
- pcli/session/directory_check.py +28 -0
- pcli/session/export.py +57 -0
- pcli/session/importer.py +92 -0
- pcli/session/models.py +168 -0
- pcli/session/store.py +127 -0
- pcli/telegram/__init__.py +0 -0
- pcli/telegram/bot.py +266 -0
- pcli/telegram/daemon.py +1197 -0
- pcli/telegram/permissions.py +131 -0
- pcli/telegram/sender.py +58 -0
- pcli/tools/__init__.py +0 -0
- pcli/tools/_nested_agent.py +204 -0
- pcli/tools/agent_tools.py +264 -0
- pcli/tools/agent_tools_store.py +69 -0
- pcli/tools/artifacts.py +47 -0
- pcli/tools/base.py +185 -0
- pcli/tools/builtin/__init__.py +0 -0
- pcli/tools/builtin/agent_tool_register_tool.py +100 -0
- pcli/tools/builtin/artifact_tool.py +212 -0
- pcli/tools/builtin/ask_tool.py +77 -0
- pcli/tools/builtin/browser_tool.py +253 -0
- pcli/tools/builtin/decision_tool.py +73 -0
- pcli/tools/builtin/describe_tool.py +389 -0
- pcli/tools/builtin/diff_tools.py +225 -0
- pcli/tools/builtin/fs_tools.py +371 -0
- pcli/tools/builtin/grep_tool.py +88 -0
- pcli/tools/builtin/memory_tool.py +108 -0
- pcli/tools/builtin/network_tools.py +107 -0
- pcli/tools/builtin/pip_tool.py +106 -0
- pcli/tools/builtin/shell_tool.py +240 -0
- pcli/tools/builtin/subagent_tool.py +146 -0
- pcli/tools/builtin/todo_tool.py +122 -0
- pcli/tools/builtin/toolbox_register_tool.py +76 -0
- pcli/tools/builtin/web_tools.py +322 -0
- pcli/tools/pydiscovery/__init__.py +0 -0
- pcli/tools/pydiscovery/cache.py +51 -0
- pcli/tools/pydiscovery/index.py +48 -0
- pcli/tools/pydiscovery/invoke.py +181 -0
- pcli/tools/pydiscovery/search.py +117 -0
- pcli/tools/registry.py +138 -0
- pcli/tools/toolbox/__init__.py +0 -0
- pcli/tools/toolbox/introspect.py +48 -0
- pcli/tools/toolbox/manager.py +336 -0
- pcli/tools/toolbox/plugin_base.py +51 -0
- pcli/tools/toolbox/plugins/__init__.py +6 -0
- pcli/tools/toolbox/plugins/httpd.py +99 -0
- pcli/tools/toolbox/plugins/kafka.py +162 -0
- pcli/tools/toolbox/plugins/kubectl.py +211 -0
- pcli/tools/toolbox/plugins/sge.py +146 -0
- pcli/tools/toolbox/store.py +65 -0
- pcli/tools/toolbox/synthesize.py +100 -0
- pcli/tui/__init__.py +0 -0
- pcli/tui/app.py +37 -0
- pcli/tui/screens/__init__.py +0 -0
- pcli/tui/screens/ask_question_modal.py +54 -0
- pcli/tui/screens/chat.py +2070 -0
- pcli/tui/screens/confirm_modal.py +39 -0
- pcli/tui/screens/models.py +43 -0
- pcli/tui/screens/permission_modal.py +71 -0
- pcli/tui/screens/sessions.py +162 -0
- pcli/tui/screens/subagent_activity_modal.py +71 -0
- pcli/tui/shell_passthrough.py +56 -0
- pcli/tui/styles/pcli.tcss +241 -0
- pcli/tui/themes.py +84 -0
- pcli/tui/widgets/__init__.py +0 -0
- pcli/tui/widgets/chat_input.py +240 -0
- pcli/tui/widgets/command_suggestions.py +33 -0
- pcli/tui/widgets/message_view.py +328 -0
- pcli/tui/widgets/paste_input.py +99 -0
- pcli/tui/widgets/paste_marker.py +69 -0
- pcli/tui/widgets/status_bar.py +133 -0
- pcli/tui/widgets/status_pane.py +58 -0
- pcli/util/__init__.py +0 -0
- pcli/util/ids.py +15 -0
- pcli/util/logging.py +18 -0
- pcli/util/text.py +10 -0
- pcli_agent-0.1.0.dist-info/METADATA +259 -0
- pcli_agent-0.1.0.dist-info/RECORD +130 -0
- pcli_agent-0.1.0.dist-info/WHEEL +4 -0
- pcli_agent-0.1.0.dist-info/entry_points.txt +2 -0
- pcli_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
pcli/browser/session.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""BrowserSession: a lazily-launched Playwright browser, wrapped for the
|
|
2
|
+
browser_* tools (tools/builtin/browser_tool.py).
|
|
3
|
+
|
|
4
|
+
Playwright (`pip install -e ".[browser]"`, then a one-time `playwright
|
|
5
|
+
install chromium`) is an optional dependency - this module must import
|
|
6
|
+
cleanly even when it isn't installed, so the actual `import playwright...`
|
|
7
|
+
only happens inside _ensure_page (first real use), not at module or
|
|
8
|
+
BrowserSession-construction time. A BrowserSession is cheap to construct
|
|
9
|
+
and hold onto even when nothing ever calls into it (see agent/runtime.py,
|
|
10
|
+
which always attaches one to AgentRuntime) - the browser process itself is
|
|
11
|
+
only started the first time a browser_* tool is actually called.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
from pcli.config.paths import browser_profiles_dir
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
# Optional dependency (the "browser" extra) - not installed in a plain
|
|
23
|
+
# dev environment, so mypy can't resolve this even though it's
|
|
24
|
+
# type-checking-only and never actually imported at runtime unless a
|
|
25
|
+
# browser_* tool is actually called (see _ensure_page below).
|
|
26
|
+
from playwright.async_api import Page # type: ignore[import-not-found]
|
|
27
|
+
|
|
28
|
+
_INSTALL_HINT = (
|
|
29
|
+
'Browser tools need the optional "browser" extra: run `pip install -e ".[browser]"`, then '
|
|
30
|
+
"`playwright install chromium` once to download the browser itself."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BrowserUnavailableError(Exception):
|
|
35
|
+
"""Raised when a browser_* tool is used but Playwright isn't installed,
|
|
36
|
+
or Chromium hasn't been downloaded yet (`playwright install chromium`)."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BrowserSession:
|
|
40
|
+
"""Wraps one Playwright persistent BrowserContext and its single active
|
|
41
|
+
Page. One instance is shared for the life of an AgentRuntime (agent/
|
|
42
|
+
runtime.py) and threaded through every ToolContext built from it, so
|
|
43
|
+
every browser_* call in a session reuses the same browser
|
|
44
|
+
process/tab - navigating then clicking then reading stays in the same
|
|
45
|
+
login/cookie state, the way a person's own browser session would. The
|
|
46
|
+
profile directory (browser_profiles_dir) persists that state further,
|
|
47
|
+
across separate pcli invocations too (e.g. separate scheduled `pcli
|
|
48
|
+
run` runs), not just within one running session."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, *, profile: str = "default", headless: bool = False) -> None:
|
|
51
|
+
self._profile = profile
|
|
52
|
+
self._headless = headless
|
|
53
|
+
self._playwright: Any | None = None
|
|
54
|
+
self._context: Any | None = None
|
|
55
|
+
self._page: Page | None = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def is_started(self) -> bool:
|
|
59
|
+
return self._page is not None
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def headless(self) -> bool:
|
|
63
|
+
return self._headless
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def current_url(self) -> str | None:
|
|
67
|
+
return self._page.url if self._page is not None else None
|
|
68
|
+
|
|
69
|
+
async def _ensure_page(self) -> Page:
|
|
70
|
+
if self._page is not None:
|
|
71
|
+
return self._page
|
|
72
|
+
try:
|
|
73
|
+
from playwright.async_api import async_playwright
|
|
74
|
+
except ImportError as exc:
|
|
75
|
+
raise BrowserUnavailableError(_INSTALL_HINT) from exc
|
|
76
|
+
|
|
77
|
+
self._playwright = await async_playwright().start()
|
|
78
|
+
try:
|
|
79
|
+
self._context = await self._playwright.chromium.launch_persistent_context(
|
|
80
|
+
str(browser_profiles_dir(self._profile)),
|
|
81
|
+
headless=self._headless,
|
|
82
|
+
)
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
await self._playwright.stop()
|
|
85
|
+
self._playwright = None
|
|
86
|
+
if "Executable doesn't exist" in str(exc):
|
|
87
|
+
raise BrowserUnavailableError(
|
|
88
|
+
"Chromium isn't downloaded yet - run `playwright install chromium` once."
|
|
89
|
+
) from exc
|
|
90
|
+
raise
|
|
91
|
+
self._page = self._context.pages[0] if self._context.pages else await self._context.new_page()
|
|
92
|
+
return self._page
|
|
93
|
+
|
|
94
|
+
async def close(self) -> None:
|
|
95
|
+
"""Safe to call even if the browser was never started (nothing to
|
|
96
|
+
close) - mirrors kill_all_background_jobs' own no-op-when-unused
|
|
97
|
+
shape (sandbox/subprocess_backend.py), called the same way from
|
|
98
|
+
ChatScreen.on_unmount / `pcli run`'s finally block."""
|
|
99
|
+
if self._context is not None:
|
|
100
|
+
await self._context.close()
|
|
101
|
+
self._context = None
|
|
102
|
+
self._page = None
|
|
103
|
+
if self._playwright is not None:
|
|
104
|
+
await self._playwright.stop()
|
|
105
|
+
self._playwright = None
|
|
106
|
+
|
|
107
|
+
async def navigate(self, url: str) -> str:
|
|
108
|
+
page = await self._ensure_page()
|
|
109
|
+
await page.goto(url, wait_until="load")
|
|
110
|
+
return page.url
|
|
111
|
+
|
|
112
|
+
async def click(self, selector: str) -> None:
|
|
113
|
+
page = await self._ensure_page()
|
|
114
|
+
await page.locator(selector).first.click()
|
|
115
|
+
|
|
116
|
+
async def type_text(self, selector: str, text: str) -> None:
|
|
117
|
+
page = await self._ensure_page()
|
|
118
|
+
await page.locator(selector).first.fill(text)
|
|
119
|
+
|
|
120
|
+
async def press_key(self, key: str) -> None:
|
|
121
|
+
page = await self._ensure_page()
|
|
122
|
+
await page.keyboard.press(key)
|
|
123
|
+
|
|
124
|
+
async def wait_for(self, selector: str, timeout_s: float) -> None:
|
|
125
|
+
page = await self._ensure_page()
|
|
126
|
+
await page.locator(selector).first.wait_for(timeout=timeout_s * 1000)
|
|
127
|
+
|
|
128
|
+
async def read_page(self) -> str:
|
|
129
|
+
page = await self._ensure_page()
|
|
130
|
+
text: str = await page.inner_text("body")
|
|
131
|
+
return text
|
|
132
|
+
|
|
133
|
+
async def screenshot(self, path: Path) -> None:
|
|
134
|
+
page = await self._ensure_page()
|
|
135
|
+
await page.screenshot(path=str(path))
|