ltcai 0.5.1 → 1.0.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.
- package/README.md +29 -12
- package/docs/CHANGELOG.md +75 -0
- package/knowledge_graph.py +33 -0
- package/latticeai/__init__.py +3 -1
- package/latticeai/core/agent.py +2 -2
- package/latticeai/core/agent_prompts.py +101 -0
- package/latticeai/core/tool_registry.py +288 -0
- package/latticeai/core/workspace_os.py +1178 -0
- package/latticeai/server_app.py +6405 -0
- package/package.json +6 -3
- package/server.py +13 -6259
- package/static/admin.html +1 -0
- package/static/graph.html +1 -0
- package/static/manifest.json +2 -2
- package/static/scripts/chat.js +4 -2
- package/static/scripts/graph.js +3 -3
- package/static/scripts/workspace.js +382 -0
- package/static/sw.js +5 -1
- package/static/workspace.css +515 -0
- package/static/workspace.html +199 -0
- package/tools.py +6 -5
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<img src="https://raw.githubusercontent.com/TaeSooPark-PTS/LatticeAI/main/docs/images/logo.svg" alt="Lattice AI" width="280"/>
|
|
3
3
|
<br/>
|
|
4
|
-
<strong>
|
|
4
|
+
<strong>AI Workspace OS for local-first graph, memory, agents, workflows, skills, and timelines.</strong>
|
|
5
5
|
<br/><br/>
|
|
6
6
|
|
|
7
7
|
[](https://pypi.org/project/ltcai/)
|
|
@@ -24,18 +24,33 @@
|
|
|
24
24
|
|
|
25
25
|
Most AI tools answer one chat at a time. They do not remember your folders, your project history, your previous decisions, or how your files relate to each other.
|
|
26
26
|
|
|
27
|
-
**Lattice AI turns your local workspace into an AI
|
|
27
|
+
**Lattice AI turns your local workspace into an AI Workspace OS.**
|
|
28
28
|
|
|
29
|
-
It reads approved local folders, indexes chats and documents, builds a searchable knowledge graph, and
|
|
29
|
+
It reads approved local folders, indexes chats and documents, builds a searchable knowledge graph, and connects the graph to snapshots, personal memory, agent runs, workflow history, skills, and an auditable timeline.
|
|
30
30
|
|
|
31
31
|
```text
|
|
32
32
|
Local files + chats + folders
|
|
33
33
|
↓
|
|
34
34
|
Automatic knowledge graph
|
|
35
35
|
↓
|
|
36
|
-
Graph-aware
|
|
36
|
+
Graph-aware chat, snapshots, memory, agents, workflows, skills, and timeline
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### New in 1.0.0: AI Workspace OS
|
|
40
|
+
|
|
41
|
+
- Workspace OS command center at `/workspace`
|
|
42
|
+
- First-run onboarding state API and UI
|
|
43
|
+
- Graph RAG answer traces with sources, nodes, edges, confidence, and jump links
|
|
44
|
+
- Local indexing dashboard with watcher state, success/failure counts, pause/resume/remove
|
|
45
|
+
- Workspace snapshots, Time Machine views, export, and Knowledge Diff
|
|
46
|
+
- Personal memory CRUD/search linked back to the graph
|
|
47
|
+
- Multi-agent graph entities and agent run history
|
|
48
|
+
- Relationship Explorer for inbound, outbound, related entities, and shortest path
|
|
49
|
+
- Local Computer Memory remains OFF by default and requires explicit approval
|
|
50
|
+
- Skill Marketplace registry with install, uninstall, update, enable, disable, and version state
|
|
51
|
+
- Workflow Graph for upload -> summarize -> generate -> export style work histories
|
|
52
|
+
- VS Code commands for Explain Selection, Refactor Selection, Generate Tests, Send To Lattice, and Ask About Current File
|
|
53
|
+
|
|
39
54
|
### Built for people who want
|
|
40
55
|
|
|
41
56
|
- a private AI workspace that runs from their own machine
|
|
@@ -76,7 +91,7 @@ LTCAI
|
|
|
76
91
|
2. Start the local server with `LTCAI`
|
|
77
92
|
3. Press `Cmd+Shift+A` to open the chat panel
|
|
78
93
|
|
|
79
|
-
**First run:** create an account
|
|
94
|
+
**First run:** create an account -> the first account becomes admin -> open `/workspace` -> complete onboarding -> choose a model -> connect folders -> start asking questions.
|
|
80
95
|
|
|
81
96
|
---
|
|
82
97
|
|
|
@@ -266,14 +281,16 @@ Supported routes include OpenAI-compatible APIs, OpenRouter, Groq, Together, xAI
|
|
|
266
281
|
|
|
267
282
|
## Current release
|
|
268
283
|
|
|
269
|
-
**0.
|
|
284
|
+
**0.6.0** completes the runtime / registry / config extraction sprint:
|
|
270
285
|
|
|
271
|
-
-
|
|
272
|
-
`
|
|
273
|
-
-
|
|
274
|
-
|
|
275
|
-
-
|
|
276
|
-
|
|
286
|
+
- `server.py` is now a thin compatibility entrypoint; FastAPI app assembly lives
|
|
287
|
+
in `latticeai.server_app`
|
|
288
|
+
- tool dispatch, governance, permission views, MCP descriptions, and prompt
|
|
289
|
+
catalog metadata are centralized in `ToolRegistry`
|
|
290
|
+
- agent role prompts are split into `latticeai.core.agent_prompts`, while
|
|
291
|
+
`AgentRuntime` remains the injected state-machine core
|
|
292
|
+
- Python package, npm package, VS Code extension, FastAPI app, and `/health`
|
|
293
|
+
version metadata are aligned at `0.6.0`
|
|
277
294
|
|
|
278
295
|
See the full [changelog](docs/CHANGELOG.md).
|
|
279
296
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0] - 2026-05-31
|
|
4
|
+
|
|
5
|
+
> AI Workspace OS integration release.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Workspace OS foundation** — new `/workspace` UI and `/workspace/*` API surface
|
|
10
|
+
organize LatticeAI around Graph, Snapshot, Memory, Agent, Workflow, Skills,
|
|
11
|
+
and Timeline areas while preserving existing chat, graph, admin, CLI, and MCP
|
|
12
|
+
compatibility.
|
|
13
|
+
- **First-run onboarding wizard** — reentrant step state, completion API,
|
|
14
|
+
hardware scan, model recommendations, folder connection state, and recovery
|
|
15
|
+
from failed/skipped steps.
|
|
16
|
+
- **Graph RAG answer trace** — each generated answer records source files,
|
|
17
|
+
graph nodes, graph edges, confidence, retrieval metadata, graph jumps, and
|
|
18
|
+
source jumps.
|
|
19
|
+
- **Local indexing dashboard** — indexed folder status, watcher state, success
|
|
20
|
+
and failure counts, last scan time, graph node/edge totals, and pause/resume/
|
|
21
|
+
remove operations.
|
|
22
|
+
- **Workspace snapshots and Time Machine** — immutable snapshots capture graph,
|
|
23
|
+
chat, settings, indexed folders, and loaded model state. Snapshots can be
|
|
24
|
+
listed, viewed by area, compared, and exported as ZIP artifacts.
|
|
25
|
+
- **Knowledge Diff** — Snapshot A/B comparison reports nodes added/removed/
|
|
26
|
+
changed, edges added/removed, and decisions changed.
|
|
27
|
+
- **Personal Memory layer** — per-user preferences, decisions, working style,
|
|
28
|
+
frequently used tools, and long-term memory with CRUD/search and graph links.
|
|
29
|
+
- **Multi-Agent Graph** — Planner, Executor, Reviewer, Researcher, and Release
|
|
30
|
+
Agent entities plus agent run history and timeline recording.
|
|
31
|
+
- **Relationship Explorer** — inbound/outbound edge views, related entities, and
|
|
32
|
+
shortest-path exploration for graph nodes.
|
|
33
|
+
- **Local Computer Memory** — defaults OFF, requires explicit approval, tracks
|
|
34
|
+
activity summaries only after consent, and links approved records to graph.
|
|
35
|
+
- **Skill Marketplace registry** — install, uninstall, update, enable, disable,
|
|
36
|
+
version tracking, and metadata state surfaced in Workspace OS.
|
|
37
|
+
- **Workflow Graph** — stores workflow timelines and searchable workflow graphs
|
|
38
|
+
for repeatable actions such as Upload -> Summarize -> Generate -> Export.
|
|
39
|
+
- **VS Code workflow** — added Refactor Selection, Generate Tests, Send To
|
|
40
|
+
Lattice, and Ask About Current File while preserving Explain Selection.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Release metadata aligned to `1.0.0` across Python, npm, VS Code extension,
|
|
45
|
+
FastAPI app metadata, `/health`, README, changelog, and release docs.
|
|
46
|
+
- `KnowledgeGraphStore` gained non-destructive `remove_local_source()` for
|
|
47
|
+
deleting only derived index/graph data while leaving user files untouched.
|
|
48
|
+
|
|
49
|
+
### Validation
|
|
50
|
+
|
|
51
|
+
- Unit, integration, Python build, npm build, VSIX packaging, and package
|
|
52
|
+
verification were run for this release.
|
|
53
|
+
|
|
54
|
+
## [0.6.0] - 2026-05-31
|
|
55
|
+
|
|
56
|
+
> Runtime / registry / config extraction release.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **server.py thin entrypoint** — moved FastAPI app assembly and route wiring to
|
|
61
|
+
`latticeai.server_app`; `server.py` now preserves the historical `server:app`
|
|
62
|
+
import path for uvicorn, Docker, CLI scripts, and tests.
|
|
63
|
+
- **ToolRegistry ownership** — centralized tool dispatch, governance policies,
|
|
64
|
+
permission views, MCP descriptions, prompt catalog text, and file-create
|
|
65
|
+
metadata in `latticeai.core.tool_registry`. `tools.execute_tool()` delegates
|
|
66
|
+
through the registry.
|
|
67
|
+
- **Agent prompts separated** — moved planner / executor / critic / memory
|
|
68
|
+
updater prompts to `latticeai.core.agent_prompts`; `AgentRuntime` remains the
|
|
69
|
+
injected state-machine core in `latticeai.core.agent`.
|
|
70
|
+
- **Release metadata** — bumped Python package, npm package, VS Code extension,
|
|
71
|
+
FastAPI app, and `/health` version to `0.6.0`.
|
|
72
|
+
|
|
73
|
+
### Validation
|
|
74
|
+
|
|
75
|
+
- Full test suite: 202 passed.
|
|
76
|
+
- Python package build, `twine check`, npm pack, and VSIX package build verified.
|
|
77
|
+
|
|
3
78
|
## [0.5.1] - 2026-05-31
|
|
4
79
|
|
|
5
80
|
> KGStoreV2 정규화 스키마 + 마이그레이션 하드닝 + native API 정리(릴리스).
|
package/knowledge_graph.py
CHANGED
|
@@ -1609,6 +1609,39 @@ class KnowledgeGraphStore:
|
|
|
1609
1609
|
)
|
|
1610
1610
|
return {"source_id": source_id, "watch_enabled": bool(enabled)}
|
|
1611
1611
|
|
|
1612
|
+
def remove_local_source(self, source_id: str) -> Dict[str, Any]:
|
|
1613
|
+
"""Remove one approved local source and its derived graph projection.
|
|
1614
|
+
|
|
1615
|
+
This is intentionally non-destructive for user files: only the LatticeAI
|
|
1616
|
+
index rows, graph nodes, edges, and chunks derived from the source are
|
|
1617
|
+
removed. The original folder and files are never touched.
|
|
1618
|
+
"""
|
|
1619
|
+
source_id = str(source_id or "").strip()
|
|
1620
|
+
if not source_id:
|
|
1621
|
+
raise ValueError("source_id required")
|
|
1622
|
+
with self._connect() as conn:
|
|
1623
|
+
source = conn.execute(
|
|
1624
|
+
"SELECT id, root_path FROM knowledge_sources WHERE id=?",
|
|
1625
|
+
(source_id,),
|
|
1626
|
+
).fetchone()
|
|
1627
|
+
if not source:
|
|
1628
|
+
raise ValueError(f"knowledge source not found: {source_id}")
|
|
1629
|
+
rows = conn.execute(
|
|
1630
|
+
"SELECT graph_node_id FROM local_file_index WHERE source_id=? AND graph_node_id IS NOT NULL",
|
|
1631
|
+
(source_id,),
|
|
1632
|
+
).fetchall()
|
|
1633
|
+
graph_node_ids = [row["graph_node_id"] for row in rows if row["graph_node_id"]]
|
|
1634
|
+
for graph_node_id in graph_node_ids:
|
|
1635
|
+
self._delete_local_file_graph(conn, graph_node_id)
|
|
1636
|
+
conn.execute("DELETE FROM local_file_index WHERE source_id=?", (source_id,))
|
|
1637
|
+
conn.execute("DELETE FROM knowledge_sources WHERE id=?", (source_id,))
|
|
1638
|
+
self._cleanup_local_graph_orphans(conn, source_id)
|
|
1639
|
+
return {
|
|
1640
|
+
"source_id": source_id,
|
|
1641
|
+
"root_path": source["root_path"],
|
|
1642
|
+
"removed_graph_nodes": len(graph_node_ids),
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1612
1645
|
def _extract_local_file_text(self, path: Path, category: str, *, include_ocr: bool) -> Tuple[str, Dict[str, Any]]:
|
|
1613
1646
|
ext = path.suffix.lower()
|
|
1614
1647
|
meta: Dict[str, Any] = {"parser": _parser_type_for_category(category, ext)}
|
package/latticeai/__init__.py
CHANGED
package/latticeai/core/agent.py
CHANGED
|
@@ -8,13 +8,13 @@ no globals, and no I/O of its own — every collaborator is injected through
|
|
|
8
8
|
|
|
9
9
|
Two adapters justify the seam:
|
|
10
10
|
|
|
11
|
-
* production wires ``AgentDeps`` from
|
|
11
|
+
* production wires ``AgentDeps`` from ``latticeai.server_app``'s ``LLMRouter``, governance
|
|
12
12
|
map, audit log, and prompts;
|
|
13
13
|
* tests pass fake ports (an LLM that returns canned JSON, a recording tool
|
|
14
14
|
executor) and drive a full PLAN→EXECUTE→VERIFY→DONE cycle without a server.
|
|
15
15
|
|
|
16
16
|
HTTP concerns — request parsing, chat-history persistence, response shaping,
|
|
17
|
-
scheduling the background memory update — stay in
|
|
17
|
+
scheduling the background memory update — stay in the app layer. This module
|
|
18
18
|
only owns the state machine.
|
|
19
19
|
"""
|
|
20
20
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Role prompts for the Lattice multi-role agent runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from latticeai.core.tool_registry import TOOL_CATALOG_BRIEF
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PLANNER_PROMPT = """You are the PLANNER role in Lattice AI's multi-role agent harness.
|
|
9
|
+
Your ONLY job: analyze the request and produce a structured execution plan.
|
|
10
|
+
You do NOT call tools or write code.
|
|
11
|
+
|
|
12
|
+
Respond with exactly ONE JSON object (no markdown, no fences):
|
|
13
|
+
{
|
|
14
|
+
"action": "plan",
|
|
15
|
+
"state": "PLANNING",
|
|
16
|
+
"goal": "one-sentence goal in the user's language",
|
|
17
|
+
"steps": [
|
|
18
|
+
{"id": 1, "description": "what this step does", "action": "expected_tool", "purpose": "why needed"}
|
|
19
|
+
],
|
|
20
|
+
"requires_approval": true,
|
|
21
|
+
"rollback_strategy": "git",
|
|
22
|
+
"estimated_steps": 3
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Rules:
|
|
26
|
+
- requires_approval = true if ANY step uses write/exec tools (edit_file, write_file, run_command, etc.)
|
|
27
|
+
- rollback_strategy = "git" if steps modify existing files; "none" otherwise
|
|
28
|
+
- Keep steps realistic: 2-4 for simple tasks, up to 10 for complex ones
|
|
29
|
+
- Do NOT specify full tool args -- that is the Executor's job
|
|
30
|
+
|
|
31
|
+
Available tools:""" + TOOL_CATALOG_BRIEF
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
EXECUTOR_PROMPT = """You are the EXECUTOR role in Lattice AI's multi-role agent harness.
|
|
35
|
+
You have a plan from the Planner. Execute it step by step using exactly one tool per response.
|
|
36
|
+
|
|
37
|
+
You think and act like a senior software engineer:
|
|
38
|
+
- Read (read_file, grep) BEFORE editing -- never guess at file contents
|
|
39
|
+
- Prefer edit_file over write_file for existing files
|
|
40
|
+
- Keep changes small and precise
|
|
41
|
+
- Verify after changes with build_project or run_command
|
|
42
|
+
|
|
43
|
+
Respond with exactly ONE JSON object per step:
|
|
44
|
+
{"thoughts": "what you learned / why this next action", "action": "tool_name", "args": {...}}
|
|
45
|
+
|
|
46
|
+
When the task is fully done AND a tool result in this run confirms it:
|
|
47
|
+
{"thoughts": "verified", "action": "final", "message": "한국어로 무엇을 했고 어디서 검증했는지 요약"}
|
|
48
|
+
|
|
49
|
+
ANTI-PATTERNS (will halt the loop):
|
|
50
|
+
- Editing without reading first -> read_file + grep BEFORE edit_file
|
|
51
|
+
- Repeating the same action+args -> check the transcript
|
|
52
|
+
- Claiming done without a verification tool result in transcript
|
|
53
|
+
- Hallucinating imports or file paths that were never confirmed by a tool result
|
|
54
|
+
|
|
55
|
+
Available tools:""" + TOOL_CATALOG_BRIEF
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
CRITIC_PROMPT = """You are the CRITIC / REVIEWER role in Lattice AI's multi-role agent harness.
|
|
59
|
+
Review the execution transcript and determine whether the goal was achieved.
|
|
60
|
+
|
|
61
|
+
Respond with exactly ONE JSON object:
|
|
62
|
+
{
|
|
63
|
+
"action": "verdict",
|
|
64
|
+
"state": "VERIFYING",
|
|
65
|
+
"verdict": "PASS",
|
|
66
|
+
"reason": "why you think it passed or failed (cite specific tool results)",
|
|
67
|
+
"corrections": [],
|
|
68
|
+
"confidence": 0.95,
|
|
69
|
+
"next_state": "DONE"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
verdict: "PASS" | "FAIL"
|
|
73
|
+
next_state:
|
|
74
|
+
"DONE" -- task succeeded; finish
|
|
75
|
+
"EXECUTING" -- task failed but corrections can fix it (use corrections field for retry)
|
|
76
|
+
"ROLLBACK" -- task failed AND file changes should be undone
|
|
77
|
+
|
|
78
|
+
Criteria for PASS: a tool result in the transcript explicitly confirms success.
|
|
79
|
+
Be strict. Claiming done without evidence = FAIL."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
MEMORY_UPDATER_PROMPT = """You are the MEMORY UPDATER role in Lattice AI's multi-role agent harness.
|
|
83
|
+
After a completed task, extract reusable learnings.
|
|
84
|
+
|
|
85
|
+
Respond with exactly ONE JSON object:
|
|
86
|
+
{
|
|
87
|
+
"action": "memory",
|
|
88
|
+
"state": "DONE",
|
|
89
|
+
"learnings": ["one concise fact about this codebase or task"],
|
|
90
|
+
"artifacts": ["relative/path/to/created_or_modified_file"],
|
|
91
|
+
"save_to_knowledge": false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Rules:
|
|
95
|
+
- max 5 learnings, one sentence each
|
|
96
|
+
- save_to_knowledge = true only if learnings are genuinely useful across future sessions
|
|
97
|
+
- artifacts = files the Executor actually created or modified (from transcript)
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
AGENT_SYSTEM_PROMPT = EXECUTOR_PROMPT
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Tool dispatch, governance, and catalog metadata.
|
|
2
|
+
|
|
3
|
+
The registry is the single ownership point for tool names: one object exposes
|
|
4
|
+
dispatch, policy lookup, prompt catalog text, MCP descriptions, and permission
|
|
5
|
+
views. The actual tool functions still live in the top-level ``tools`` module
|
|
6
|
+
to preserve the public API and keep this module free of filesystem side
|
|
7
|
+
effects at import time.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Callable, Dict, Mapping, Optional, TypedDict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolPolicy(TypedDict):
|
|
17
|
+
risk: str
|
|
18
|
+
destructive: bool
|
|
19
|
+
shell: bool
|
|
20
|
+
network: bool
|
|
21
|
+
auto_approve: bool
|
|
22
|
+
sandbox: str
|
|
23
|
+
rollback: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ToolPermission(TypedDict):
|
|
27
|
+
tool: str
|
|
28
|
+
risk: str
|
|
29
|
+
requires_approval: bool
|
|
30
|
+
network: bool
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
TOOL_CATALOG_BRIEF = """
|
|
34
|
+
FILESYSTEM : list_dir workspace_tree read_file write_file edit_file grep search_files inspect_html preview_url
|
|
35
|
+
PLANNING : todo_read todo_write
|
|
36
|
+
PROJECT : run_command build_project deploy_project create_web_project
|
|
37
|
+
GIT (read) : git_status git_diff git_log git_show
|
|
38
|
+
LOCAL FS : local_list local_read local_write read_document
|
|
39
|
+
DOCS : create_docx create_xlsx create_pptx create_pdf
|
|
40
|
+
KNOWLEDGE : knowledge_save knowledge_search knowledge_tree
|
|
41
|
+
COMPUTER : computer_screenshot computer_open_app computer_open_url computer_click computer_type computer_key
|
|
42
|
+
MISC : network_status clear_history final
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
FILE_CREATE_ACTIONS = frozenset({
|
|
46
|
+
"create_docx",
|
|
47
|
+
"create_xlsx",
|
|
48
|
+
"create_pptx",
|
|
49
|
+
"create_pdf",
|
|
50
|
+
"write_file",
|
|
51
|
+
"edit_file",
|
|
52
|
+
"create_web_project",
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
LOCAL_WRITE_BLOCKED_PREFIXES = (
|
|
56
|
+
"/etc/",
|
|
57
|
+
"/usr/",
|
|
58
|
+
"/bin/",
|
|
59
|
+
"/sbin/",
|
|
60
|
+
"/System/",
|
|
61
|
+
"/private/etc/",
|
|
62
|
+
"/Library/LaunchDaemons/",
|
|
63
|
+
"/Library/LaunchAgents/",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
RISK_LEVEL_MAP = {
|
|
67
|
+
"read": "low",
|
|
68
|
+
"write": "medium",
|
|
69
|
+
"exec": "high",
|
|
70
|
+
"destructive": "high",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _r(sandbox: str = "workspace", rollback: str = "none") -> ToolPolicy:
|
|
75
|
+
return ToolPolicy(
|
|
76
|
+
risk="read", destructive=False, shell=False, network=False,
|
|
77
|
+
auto_approve=True, sandbox=sandbox, rollback=rollback,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _rs(sandbox: str = "workspace", rollback: str = "none") -> ToolPolicy:
|
|
82
|
+
return ToolPolicy(
|
|
83
|
+
risk="read", destructive=False, shell=True, network=False,
|
|
84
|
+
auto_approve=True, sandbox=sandbox, rollback=rollback,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _rn(sandbox: str = "system", rollback: str = "none") -> ToolPolicy:
|
|
89
|
+
return ToolPolicy(
|
|
90
|
+
risk="read", destructive=False, shell=True, network=True,
|
|
91
|
+
auto_approve=True, sandbox=sandbox, rollback=rollback,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _w(sandbox: str = "workspace", rollback: str = "none") -> ToolPolicy:
|
|
96
|
+
return ToolPolicy(
|
|
97
|
+
risk="write", destructive=False, shell=False, network=False,
|
|
98
|
+
auto_approve=False, sandbox=sandbox, rollback=rollback,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _e(sandbox: str = "workspace", rollback: str = "none") -> ToolPolicy:
|
|
103
|
+
return ToolPolicy(
|
|
104
|
+
risk="exec", destructive=False, shell=True, network=False,
|
|
105
|
+
auto_approve=False, sandbox=sandbox, rollback=rollback,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _en(sandbox: str = "workspace", rollback: str = "none") -> ToolPolicy:
|
|
110
|
+
return ToolPolicy(
|
|
111
|
+
risk="exec", destructive=False, shell=True, network=True,
|
|
112
|
+
auto_approve=False, sandbox=sandbox, rollback=rollback,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _ec(sandbox: str = "system", rollback: str = "none") -> ToolPolicy:
|
|
117
|
+
return ToolPolicy(
|
|
118
|
+
risk="exec", destructive=False, shell=False, network=False,
|
|
119
|
+
auto_approve=False, sandbox=sandbox, rollback=rollback,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
TOOL_GOVERNANCE: Dict[str, ToolPolicy] = {
|
|
124
|
+
"list_dir": _r(),
|
|
125
|
+
"workspace_tree": _r(),
|
|
126
|
+
"read_file": _r(),
|
|
127
|
+
"search_files": _r(),
|
|
128
|
+
"grep": _r(),
|
|
129
|
+
"inspect_html": _r(),
|
|
130
|
+
"todo_read": _r(),
|
|
131
|
+
"local_list": _r(sandbox="home"),
|
|
132
|
+
"local_read": _r(sandbox="home"),
|
|
133
|
+
"git_status": _rs(),
|
|
134
|
+
"git_diff": _rs(),
|
|
135
|
+
"git_log": _rs(),
|
|
136
|
+
"git_show": _rs(),
|
|
137
|
+
"knowledge_search": _r(sandbox="home"),
|
|
138
|
+
"knowledge_tree": _r(sandbox="home"),
|
|
139
|
+
"obsidian_search": _r(sandbox="home"),
|
|
140
|
+
"obsidian_tree": _r(sandbox="home"),
|
|
141
|
+
"computer_screenshot": _r(sandbox="system"),
|
|
142
|
+
"computer_status": _r(sandbox="system"),
|
|
143
|
+
"chrome_status": _r(sandbox="system"),
|
|
144
|
+
"computer_use_status": _r(sandbox="system"),
|
|
145
|
+
"network_status": _rn(),
|
|
146
|
+
"write_file": _w(rollback="git"),
|
|
147
|
+
"edit_file": _w(rollback="git"),
|
|
148
|
+
"create_web_project": _w(),
|
|
149
|
+
"create_docx": _w(),
|
|
150
|
+
"create_xlsx": _w(),
|
|
151
|
+
"create_pptx": _w(),
|
|
152
|
+
"create_pdf": _w(),
|
|
153
|
+
"preview_url": _w(),
|
|
154
|
+
"todo_write": _w(),
|
|
155
|
+
"knowledge_save": _w(sandbox="home"),
|
|
156
|
+
"obsidian_save": _w(sandbox="home"),
|
|
157
|
+
"local_write": _w(sandbox="home"),
|
|
158
|
+
"run_command": _e(),
|
|
159
|
+
"build_project": _e(),
|
|
160
|
+
"deploy_project": _en(),
|
|
161
|
+
"computer_click": _ec(),
|
|
162
|
+
"computer_type": _ec(),
|
|
163
|
+
"computer_key": _ec(),
|
|
164
|
+
"computer_scroll": _ec(),
|
|
165
|
+
"computer_drag": _ec(),
|
|
166
|
+
"computer_move": _ec(),
|
|
167
|
+
"computer_open_app": _ec(),
|
|
168
|
+
"computer_open_url": ToolPolicy(
|
|
169
|
+
risk="exec", destructive=False, shell=False, network=True,
|
|
170
|
+
auto_approve=False, sandbox="system", rollback="none",
|
|
171
|
+
),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
TOOL_GOVERNANCE_DEFAULT = ToolPolicy(
|
|
175
|
+
risk="write", destructive=False, shell=False, network=False,
|
|
176
|
+
auto_approve=False, sandbox="workspace", rollback="none",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
MCP_TOOL_DESCRIPTIONS: Dict[str, str] = {
|
|
180
|
+
"list_dir": "List files in the agent workspace.",
|
|
181
|
+
"workspace_tree": "Return a recursive workspace tree.",
|
|
182
|
+
"read_file": "Read a UTF-8 file from the workspace with optional line numbers and offset/limit slicing.",
|
|
183
|
+
"write_file": "Write a UTF-8 file inside the workspace (new files / full rewrites).",
|
|
184
|
+
"edit_file": "Precise diff-style edit: replace exact old_string with new_string. Requires unique match unless replace_all=true.",
|
|
185
|
+
"search_files": "Substring search in text files (legacy).",
|
|
186
|
+
"grep": "Regex search across the workspace with line numbers and optional context.",
|
|
187
|
+
"todo_read": "Read the agent's persistent TODO list for the current workspace.",
|
|
188
|
+
"todo_write": "Replace the agent's TODO list (id, content, status: pending/in_progress/completed).",
|
|
189
|
+
"clear_history": "Clear chat history to reduce context and speed up responses.",
|
|
190
|
+
"inspect_html": "Inspect local HTML structure and assets.",
|
|
191
|
+
"preview_url": "Return a server URL for a workspace file.",
|
|
192
|
+
"create_docx": "Create a Word DOCX document in the agent workspace.",
|
|
193
|
+
"create_xlsx": "Create an XLSX spreadsheet in the agent workspace.",
|
|
194
|
+
"create_pptx": "Create a PPTX presentation deck in the agent workspace.",
|
|
195
|
+
"create_pdf": "Create a PDF document in the agent workspace.",
|
|
196
|
+
"local_list": "List any local folder (requires user permission via UI).",
|
|
197
|
+
"local_read": "Read any local file (requires user permission via UI).",
|
|
198
|
+
"local_write": "Write any local file (requires user permission via UI).",
|
|
199
|
+
"read_document": "Extract text from PDF, DOCX, XLSX, PPTX, TXT, MD, CSV files.",
|
|
200
|
+
"computer_screenshot": "Capture the current Mac screen as base64 PNG.",
|
|
201
|
+
"computer_open_app": "Open or focus a Mac app, e.g. Google Chrome.",
|
|
202
|
+
"computer_open_url": "Open a URL in a Mac app, e.g. Google Chrome.",
|
|
203
|
+
"computer_click": "Click at screen coordinates (x, y).",
|
|
204
|
+
"computer_type": "Type text at the current focus position.",
|
|
205
|
+
"computer_key": "Press a keyboard key or shortcut (e.g. 'command+c').",
|
|
206
|
+
"computer_scroll": "Scroll at screen coordinates.",
|
|
207
|
+
"computer_move": "Move the mouse to screen coordinates.",
|
|
208
|
+
"computer_drag": "Drag from (x1,y1) to (x2,y2).",
|
|
209
|
+
"computer_status": "Check if Mac desktop control (pyautogui) is available.",
|
|
210
|
+
"chrome_status": "Report Chrome desktop bridge availability.",
|
|
211
|
+
"computer_use_status": "Report Mac desktop-control bridge availability.",
|
|
212
|
+
"knowledge_save": "Save a note into the local knowledge garden.",
|
|
213
|
+
"knowledge_search": "Search the local knowledge garden.",
|
|
214
|
+
"knowledge_tree": "List local knowledge garden markdown files.",
|
|
215
|
+
"knowledge_graph_ingest": "Ingest a message, AI answer, or connector event into the SQLite knowledge graph.",
|
|
216
|
+
"knowledge_graph_search": "Search graph nodes, summaries, and JSON metadata.",
|
|
217
|
+
"knowledge_graph_graph": "Return Obsidian-style graph nodes and edges.",
|
|
218
|
+
"knowledge_graph_context": "Return compact graph-backed RAG context for a prompt.",
|
|
219
|
+
"obsidian_save": "Save a note into the Obsidian-compatible memory vault.",
|
|
220
|
+
"obsidian_search": "Search the Obsidian-compatible memory vault.",
|
|
221
|
+
"obsidian_tree": "List Obsidian memory vault markdown files.",
|
|
222
|
+
"git_status": "Read-only local git status inside the workspace.",
|
|
223
|
+
"git_diff": "Read-only local git diff inside the workspace.",
|
|
224
|
+
"git_log": "Read-only local git log inside the workspace.",
|
|
225
|
+
"git_show": "Read-only local git show --stat inside the workspace.",
|
|
226
|
+
"network_status": "Get current local/private IP, public IP, hostname, and Wi-Fi info.",
|
|
227
|
+
"run_command": "Run an allowlisted local command inside the workspace.",
|
|
228
|
+
"build_project": "Run an allowlisted package.json build/compile/typecheck/test script to verify changes actually work.",
|
|
229
|
+
"deploy_project": "Run an allowlisted package.json deploy/preview/release/package installer script (pkg/exe).",
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class ToolRegistry:
|
|
235
|
+
handlers: Mapping[str, Callable[[Dict[str, Any]], Dict[str, Any]]]
|
|
236
|
+
governance: Mapping[str, ToolPolicy] = field(default_factory=lambda: TOOL_GOVERNANCE)
|
|
237
|
+
default_policy: ToolPolicy = field(default_factory=lambda: TOOL_GOVERNANCE_DEFAULT)
|
|
238
|
+
descriptions: Mapping[str, str] = field(default_factory=lambda: MCP_TOOL_DESCRIPTIONS)
|
|
239
|
+
catalog_brief: str = TOOL_CATALOG_BRIEF
|
|
240
|
+
file_create_actions: frozenset[str] = FILE_CREATE_ACTIONS
|
|
241
|
+
local_write_blocked_prefixes: tuple[str, ...] = LOCAL_WRITE_BLOCKED_PREFIXES
|
|
242
|
+
risk_level_map: Mapping[str, str] = field(default_factory=lambda: RISK_LEVEL_MAP)
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def admin_only_tools(self) -> frozenset[str]:
|
|
246
|
+
return frozenset(
|
|
247
|
+
name for name, policy in self.governance.items()
|
|
248
|
+
if policy["sandbox"] == "system" or policy["risk"] in {"exec", "destructive"}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def registered_tools(self) -> frozenset[str]:
|
|
252
|
+
return frozenset(self.handlers)
|
|
253
|
+
|
|
254
|
+
def execute(self, action: str, args: Dict[str, Any], *, error_cls: type[Exception]) -> Dict[str, Any]:
|
|
255
|
+
handler = self.handlers.get(action)
|
|
256
|
+
if handler is None:
|
|
257
|
+
raise error_cls(f"Unknown action: {action}")
|
|
258
|
+
return handler(args or {})
|
|
259
|
+
|
|
260
|
+
def policy_for(self, action_name: str, args: Optional[dict] = None) -> ToolPolicy:
|
|
261
|
+
policy = self.governance.get(action_name, self.default_policy)
|
|
262
|
+
if action_name == "local_write":
|
|
263
|
+
path = str((args or {}).get("path", ""))
|
|
264
|
+
if any(path.startswith(prefix) for prefix in self.local_write_blocked_prefixes):
|
|
265
|
+
return ToolPolicy(
|
|
266
|
+
risk="destructive", destructive=True, shell=False, network=False,
|
|
267
|
+
auto_approve=False, sandbox="system", rollback="none",
|
|
268
|
+
)
|
|
269
|
+
return policy
|
|
270
|
+
|
|
271
|
+
def risk_level(self, policy_or_action: ToolPolicy | str, args: Optional[dict] = None) -> str:
|
|
272
|
+
if isinstance(policy_or_action, str):
|
|
273
|
+
policy = self.policy_for(policy_or_action, args or {})
|
|
274
|
+
else:
|
|
275
|
+
policy = policy_or_action
|
|
276
|
+
return self.risk_level_map.get(policy["risk"], "medium")
|
|
277
|
+
|
|
278
|
+
def permission(self, name: str, args: Optional[dict] = None) -> ToolPermission:
|
|
279
|
+
policy = self.policy_for(name, args or {})
|
|
280
|
+
return ToolPermission(
|
|
281
|
+
tool=name,
|
|
282
|
+
risk=self.risk_level(policy),
|
|
283
|
+
requires_approval=not policy["auto_approve"],
|
|
284
|
+
network=policy["network"],
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def permissions(self) -> list[ToolPermission]:
|
|
288
|
+
return [self.permission(name) for name in sorted(self.governance.keys())]
|