ltcai 3.4.1 → 3.5.0

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.
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """Discover-and-compile every first-party Python module.
3
+
4
+ Replaces the hand-maintained ``py_compile`` enumeration in CI and
5
+ ``package.json``: walks the repository, skips vendored / virtualenv / build /
6
+ cache / generated directories, and byte-compiles everything that remains. New
7
+ modules are picked up automatically — there is nothing to update when a file is
8
+ added, so the syntax gate can never silently fall behind the codebase.
9
+
10
+ Usage::
11
+
12
+ python scripts/check_python.py # compile all discovered modules
13
+ python scripts/check_python.py --list # just print what would be compiled
14
+
15
+ Exit code is non-zero if any module fails to compile.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import py_compile
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ ROOT = Path(__file__).resolve().parent.parent
25
+
26
+ # Directory names excluded anywhere in the tree: virtualenvs, build/cache
27
+ # artifacts, generated agent output, and vendored snapshots of older releases.
28
+ EXCLUDE_DIRS = {
29
+ ".git",
30
+ ".venv",
31
+ "venv",
32
+ "env",
33
+ ".build-venv",
34
+ ".npm-cache",
35
+ "build",
36
+ "dist",
37
+ "node_modules",
38
+ "__pycache__",
39
+ ".pytest_cache",
40
+ ".mypy_cache",
41
+ ".ruff_cache",
42
+ "agent_workspace",
43
+ "outputs",
44
+ "playwright-report",
45
+ "test-results",
46
+ "ltcai.egg-info",
47
+ ".ltcai",
48
+ ".ltcai-brain",
49
+ ".ltcai-test",
50
+ # Vendored snapshot of an older packaged release — not part of the build.
51
+ "ltcai-0.3.1",
52
+ }
53
+
54
+
55
+ def iter_modules():
56
+ for path in ROOT.rglob("*.py"):
57
+ parts = path.relative_to(ROOT).parts
58
+ if any(part in EXCLUDE_DIRS for part in parts):
59
+ continue
60
+ yield path
61
+
62
+
63
+ def main(argv: list[str]) -> int:
64
+ modules = sorted(iter_modules())
65
+ if "--list" in argv:
66
+ for path in modules:
67
+ print(path.relative_to(ROOT))
68
+ return 0
69
+
70
+ failures: list[str] = []
71
+ for path in modules:
72
+ try:
73
+ py_compile.compile(str(path), doraise=True)
74
+ except py_compile.PyCompileError as exc:
75
+ failures.append(str(exc))
76
+
77
+ if failures:
78
+ print("\n".join(failures))
79
+ print(f"check:python FAILED — {len(failures)} of {len(modules)} module(s) did not compile")
80
+ return 1
81
+
82
+ print(f"check:python OK — compiled {len(modules)} modules")
83
+ return 0
84
+
85
+
86
+ if __name__ == "__main__":
87
+ raise SystemExit(main(sys.argv[1:]))
@@ -117,7 +117,7 @@
117
117
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,0.9);
118
118
  position: relative;
119
119
  z-index: 1;
120
- backdrop-filter: blur(28px);
120
+ backdrop-filter: none; /* glass removed v3.5.0 */
121
121
  }
122
122
 
123
123
  .card::before {
@@ -45,7 +45,7 @@
45
45
  padding: 22px 28px;
46
46
  border-bottom: 1px solid rgba(111,66,232,0.10);
47
47
  background: var(--sidebar);
48
- backdrop-filter: blur(20px);
48
+ backdrop-filter: none; /* glass removed v3.5.0 */
49
49
  position: sticky;
50
50
  top: 0;
51
51
  z-index: 2;
@@ -46,7 +46,7 @@
46
46
  background: var(--card);
47
47
  border-color: var(--accent-soft);
48
48
  box-shadow: 0 20px 64px rgba(102, 82, 168, 0.20), inset 0 1px 0 rgba(255,255,255,0.86);
49
- backdrop-filter: blur(26px);
49
+ backdrop-filter: none; /* glass removed v3.5.0 */
50
50
  }
51
51
 
52
52
  .lattice-ref-auth .card::before {
@@ -65,7 +65,7 @@
65
65
  color: var(--ref-ink);
66
66
  background: var(--surface);
67
67
  border-bottom: 1px solid rgba(111,66,232,0.08);
68
- backdrop-filter: blur(12px);
68
+ backdrop-filter: none; /* glass removed v3.5.0 */
69
69
  }
70
70
 
71
71
  .auth-window-brand {
@@ -712,7 +712,10 @@
712
712
  font-weight: 700;
713
713
  background: transparent;
714
714
  cursor: pointer;
715
- transition: 160ms ease;
715
+ /* Animate surface affordances, but never `color`: a color transition on
716
+ theme switch would briefly render the rail link dark-on-dark (the v226
717
+ dark-mode contrast gate caught this on slow CI). Color flips instantly. */
718
+ transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease;
716
719
  }
717
720
 
718
721
  .reference-rail a.active,
@@ -842,7 +845,7 @@
842
845
  background: var(--sidebar);
843
846
  border-bottom: 1px solid rgba(111,66,232,0.11);
844
847
  box-shadow: 0 2px 12px rgba(88,72,150,0.06);
845
- backdrop-filter: blur(14px);
848
+ backdrop-filter: none; /* glass removed v3.5.0 */
846
849
  }
847
850
 
848
851
  .lattice-ref-chat .messages-viewport {
@@ -1295,7 +1298,7 @@
1295
1298
  height: 96px;
1296
1299
  background: var(--sidebar);
1297
1300
  border-bottom: 1px solid rgba(111,66,232,0.10);
1298
- backdrop-filter: blur(14px);
1301
+ backdrop-filter: none; /* glass removed v3.5.0 */
1299
1302
  padding: 22px 34px 8px;
1300
1303
  position: sticky;
1301
1304
  }
@@ -81,8 +81,8 @@
81
81
  display: flex;
82
82
  flex-direction: column;
83
83
  min-width: 240px;
84
- backdrop-filter: blur(24px);
85
- -webkit-backdrop-filter: blur(24px);
84
+ backdrop-filter: none; /* glass removed v3.5.0 */
85
+ -webkit-backdrop-filter: none; /* glass removed v3.5.0 */
86
86
  }
87
87
 
88
88
  .sidebar-header {
@@ -342,8 +342,8 @@
342
342
  padding: 10px 22px;
343
343
  border-bottom: 1px solid rgba(111,66,232,0.10);
344
344
  background: var(--sidebar);
345
- backdrop-filter: blur(20px);
346
- -webkit-backdrop-filter: blur(20px);
345
+ backdrop-filter: none; /* glass removed v3.5.0 */
346
+ -webkit-backdrop-filter: none; /* glass removed v3.5.0 */
347
347
  position: relative;
348
348
  z-index: 50;
349
349
  }
@@ -636,7 +636,7 @@
636
636
  justify-content: space-between;
637
637
  gap: 12px;
638
638
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,0.80);
639
- backdrop-filter: blur(10px);
639
+ backdrop-filter: none; /* glass removed v3.5.0 */
640
640
  transition: all .2s;
641
641
  }
642
642
 
@@ -1323,7 +1323,7 @@
1323
1323
  box-shadow: var(--shadow), 0 0 0 1px rgba(111,66,232,0.05);
1324
1324
  position: relative;
1325
1325
  z-index: 1;
1326
- backdrop-filter: blur(24px);
1326
+ backdrop-filter: none; /* glass removed v3.5.0 */
1327
1327
  }
1328
1328
 
1329
1329
  .auth-card::before {
@@ -2754,7 +2754,7 @@
2754
2754
  .admin-panel {
2755
2755
  background: var(--card);
2756
2756
  border-left: 1px solid var(--border);
2757
- backdrop-filter: blur(24px);
2757
+ backdrop-filter: none; /* glass removed v3.5.0 */
2758
2758
  color: var(--text);
2759
2759
  }
2760
2760
  .admin-header {
@@ -4254,7 +4254,7 @@
4254
4254
  background: var(--sidebar);
4255
4255
  border-bottom: 1px solid var(--line);
4256
4256
  box-shadow: none;
4257
- backdrop-filter: blur(14px);
4257
+ backdrop-filter: none; /* glass removed v3.5.0 */
4258
4258
  }
4259
4259
 
4260
4260
  /* Messages viewport */
@@ -120,7 +120,7 @@
120
120
  z-index: 20;
121
121
  border: 1px solid var(--line);
122
122
  background: var(--panel);
123
- backdrop-filter: blur(18px);
123
+ backdrop-filter: none; /* glass removed v3.5.0 */
124
124
  box-shadow: var(--shadow);
125
125
  }
126
126
 
@@ -904,7 +904,7 @@
904
904
  border-radius: 10px;
905
905
  background: var(--surface-2);
906
906
  box-shadow: var(--shadow);
907
- backdrop-filter: blur(18px);
907
+ backdrop-filter: none; /* glass removed v3.5.0 */
908
908
  }
909
909
 
910
910
  .focus-chip span {
@@ -319,8 +319,8 @@ select {
319
319
  z-index: 99;
320
320
  display: none;
321
321
  background: rgba(15, 12, 30, 0.42);
322
- -webkit-backdrop-filter: blur(2px);
323
- backdrop-filter: blur(2px);
322
+ -webkit-backdrop-filter: none; /* glass removed v3.5.0 */
323
+ backdrop-filter: none; /* glass removed v3.5.0 */
324
324
  }
325
325
  body.sidebar-open .sidebar-overlay { display: block; }
326
326
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.4.1",
2
+ "version": "3.5.0",
3
3
  "generated_at": "deterministic",
4
4
  "entrypoints": {
5
5
  "app": "/static/v3/js/app.d086489d.js",
@@ -8,7 +8,7 @@
8
8
  "/static/v3/css/lattice.tokens.e7018963.css",
9
9
  "/static/v3/css/lattice.base.e4cdd05d.css",
10
10
  "/static/v3/css/lattice.components.9b49d614.css",
11
- "/static/v3/css/lattice.shell.6ceea7c8.css",
11
+ "/static/v3/css/lattice.shell.8fcc9d33.css",
12
12
  "/static/v3/css/lattice.views.22f69117.css"
13
13
  ]
14
14
  },
@@ -17,7 +17,7 @@
17
17
  "static/v3/css/lattice.tokens.css": "/static/v3/css/lattice.tokens.e7018963.css",
18
18
  "static/v3/css/lattice.base.css": "/static/v3/css/lattice.base.e4cdd05d.css",
19
19
  "static/v3/css/lattice.components.css": "/static/v3/css/lattice.components.9b49d614.css",
20
- "static/v3/css/lattice.shell.css": "/static/v3/css/lattice.shell.6ceea7c8.css",
20
+ "static/v3/css/lattice.shell.css": "/static/v3/css/lattice.shell.8fcc9d33.css",
21
21
  "static/v3/css/lattice.views.css": "/static/v3/css/lattice.views.22f69117.css",
22
22
  "static/v3/js/app.js": "/static/v3/js/app.d086489d.js",
23
23
  "static/v3/js/core/api.js": "/static/v3/js/core/api.12b568ad.js",
@@ -349,8 +349,9 @@
349
349
  .lt3-scrim {
350
350
  position: fixed; inset: 0;
351
351
  z-index: var(--lt3-z-scrim);
352
+ /* Solid dim scrim — no backdrop blur (glassmorphism removed in v3.5.0 for a
353
+ crisp, stable surface). */
352
354
  background: var(--overlay);
353
- backdrop-filter: blur(2px);
354
355
  opacity: 0;
355
356
  animation: lt3-fade var(--lt3-dur-2) var(--lt3-ease) forwards;
356
357
  }
@@ -349,8 +349,9 @@
349
349
  .lt3-scrim {
350
350
  position: fixed; inset: 0;
351
351
  z-index: var(--lt3-z-scrim);
352
+ /* Solid dim scrim — no backdrop blur (glassmorphism removed in v3.5.0 for a
353
+ crisp, stable surface). */
352
354
  background: var(--overlay);
353
- backdrop-filter: blur(2px);
354
355
  opacity: 0;
355
356
  animation: lt3-fade var(--lt3-dur-2) var(--lt3-ease) forwards;
356
357
  }
@@ -873,7 +873,7 @@ main {
873
873
  padding: 16px clamp(16px, 2vw, 28px);
874
874
  background: color-mix(in srgb, var(--surface-elevated) 94%, transparent);
875
875
  border-bottom: 1px solid var(--line);
876
- backdrop-filter: blur(18px);
876
+ backdrop-filter: none; /* glass removed v3.5.0 */
877
877
  }
878
878
 
879
879
  .topbar-subtitle {
@@ -0,0 +1,276 @@
1
+ """Safe local tools for Lattice AI agent mode.
2
+
3
+ All filesystem operations are confined to LATTICEAI_AGENT_ROOT, defaulting to
4
+ ./agent_workspace. Command execution runs without a shell and from inside that
5
+ workspace.
6
+
7
+ v3.5.0 splits the historical flat ``tools.py`` into focused submodules
8
+ (computer, filesystem, documents, local_files, knowledge, network, commands)
9
+ while keeping the exact import surface: this package re-exports every public name
10
+ and ``execute_tool``/``DEFAULT_TOOL_REGISTRY``, so ``import tools`` and
11
+ ``from tools import X`` behave identically to before. ``AGENT_ROOT`` and the path
12
+ helpers live here as the single source of truth (tests monkeypatch
13
+ ``tools.AGENT_ROOT``); submodules read it dynamically.
14
+ """
15
+
16
+ import base64
17
+ import json
18
+ import os
19
+ import platform
20
+ import re
21
+ import shlex
22
+ import socket
23
+ import subprocess
24
+ import tempfile
25
+ from html.parser import HTMLParser
26
+ from pathlib import Path
27
+ from typing import Any, Callable, Dict, List, Optional
28
+
29
+ from latticeai.core.tool_registry import ToolRegistry
30
+ from p_reinforce import BRAIN_DIR, STRUCTURE
31
+
32
+ _PLATFORM = platform.system() # "Darwin" | "Windows" | "Linux"
33
+
34
+
35
+ # ── base: agent-root sandbox, shared constants, path helpers ──────────────────
36
+ AGENT_ROOT = Path(os.getenv("LATTICEAI_AGENT_ROOT") or "agent_workspace").resolve()
37
+ MAX_FILE_BYTES = 512_000
38
+ MAX_COMMAND_SECONDS = 30
39
+ MAX_BUILD_SECONDS = 180
40
+ MAX_DEPLOY_SECONDS = 300
41
+ MAX_COMMAND_OUTPUT = 12_000
42
+
43
+ BLOCKED_COMMANDS = {
44
+ "rm",
45
+ "rmdir",
46
+ "sudo",
47
+ "su",
48
+ "chmod",
49
+ "chown",
50
+ "curl",
51
+ "wget",
52
+ "ssh",
53
+ "scp",
54
+ "rsync",
55
+ "dd",
56
+ "mkfs",
57
+ "diskutil",
58
+ "launchctl",
59
+ }
60
+
61
+ ALLOWED_COMMANDS = {
62
+ "pwd",
63
+ "ls",
64
+ "find",
65
+ "cat",
66
+ "sed",
67
+ "head",
68
+ "tail",
69
+ "wc",
70
+ "rg",
71
+ "python",
72
+ "python3",
73
+ "node",
74
+ "npm",
75
+ "npx",
76
+ "git",
77
+ }
78
+
79
+ BUILD_SCRIPT_NAMES = {"build", "compile", "typecheck", "test"}
80
+ DEPLOY_SCRIPT_NAMES = {
81
+ "deploy",
82
+ "preview",
83
+ "release",
84
+ "package",
85
+ "dist",
86
+ "make",
87
+ "build:installer",
88
+ "build:pkg",
89
+ "build:exe",
90
+ "package:mac",
91
+ "package:win",
92
+ }
93
+
94
+ ALLOWED_GIT_SUBCOMMANDS = {"status", "diff", "log", "show"}
95
+
96
+ TEXT_EXTENSIONS = {
97
+ ".css",
98
+ ".csv",
99
+ ".html",
100
+ ".js",
101
+ ".json",
102
+ ".jsx",
103
+ ".md",
104
+ ".py",
105
+ ".ts",
106
+ ".tsx",
107
+ ".txt",
108
+ ".xml",
109
+ ".yaml",
110
+ ".yml",
111
+ }
112
+
113
+ DOCUMENT_OUTPUT_DIR = "generated_documents"
114
+ PRESENTATION_OUTPUT_DIR = "generated_presentations"
115
+ SPREADSHEET_OUTPUT_DIR = "generated_spreadsheets"
116
+
117
+
118
+ class ToolError(ValueError):
119
+ pass
120
+
121
+
122
+ def ensure_agent_root() -> Path:
123
+ AGENT_ROOT.mkdir(parents=True, exist_ok=True)
124
+ return AGENT_ROOT
125
+
126
+
127
+ def _resolve_path(path: str = "") -> Path:
128
+ ensure_agent_root()
129
+ if not path:
130
+ return AGENT_ROOT
131
+ candidate = (AGENT_ROOT / path).resolve()
132
+ if candidate != AGENT_ROOT and AGENT_ROOT not in candidate.parents:
133
+ raise ToolError("Path escapes the agent workspace.")
134
+ return candidate
135
+
136
+
137
+ def _relative(path: Path) -> str:
138
+ return str(path.relative_to(AGENT_ROOT))
139
+
140
+
141
+ # ── document / local / read constants (shared by submodules) ──────────────────
142
+ PDF_OUTPUT_DIR = "generated_pdfs"
143
+ LOCAL_MAX_FILE_BYTES = 2_000_000 # 2 MB cap for local reads
144
+
145
+
146
+ # CJK-capable fonts (Korean + Chinese + Japanese)
147
+ _CJK_FONT_CANDIDATES = [
148
+ "/System/Library/Fonts/AppleSDGothicNeo.ttc", # Korean (macOS)
149
+ "/System/Library/Fonts/STHeiti Light.ttc", # Chinese (macOS)
150
+ "/System/Library/Fonts/PingFang.ttc", # Chinese (macOS)
151
+ "/Library/Fonts/NanumGothic.ttf", # Korean
152
+ "/usr/share/fonts/truetype/nanum/NanumGothic.ttf",
153
+ ]
154
+
155
+ _SUPPORTED_READ_EXTENSIONS = {".pdf", ".docx", ".xlsx", ".pptx", ".txt", ".md", ".csv"}
156
+ DOCUMENT_MAX_READ_BYTES = 10_000_000 # 10 MB
157
+
158
+
159
+ # ── focused tool submodules (re-exported flat for import compatibility) ───────
160
+ from tools.computer import * # noqa: E402,F401,F403
161
+ from tools.filesystem import * # noqa: E402,F401,F403
162
+ from tools.documents import * # noqa: E402,F401,F403
163
+ from tools.local_files import * # noqa: E402,F401,F403
164
+ from tools.knowledge import * # noqa: E402,F401,F403
165
+ from tools.network import * # noqa: E402,F401,F403
166
+ from tools.commands import * # noqa: E402,F401,F403
167
+
168
+
169
+ # ── tool registry: the single name → invocation source of truth ───────────────
170
+ def _h_create_xlsx(args: Dict[str, Any]) -> Dict[str, Any]:
171
+ rows = args.get("rows", [])
172
+ if isinstance(rows, str):
173
+ rows = json.loads(rows)
174
+ return create_xlsx(rows, args.get("filename", "spreadsheet.xlsx"), args.get("sheet_name", "Sheet1"))
175
+
176
+
177
+ def _h_create_pptx(args: Dict[str, Any]) -> Dict[str, Any]:
178
+ slides = args.get("slides", [])
179
+ if isinstance(slides, str):
180
+ slides = json.loads(slides)
181
+ return create_pptx(args.get("title", ""), slides, args.get("filename", "presentation.pptx"))
182
+
183
+
184
+ # ── Tool registry: the single source of truth for name → invocation ───────────
185
+ # Each entry binds the args dict to a tool function. ``execute_tool`` is a
186
+ # lookup over this table — adding a tool means adding one entry here, not
187
+ # editing an if/elif chain. server.py's governance map and catalog brief are
188
+ # checked against ``registered_tools()`` so the three never silently drift.
189
+ TOOL_HANDLERS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {
190
+ # filesystem
191
+ "list_dir": lambda a: list_dir(a.get("path", ".")),
192
+ "workspace_tree": lambda a: workspace_tree(a.get("path", "."), a.get("max_depth", 3)),
193
+ "read_file": lambda a: read_file(a["path"], offset=a.get("offset", 0), limit=a.get("limit", 0), line_numbers=a.get("line_numbers", True)),
194
+ "write_file": lambda a: write_file(a["path"], a.get("content", "")),
195
+ "edit_file": lambda a: edit_file(a["path"], a["old_string"], a["new_string"], replace_all=bool(a.get("replace_all", False))),
196
+ "grep": lambda a: grep(a["pattern"], path=a.get("path", "."), glob=a.get("glob"), max_results=a.get("max_results", 50), case_insensitive=bool(a.get("case_insensitive", False)), context_lines=a.get("context_lines", 0)),
197
+ "search_files": lambda a: search_files(a["query"], a.get("path", "."), a.get("max_results", 20)),
198
+ "inspect_html": lambda a: inspect_html(a["path"]),
199
+ "preview_url": lambda a: preview_url(a.get("path", "index.html")),
200
+ # planning
201
+ "todo_read": lambda a: todo_read(),
202
+ "todo_write": lambda a: todo_write(a.get("todos") or []),
203
+ # documents
204
+ "create_docx": lambda a: create_docx(a.get("title", ""), a.get("body", ""), a.get("filename", "document.docx")),
205
+ "create_xlsx": _h_create_xlsx,
206
+ "create_pptx": _h_create_pptx,
207
+ "create_pdf": lambda a: create_pdf(a.get("title", ""), a.get("body", ""), a.get("filename", "document.pdf")),
208
+ "create_web_project": lambda a: create_web_project(a.get("path", ""), a.get("framework", "react"), a.get("template", "vite")),
209
+ # local filesystem
210
+ "local_list": lambda a: local_list(a["path"]),
211
+ "local_read": lambda a: local_read(a["path"]),
212
+ "local_write": lambda a: local_write(a["path"], a.get("content", "")),
213
+ "read_document": lambda a: read_document(a["path"]),
214
+ "network_status": lambda a: network_status(),
215
+ # computer use
216
+ "computer_screenshot": lambda a: computer_screenshot(),
217
+ "computer_open_app": lambda a: computer_open_app(a.get("app", "Google Chrome")),
218
+ "computer_open_url": lambda a: computer_open_url(a["url"], a.get("app", "Google Chrome")),
219
+ "computer_click": lambda a: computer_click(a.get("x", 0), a.get("y", 0), a.get("button", "left"), a.get("double", False)),
220
+ "computer_type": lambda a: computer_type(a["text"], a.get("interval", 0.04)),
221
+ "computer_key": lambda a: computer_key(a["key"]),
222
+ "computer_scroll": lambda a: computer_scroll(a.get("x", 0), a.get("y", 0), a.get("direction", "down"), a.get("clicks", 3)),
223
+ "computer_move": lambda a: computer_move(a.get("x", 0), a.get("y", 0)),
224
+ "computer_drag": lambda a: computer_drag(a.get("x1", 0), a.get("y1", 0), a.get("x2", 0), a.get("y2", 0)),
225
+ "computer_status": lambda a: computer_status(),
226
+ "chrome_status": lambda a: desktop_bridge_status(),
227
+ "computer_use_status": lambda a: desktop_bridge_status(),
228
+ # knowledge / obsidian
229
+ "knowledge_save": lambda a: knowledge_save(a["content"], a.get("folder", "00_Raw"), a.get("title")),
230
+ "knowledge_search": lambda a: knowledge_search(a["query"], a.get("max_results", 5)),
231
+ "knowledge_tree": lambda a: knowledge_tree(),
232
+ "obsidian_save": lambda a: obsidian_save(a["content"], a.get("folder", "00_Raw"), a.get("title")),
233
+ "obsidian_search": lambda a: obsidian_search(a["query"], a.get("max_results", 5)),
234
+ "obsidian_tree": lambda a: obsidian_tree(),
235
+ # git (read-only)
236
+ "git_status": lambda a: git_status(a.get("cwd")),
237
+ "git_diff": lambda a: git_diff(a.get("path"), a.get("cwd")),
238
+ "git_log": lambda a: git_log(a.get("max_count", 5), a.get("cwd")),
239
+ "git_show": lambda a: git_show(a.get("revision", "HEAD"), a.get("cwd")),
240
+ # exec
241
+ "run_command": lambda a: run_command(a["command"], a.get("cwd")),
242
+ "build_project": lambda a: build_project(a.get("cwd"), a.get("script", "build")),
243
+ "deploy_project": lambda a: deploy_project(a.get("cwd"), a.get("script", "deploy")),
244
+ }
245
+
246
+
247
+ DEFAULT_TOOL_REGISTRY = ToolRegistry(TOOL_HANDLERS)
248
+
249
+
250
+ def registered_tools() -> frozenset:
251
+ """Names dispatchable through ``execute_tool`` — the seam other modules verify against."""
252
+ return DEFAULT_TOOL_REGISTRY.registered_tools()
253
+
254
+
255
+ def execute_tool(action: str, args: Dict[str, Any]) -> Dict[str, Any]:
256
+ return DEFAULT_TOOL_REGISTRY.execute(action, args, error_cls=ToolError)
257
+
258
+
259
+ __all__ = [
260
+ "AGENT_ROOT", "ToolError", "ensure_agent_root",
261
+ "list_dir", "workspace_tree", "read_file", "write_file", "edit_file", "grep",
262
+ "search_files", "inspect_html", "preview_url", "create_web_project",
263
+ "todo_read", "todo_write",
264
+ "create_docx", "create_xlsx", "create_pptx", "create_pdf", "read_document",
265
+ "local_list", "local_read", "local_write", "desktop_bridge_status",
266
+ "knowledge_save", "knowledge_search", "knowledge_tree",
267
+ "obsidian_save", "obsidian_search", "obsidian_tree",
268
+ "network_status",
269
+ "computer_screenshot", "computer_open_app", "computer_open_url",
270
+ "computer_click", "computer_type", "computer_key", "computer_scroll",
271
+ "computer_move", "computer_drag", "computer_status",
272
+ "run_command", "build_project", "deploy_project",
273
+ "git_status", "git_diff", "git_log", "git_show",
274
+ "TOOL_HANDLERS", "DEFAULT_TOOL_REGISTRY", "registered_tools", "execute_tool",
275
+ "BRAIN_DIR", "STRUCTURE",
276
+ ]