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.
Files changed (76) hide show
  1. patchbai/__init__.py +1 -0
  2. patchbai/__main__.py +10 -0
  3. patchbai/actions.py +34 -0
  4. patchbai/activity/__init__.py +0 -0
  5. patchbai/activity/log.py +237 -0
  6. patchbai/agents/__init__.py +0 -0
  7. patchbai/agents/child_tools.py +66 -0
  8. patchbai/agents/fake_sdk_adapter.py +45 -0
  9. patchbai/agents/manager.py +272 -0
  10. patchbai/agents/request_inbox.py +65 -0
  11. patchbai/agents/sdk_adapter.py +49 -0
  12. patchbai/agents/session.py +224 -0
  13. patchbai/agents/sort.py +66 -0
  14. patchbai/agents/state.py +80 -0
  15. patchbai/app.py +1288 -0
  16. patchbai/config.py +128 -0
  17. patchbai/events.py +236 -0
  18. patchbai/layout/__init__.py +0 -0
  19. patchbai/layout/custom_widgets.py +82 -0
  20. patchbai/layout/defaults.py +33 -0
  21. patchbai/layout/engine.py +241 -0
  22. patchbai/layout/local_widgets.py +188 -0
  23. patchbai/layout/registry.py +69 -0
  24. patchbai/layout/spec.py +104 -0
  25. patchbai/layout/splitter.py +170 -0
  26. patchbai/layout/titles.py +70 -0
  27. patchbai/orchestrator/__init__.py +0 -0
  28. patchbai/orchestrator/formatting.py +15 -0
  29. patchbai/orchestrator/session.py +644 -0
  30. patchbai/orchestrator/tabs_tools.py +149 -0
  31. patchbai/orchestrator/tools.py +976 -0
  32. patchbai/persistence/__init__.py +0 -0
  33. patchbai/persistence/agents_index.py +68 -0
  34. patchbai/persistence/atomic.py +47 -0
  35. patchbai/persistence/layout_store.py +25 -0
  36. patchbai/persistence/layouts_store.py +61 -0
  37. patchbai/persistence/orchestrator_sessions.py +127 -0
  38. patchbai/persistence/paths.py +48 -0
  39. patchbai/persistence/themes_store.py +44 -0
  40. patchbai/persistence/transcript_store.py +64 -0
  41. patchbai/persistence/workspace_store.py +25 -0
  42. patchbai/theme/__init__.py +0 -0
  43. patchbai/theme/engine.py +75 -0
  44. patchbai/theme/spec.py +31 -0
  45. patchbai/widgets/__init__.py +0 -0
  46. patchbai/widgets/_file_lang.py +36 -0
  47. patchbai/widgets/_terminal_keys.py +89 -0
  48. patchbai/widgets/_terminal_render.py +147 -0
  49. patchbai/widgets/activity_feed.py +365 -0
  50. patchbai/widgets/agent_table.py +235 -0
  51. patchbai/widgets/agent_transcript.py +58 -0
  52. patchbai/widgets/change_cwd_screen.py +39 -0
  53. patchbai/widgets/chrome.py +210 -0
  54. patchbai/widgets/diff_viewer.py +52 -0
  55. patchbai/widgets/file_editor.py +258 -0
  56. patchbai/widgets/file_tree.py +33 -0
  57. patchbai/widgets/file_viewer.py +77 -0
  58. patchbai/widgets/history_screen.py +58 -0
  59. patchbai/widgets/layout_switcher.py +126 -0
  60. patchbai/widgets/log_tail.py +113 -0
  61. patchbai/widgets/markdown.py +65 -0
  62. patchbai/widgets/new_tab_screen.py +31 -0
  63. patchbai/widgets/notebook.py +45 -0
  64. patchbai/widgets/orchestrator_chat.py +73 -0
  65. patchbai/widgets/resume_screen.py +179 -0
  66. patchbai/widgets/rich_transcript.py +606 -0
  67. patchbai/widgets/terminal.py +251 -0
  68. patchbai/widgets/theme_switcher.py +63 -0
  69. patchbai/widgets/transcript_screen.py +39 -0
  70. patchbai/workspace/__init__.py +3 -0
  71. patchbai/workspace/spec.py +72 -0
  72. patchbai-0.1.0.dist-info/METADATA +573 -0
  73. patchbai-0.1.0.dist-info/RECORD +76 -0
  74. patchbai-0.1.0.dist-info/WHEEL +4 -0
  75. patchbai-0.1.0.dist-info/entry_points.txt +3 -0
  76. 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