patchbai 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.
- patchbai/__init__.py +1 -0
- patchbai/__main__.py +10 -0
- patchbai/actions.py +34 -0
- patchbai/activity/__init__.py +0 -0
- patchbai/activity/log.py +237 -0
- patchbai/agents/__init__.py +0 -0
- patchbai/agents/child_tools.py +66 -0
- patchbai/agents/fake_sdk_adapter.py +45 -0
- patchbai/agents/manager.py +272 -0
- patchbai/agents/request_inbox.py +65 -0
- patchbai/agents/sdk_adapter.py +49 -0
- patchbai/agents/session.py +224 -0
- patchbai/agents/sort.py +66 -0
- patchbai/agents/state.py +80 -0
- patchbai/app.py +1288 -0
- patchbai/config.py +128 -0
- patchbai/events.py +236 -0
- patchbai/layout/__init__.py +0 -0
- patchbai/layout/custom_widgets.py +82 -0
- patchbai/layout/defaults.py +33 -0
- patchbai/layout/engine.py +241 -0
- patchbai/layout/local_widgets.py +188 -0
- patchbai/layout/registry.py +69 -0
- patchbai/layout/spec.py +104 -0
- patchbai/layout/splitter.py +170 -0
- patchbai/layout/titles.py +70 -0
- patchbai/orchestrator/__init__.py +0 -0
- patchbai/orchestrator/formatting.py +15 -0
- patchbai/orchestrator/session.py +644 -0
- patchbai/orchestrator/tabs_tools.py +149 -0
- patchbai/orchestrator/tools.py +976 -0
- patchbai/persistence/__init__.py +0 -0
- patchbai/persistence/agents_index.py +68 -0
- patchbai/persistence/atomic.py +47 -0
- patchbai/persistence/layout_store.py +25 -0
- patchbai/persistence/layouts_store.py +61 -0
- patchbai/persistence/orchestrator_sessions.py +127 -0
- patchbai/persistence/paths.py +48 -0
- patchbai/persistence/themes_store.py +44 -0
- patchbai/persistence/transcript_store.py +64 -0
- patchbai/persistence/workspace_store.py +25 -0
- patchbai/theme/__init__.py +0 -0
- patchbai/theme/engine.py +75 -0
- patchbai/theme/spec.py +31 -0
- patchbai/widgets/__init__.py +0 -0
- patchbai/widgets/_file_lang.py +36 -0
- patchbai/widgets/_terminal_keys.py +89 -0
- patchbai/widgets/_terminal_render.py +147 -0
- patchbai/widgets/activity_feed.py +365 -0
- patchbai/widgets/agent_table.py +235 -0
- patchbai/widgets/agent_transcript.py +58 -0
- patchbai/widgets/change_cwd_screen.py +39 -0
- patchbai/widgets/chrome.py +210 -0
- patchbai/widgets/diff_viewer.py +52 -0
- patchbai/widgets/file_editor.py +258 -0
- patchbai/widgets/file_tree.py +33 -0
- patchbai/widgets/file_viewer.py +77 -0
- patchbai/widgets/history_screen.py +58 -0
- patchbai/widgets/layout_switcher.py +126 -0
- patchbai/widgets/log_tail.py +113 -0
- patchbai/widgets/markdown.py +65 -0
- patchbai/widgets/new_tab_screen.py +31 -0
- patchbai/widgets/notebook.py +45 -0
- patchbai/widgets/orchestrator_chat.py +73 -0
- patchbai/widgets/resume_screen.py +179 -0
- patchbai/widgets/rich_transcript.py +606 -0
- patchbai/widgets/terminal.py +251 -0
- patchbai/widgets/theme_switcher.py +63 -0
- patchbai/widgets/transcript_screen.py +39 -0
- patchbai/workspace/__init__.py +3 -0
- patchbai/workspace/spec.py +72 -0
- patchbai-0.1.0.dist-info/METADATA +573 -0
- patchbai-0.1.0.dist-info/RECORD +76 -0
- patchbai-0.1.0.dist-info/WHEEL +4 -0
- patchbai-0.1.0.dist-info/entry_points.txt +3 -0
- patchbai-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from patchbai.layout.spec import LayoutSpec
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _ok(payload: dict | list) -> dict:
|
|
8
|
+
return {"content": [{"type": "text", "text": json.dumps(payload, indent=2)}]}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _err(message: str, **extra: Any) -> dict:
|
|
12
|
+
body = {"error": message}
|
|
13
|
+
body.update(extra)
|
|
14
|
+
return {"content": [{"type": "text", "text": json.dumps(body, indent=2)}]}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _panel_ids(node) -> list[str]:
|
|
18
|
+
from patchbai.layout.spec import Container, Panel, Tabs
|
|
19
|
+
if isinstance(node, Panel):
|
|
20
|
+
return [node.id]
|
|
21
|
+
if isinstance(node, Tabs):
|
|
22
|
+
return [c.id for c in node.children]
|
|
23
|
+
if isinstance(node, Container):
|
|
24
|
+
out: list[str] = []
|
|
25
|
+
for c in node.children:
|
|
26
|
+
out.extend(_panel_ids(c))
|
|
27
|
+
return out
|
|
28
|
+
return []
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _has_chat(node) -> bool:
|
|
32
|
+
from patchbai.workspace.spec import _contains_chat
|
|
33
|
+
return _contains_chat(node)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def add_tab_handler(app):
|
|
37
|
+
"""Build an MCP handler that delegates to app.add_tab.
|
|
38
|
+
|
|
39
|
+
args:
|
|
40
|
+
title: tab strip label (required).
|
|
41
|
+
layout: optional. Either a LayoutSpec dict, or a string naming a
|
|
42
|
+
saved layout in NamedLayoutsStore. If None, a default seed
|
|
43
|
+
is used (chat-only if the workspace has no chat yet, else
|
|
44
|
+
ActivityFeed-only).
|
|
45
|
+
activate: bool, default True.
|
|
46
|
+
"""
|
|
47
|
+
async def add_tab_tool(args: dict) -> dict:
|
|
48
|
+
title = args.get("title")
|
|
49
|
+
if not title or not isinstance(title, str):
|
|
50
|
+
return _err("`title` is required and must be a string")
|
|
51
|
+
raw_layout = args.get("layout")
|
|
52
|
+
activate = bool(args.get("activate", True))
|
|
53
|
+
try:
|
|
54
|
+
if raw_layout is None:
|
|
55
|
+
layout = app._default_seed_layout()
|
|
56
|
+
elif isinstance(raw_layout, str):
|
|
57
|
+
spec = app.layouts_store.load(raw_layout)
|
|
58
|
+
if spec is None:
|
|
59
|
+
return _err(f"named layout not found: {raw_layout}",
|
|
60
|
+
suggestion="call list_layouts to see available names")
|
|
61
|
+
layout = spec
|
|
62
|
+
elif isinstance(raw_layout, dict):
|
|
63
|
+
layout = LayoutSpec.model_validate(raw_layout)
|
|
64
|
+
else:
|
|
65
|
+
return _err("`layout` must be a dict, a string name, or omitted")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
return _err(f"invalid layout: {e}")
|
|
68
|
+
try:
|
|
69
|
+
tab_id = await app.add_tab(title, layout, activate=activate)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
return _err(f"add_tab failed: {e}")
|
|
72
|
+
return _ok({"tab_id": tab_id, "title": title, "active": activate})
|
|
73
|
+
|
|
74
|
+
return add_tab_tool
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def close_tab_handler(app):
|
|
78
|
+
async def close_tab_tool(args: dict) -> dict:
|
|
79
|
+
tab_id = args.get("tab_id")
|
|
80
|
+
if not isinstance(tab_id, str) or not tab_id:
|
|
81
|
+
return _err("`tab_id` is required and must be a string")
|
|
82
|
+
result = await app.close_tab(tab_id)
|
|
83
|
+
if "error" in result:
|
|
84
|
+
return _err(result["error"], **{k: v for k, v in result.items() if k != "error"})
|
|
85
|
+
return _ok(result)
|
|
86
|
+
|
|
87
|
+
return close_tab_tool
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def switch_tab_handler(app):
|
|
91
|
+
async def switch_tab_tool(args: dict) -> dict:
|
|
92
|
+
tab_id = args.get("tab_id")
|
|
93
|
+
if not isinstance(tab_id, str) or not tab_id:
|
|
94
|
+
return _err("`tab_id` is required and must be a string")
|
|
95
|
+
if app._workspace is None or all(t.id != tab_id for t in app._workspace.tabs):
|
|
96
|
+
return _err("unknown_tab_id")
|
|
97
|
+
from textual.widgets import TabbedContent
|
|
98
|
+
tc = app.query_one("#app-tabs", TabbedContent)
|
|
99
|
+
tc.active = f"tab-{tab_id}"
|
|
100
|
+
return _ok({"active": tab_id})
|
|
101
|
+
|
|
102
|
+
return switch_tab_tool
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def rename_tab_handler(app):
|
|
106
|
+
async def rename_tab_tool(args: dict) -> dict:
|
|
107
|
+
tab_id = args.get("tab_id")
|
|
108
|
+
title = args.get("title")
|
|
109
|
+
if not isinstance(tab_id, str) or not tab_id:
|
|
110
|
+
return _err("`tab_id` is required and must be a string")
|
|
111
|
+
if not isinstance(title, str) or not title.strip():
|
|
112
|
+
return _err("`title` is required and must be a non-empty string")
|
|
113
|
+
result = await app.rename_tab(tab_id, title)
|
|
114
|
+
if "error" in result:
|
|
115
|
+
return _err(result["error"], **{k: v for k, v in result.items() if k != "error"})
|
|
116
|
+
return _ok(result)
|
|
117
|
+
|
|
118
|
+
return rename_tab_tool
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def reorder_tabs_handler(app):
|
|
122
|
+
async def reorder_tabs_tool(args: dict) -> dict:
|
|
123
|
+
tab_ids = args.get("tab_ids")
|
|
124
|
+
if not isinstance(tab_ids, list) or not all(isinstance(x, str) for x in tab_ids):
|
|
125
|
+
return _err("`tab_ids` is required and must be a list of strings")
|
|
126
|
+
result = await app.reorder_tabs(tab_ids)
|
|
127
|
+
if "error" in result:
|
|
128
|
+
return _err(result["error"], **{k: v for k, v in result.items() if k != "error"})
|
|
129
|
+
return _ok(result)
|
|
130
|
+
|
|
131
|
+
return reorder_tabs_tool
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def list_tabs_handler(app):
|
|
135
|
+
async def list_tabs_tool(_args: dict) -> dict:
|
|
136
|
+
if app._workspace is None:
|
|
137
|
+
return _ok([])
|
|
138
|
+
out = []
|
|
139
|
+
for t in app._workspace.tabs:
|
|
140
|
+
out.append({
|
|
141
|
+
"id": t.id,
|
|
142
|
+
"title": t.title,
|
|
143
|
+
"active": (t.id == app._active_tab_id),
|
|
144
|
+
"has_chat": _has_chat(t.layout.layout),
|
|
145
|
+
"panel_ids": _panel_ids(t.layout.layout),
|
|
146
|
+
})
|
|
147
|
+
return _ok(out)
|
|
148
|
+
|
|
149
|
+
return list_tabs_tool
|