ltcai 0.3.1 → 0.4.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 +285 -208
- package/docs/CHANGELOG.md +73 -0
- package/kg_schema.py +42 -0
- package/knowledge_graph.py +232 -36
- package/latticeai/api/security_dashboard.py +6 -2
- package/latticeai/core/agent.py +453 -0
- package/latticeai/core/audit.py +4 -1
- package/latticeai/core/config.py +178 -0
- package/latticeai/core/graph_curator.py +60 -4
- package/latticeai/core/model_compat.py +67 -24
- package/latticeai/core/timezones.py +80 -0
- package/package.json +2 -2
- package/server.py +108 -441
- package/static/scripts/chat.js +105 -16
- package/tools.py +87 -115
package/static/scripts/chat.js
CHANGED
|
@@ -1469,7 +1469,7 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1469
1469
|
const icon = isUnavailable ? 'ti-lock' : (engineMissing || needsPull) ? 'ti-cloud-download' : verifyUnknown ? 'ti-activity' : 'ti-switch-3';
|
|
1470
1470
|
const cls = (engineMissing || needsPull) && isLocalEngine ? ' needs-pull' : '';
|
|
1471
1471
|
const action = isLocalEngine
|
|
1472
|
-
? `
|
|
1472
|
+
? `selectModelByCard('${encodeURIComponent(model.id)}', '${engine?.id || ''}')`
|
|
1473
1473
|
: `loadSelectedModel('${encodeURIComponent(model.id)}', '${engine?.id || ''}')`;
|
|
1474
1474
|
return `
|
|
1475
1475
|
<button class="model-option${cls}" ${isUnavailable ? 'disabled' : ''} onclick="${action}">
|
|
@@ -1669,7 +1669,17 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1669
1669
|
if (!res.ok) throw new Error(data.detail || '모델 로드에 실패했습니다.');
|
|
1670
1670
|
closeModelPanel();
|
|
1671
1671
|
await loadModelStatus();
|
|
1672
|
-
|
|
1672
|
+
// 피드백 #1/#2: 클라우드 경로도 백엔드 current를 단일 진실원으로 사용한다.
|
|
1673
|
+
const actualCurrent = resolveActualCurrent(data, modelId);
|
|
1674
|
+
setCurrentModel(actualCurrent);
|
|
1675
|
+
updateCurrentModelUI(actualCurrent);
|
|
1676
|
+
let statusLine = `모델을 <b>${escapeHtml(compactModelName(actualCurrent))}</b>로 전환했습니다.`;
|
|
1677
|
+
const compat = describeCompatibility(data);
|
|
1678
|
+
if (compat) {
|
|
1679
|
+
statusLine += `<br><span class="sensitivity-preview">${escapeHtml(compat.message)}</span>`;
|
|
1680
|
+
showModelCompatibilityWarning(data);
|
|
1681
|
+
}
|
|
1682
|
+
addMessage('ai', statusLine);
|
|
1673
1683
|
} catch (e) {
|
|
1674
1684
|
document.getElementById('model-list').innerHTML = `
|
|
1675
1685
|
<div class="sensitivity-preview">${escapeHtml(e.message)}</div>
|
|
@@ -1829,6 +1839,66 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1829
1839
|
if (buffer.trim()) dispatchBlock(buffer.trim());
|
|
1830
1840
|
}
|
|
1831
1841
|
|
|
1842
|
+
// 피드백 #1/#2: "사용자가 보는 현재 모델" === "실제로 채팅에 사용되는 모델".
|
|
1843
|
+
// 백엔드가 돌려준 current/resolution을 단일 진실원으로 사용한다.
|
|
1844
|
+
function resolveActualCurrent(finalData, fallbackId) {
|
|
1845
|
+
if (!finalData) return fallbackId || '';
|
|
1846
|
+
return (
|
|
1847
|
+
finalData.current
|
|
1848
|
+
|| (finalData.resolution && (finalData.resolution.expected_current || finalData.resolution.resolved_model))
|
|
1849
|
+
|| fallbackId
|
|
1850
|
+
|| ''
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
function setCurrentModel(modelId) {
|
|
1855
|
+
if (!modelId) return;
|
|
1856
|
+
window.__latticeActiveModel = modelId;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function updateCurrentModelUI(modelId) {
|
|
1860
|
+
if (!modelId) return;
|
|
1861
|
+
const modelEl = document.getElementById('ops-model');
|
|
1862
|
+
if (modelEl) {
|
|
1863
|
+
modelEl.textContent = compactModelName(modelId);
|
|
1864
|
+
modelEl.title = modelId;
|
|
1865
|
+
}
|
|
1866
|
+
const metaEl = document.getElementById('ops-model-meta');
|
|
1867
|
+
if (metaEl && !metaEl.dataset.loaded) {
|
|
1868
|
+
metaEl.dataset.loaded = 'true';
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
function describeCompatibility(finalData) {
|
|
1873
|
+
if (!finalData) return null;
|
|
1874
|
+
if (finalData.ready_to_chat === false) {
|
|
1875
|
+
const reason = (finalData.smoke_test && finalData.smoke_test.reason) || '채팅 호환성 검사 실패';
|
|
1876
|
+
return {
|
|
1877
|
+
severity: 'degraded',
|
|
1878
|
+
message: `⚠️ 채팅 호환성이 낮습니다 (${reason}). 다른 실행 엔진을 추천합니다.`,
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
if (finalData.compatibility_status === 'degraded') {
|
|
1882
|
+
return {
|
|
1883
|
+
severity: 'degraded',
|
|
1884
|
+
message: '⚠️ 모델은 로드됐지만 호환성 테스트가 degraded로 나왔습니다. 답변 품질이 일정하지 않을 수 있어요.',
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
if (finalData.compatibility_status === 'unknown') {
|
|
1888
|
+
return {
|
|
1889
|
+
severity: 'unknown',
|
|
1890
|
+
message: '호환성 테스트를 완료하지 못했습니다. 채팅이 가능하지만 답변 품질이 일정하지 않을 수 있어요.',
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
return null;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
function showModelCompatibilityWarning(finalData) {
|
|
1897
|
+
const info = describeCompatibility(finalData);
|
|
1898
|
+
if (!info) return;
|
|
1899
|
+
try { showToast(info.message); } catch (_) {}
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1832
1902
|
async function prepareAndLoadModel(encodedId, engine = '') {
|
|
1833
1903
|
const modelId = decodeURIComponent(encodedId);
|
|
1834
1904
|
const displayName = compactModelName(modelId);
|
|
@@ -1870,22 +1940,22 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1870
1940
|
if (!finalData) throw new Error('모델 준비 응답이 비어 있습니다.');
|
|
1871
1941
|
closeModelPanel();
|
|
1872
1942
|
await loadModelStatus();
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1943
|
+
const actualCurrent = resolveActualCurrent(finalData, modelId);
|
|
1944
|
+
setCurrentModel(actualCurrent);
|
|
1945
|
+
updateCurrentModelUI(actualCurrent);
|
|
1876
1946
|
let statusLine = `<b>${escapeHtml(compactModelName(actualCurrent))}</b> 로드 되었습니다.`;
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
statusLine += `<br><span class="sensitivity-preview"
|
|
1880
|
-
} else if (finalData.compatibility_status === 'unknown') {
|
|
1881
|
-
statusLine += `<br><span class="sensitivity-preview">호환성 테스트를 완료하지 못했습니다. 채팅이 가능하지만 답변 품질이 일정하지 않을 수 있어요.</span>`;
|
|
1947
|
+
const compat = describeCompatibility(finalData);
|
|
1948
|
+
if (compat) {
|
|
1949
|
+
statusLine += `<br><span class="sensitivity-preview">${escapeHtml(compat.message)}</span>`;
|
|
1882
1950
|
}
|
|
1883
1951
|
addMessage('ai', statusLine);
|
|
1952
|
+
return finalData;
|
|
1884
1953
|
} catch (e) {
|
|
1885
1954
|
document.getElementById('model-list').innerHTML = `
|
|
1886
1955
|
<div class="sensitivity-preview">${escapeHtml(e.message)}</div>
|
|
1887
1956
|
<button class="admin-action" onclick="openModelPanel()" style="margin-top: 12px;">목록으로 돌아가기</button>
|
|
1888
1957
|
`;
|
|
1958
|
+
throw e;
|
|
1889
1959
|
}
|
|
1890
1960
|
}
|
|
1891
1961
|
|
|
@@ -1893,17 +1963,36 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1893
1963
|
return prepareAndLoadModel(encodedId, engine);
|
|
1894
1964
|
}
|
|
1895
1965
|
|
|
1896
|
-
// 피드백 #1/#2:
|
|
1897
|
-
|
|
1898
|
-
|
|
1966
|
+
// 피드백 #1/#2: 모델 카드 클릭 → prepare/load → smoke test → current 반영 → 채팅 가능 여부 표시
|
|
1967
|
+
// 가 하나의 흐름으로 이어지도록 한다. encodedId/engine 또는 card 객체 양쪽 모두 받는다.
|
|
1968
|
+
async function selectModelByCard(modelIdOrCard, engineArg) {
|
|
1969
|
+
let encoded;
|
|
1970
|
+
let engine = engineArg || '';
|
|
1971
|
+
if (typeof modelIdOrCard === 'string') {
|
|
1972
|
+
encoded = modelIdOrCard.includes('%') ? modelIdOrCard : encodeURIComponent(modelIdOrCard);
|
|
1973
|
+
} else if (modelIdOrCard && modelIdOrCard.id) {
|
|
1974
|
+
encoded = encodeURIComponent(modelIdOrCard.id);
|
|
1975
|
+
if (!engine) {
|
|
1976
|
+
engine = modelIdOrCard.engine
|
|
1977
|
+
|| (Array.isArray(modelIdOrCard.engine_options) && modelIdOrCard.engine_options[0]?.engine)
|
|
1978
|
+
|| '';
|
|
1979
|
+
}
|
|
1980
|
+
} else {
|
|
1899
1981
|
throw new Error('모델 카드가 비어 있습니다.');
|
|
1900
1982
|
}
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
|
|
1983
|
+
const result = await prepareAndLoadModel(encoded, engine);
|
|
1984
|
+
if (result && result.current) {
|
|
1985
|
+
setCurrentModel(result.current);
|
|
1986
|
+
updateCurrentModelUI(result.current);
|
|
1987
|
+
}
|
|
1988
|
+
if (result && (result.ready_to_chat === false || result.compatibility_status === 'degraded')) {
|
|
1989
|
+
showModelCompatibilityWarning(result);
|
|
1990
|
+
}
|
|
1991
|
+
return result;
|
|
1904
1992
|
}
|
|
1905
1993
|
if (typeof window !== 'undefined') {
|
|
1906
1994
|
window.selectModelByCard = selectModelByCard;
|
|
1995
|
+
window.prepareAndLoadModel = prepareAndLoadModel;
|
|
1907
1996
|
}
|
|
1908
1997
|
|
|
1909
1998
|
function fillVpcForm(config) {
|
package/tools.py
CHANGED
|
@@ -17,7 +17,7 @@ import tempfile
|
|
|
17
17
|
import json
|
|
18
18
|
from html.parser import HTMLParser
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Dict, List, Optional
|
|
20
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
21
21
|
|
|
22
22
|
_PLATFORM = platform.system() # "Darwin" | "Windows" | "Linux"
|
|
23
23
|
|
|
@@ -1435,118 +1435,90 @@ def git_show(revision: str = "HEAD", cwd: Optional[str] = None) -> Dict[str, Any
|
|
|
1435
1435
|
return _run_git(["show", "--stat", "--oneline", "--decorate", revision], cwd)
|
|
1436
1436
|
|
|
1437
1437
|
|
|
1438
|
+
def _h_create_xlsx(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1439
|
+
rows = args.get("rows", [])
|
|
1440
|
+
if isinstance(rows, str):
|
|
1441
|
+
rows = json.loads(rows)
|
|
1442
|
+
return create_xlsx(rows, args.get("filename", "spreadsheet.xlsx"), args.get("sheet_name", "Sheet1"))
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
def _h_create_pptx(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1446
|
+
slides = args.get("slides", [])
|
|
1447
|
+
if isinstance(slides, str):
|
|
1448
|
+
slides = json.loads(slides)
|
|
1449
|
+
return create_pptx(args.get("title", ""), slides, args.get("filename", "presentation.pptx"))
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
# ── Tool registry: the single source of truth for name → invocation ───────────
|
|
1453
|
+
# Each entry binds the args dict to a tool function. ``execute_tool`` is a
|
|
1454
|
+
# lookup over this table — adding a tool means adding one entry here, not
|
|
1455
|
+
# editing an if/elif chain. server.py's governance map and catalog brief are
|
|
1456
|
+
# checked against ``registered_tools()`` so the three never silently drift.
|
|
1457
|
+
TOOL_HANDLERS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {
|
|
1458
|
+
# filesystem
|
|
1459
|
+
"list_dir": lambda a: list_dir(a.get("path", ".")),
|
|
1460
|
+
"workspace_tree": lambda a: workspace_tree(a.get("path", "."), a.get("max_depth", 3)),
|
|
1461
|
+
"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)),
|
|
1462
|
+
"write_file": lambda a: write_file(a["path"], a.get("content", "")),
|
|
1463
|
+
"edit_file": lambda a: edit_file(a["path"], a["old_string"], a["new_string"], replace_all=bool(a.get("replace_all", False))),
|
|
1464
|
+
"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)),
|
|
1465
|
+
"search_files": lambda a: search_files(a["query"], a.get("path", "."), a.get("max_results", 20)),
|
|
1466
|
+
"inspect_html": lambda a: inspect_html(a["path"]),
|
|
1467
|
+
"preview_url": lambda a: preview_url(a.get("path", "index.html")),
|
|
1468
|
+
# planning
|
|
1469
|
+
"todo_read": lambda a: todo_read(),
|
|
1470
|
+
"todo_write": lambda a: todo_write(a.get("todos") or []),
|
|
1471
|
+
# documents
|
|
1472
|
+
"create_docx": lambda a: create_docx(a.get("title", ""), a.get("body", ""), a.get("filename", "document.docx")),
|
|
1473
|
+
"create_xlsx": _h_create_xlsx,
|
|
1474
|
+
"create_pptx": _h_create_pptx,
|
|
1475
|
+
"create_pdf": lambda a: create_pdf(a.get("title", ""), a.get("body", ""), a.get("filename", "document.pdf")),
|
|
1476
|
+
"create_web_project": lambda a: create_web_project(a.get("path", ""), a.get("framework", "react"), a.get("template", "vite")),
|
|
1477
|
+
# local filesystem
|
|
1478
|
+
"local_list": lambda a: local_list(a["path"]),
|
|
1479
|
+
"local_read": lambda a: local_read(a["path"]),
|
|
1480
|
+
"local_write": lambda a: local_write(a["path"], a.get("content", "")),
|
|
1481
|
+
"read_document": lambda a: read_document(a["path"]),
|
|
1482
|
+
"network_status": lambda a: network_status(),
|
|
1483
|
+
# computer use
|
|
1484
|
+
"computer_screenshot": lambda a: computer_screenshot(),
|
|
1485
|
+
"computer_open_app": lambda a: computer_open_app(a.get("app", "Google Chrome")),
|
|
1486
|
+
"computer_open_url": lambda a: computer_open_url(a["url"], a.get("app", "Google Chrome")),
|
|
1487
|
+
"computer_click": lambda a: computer_click(a.get("x", 0), a.get("y", 0), a.get("button", "left"), a.get("double", False)),
|
|
1488
|
+
"computer_type": lambda a: computer_type(a["text"], a.get("interval", 0.04)),
|
|
1489
|
+
"computer_key": lambda a: computer_key(a["key"]),
|
|
1490
|
+
"computer_scroll": lambda a: computer_scroll(a.get("x", 0), a.get("y", 0), a.get("direction", "down"), a.get("clicks", 3)),
|
|
1491
|
+
"computer_move": lambda a: computer_move(a.get("x", 0), a.get("y", 0)),
|
|
1492
|
+
"computer_drag": lambda a: computer_drag(a.get("x1", 0), a.get("y1", 0), a.get("x2", 0), a.get("y2", 0)),
|
|
1493
|
+
"computer_status": lambda a: computer_status(),
|
|
1494
|
+
"chrome_status": lambda a: desktop_bridge_status(),
|
|
1495
|
+
"computer_use_status": lambda a: desktop_bridge_status(),
|
|
1496
|
+
# knowledge / obsidian
|
|
1497
|
+
"knowledge_save": lambda a: knowledge_save(a["content"], a.get("folder", "00_Raw"), a.get("title")),
|
|
1498
|
+
"knowledge_search": lambda a: knowledge_search(a["query"], a.get("max_results", 5)),
|
|
1499
|
+
"knowledge_tree": lambda a: knowledge_tree(),
|
|
1500
|
+
"obsidian_save": lambda a: obsidian_save(a["content"], a.get("folder", "00_Raw"), a.get("title")),
|
|
1501
|
+
"obsidian_search": lambda a: obsidian_search(a["query"], a.get("max_results", 5)),
|
|
1502
|
+
"obsidian_tree": lambda a: obsidian_tree(),
|
|
1503
|
+
# git (read-only)
|
|
1504
|
+
"git_status": lambda a: git_status(a.get("cwd")),
|
|
1505
|
+
"git_diff": lambda a: git_diff(a.get("path"), a.get("cwd")),
|
|
1506
|
+
"git_log": lambda a: git_log(a.get("max_count", 5), a.get("cwd")),
|
|
1507
|
+
"git_show": lambda a: git_show(a.get("revision", "HEAD"), a.get("cwd")),
|
|
1508
|
+
# exec
|
|
1509
|
+
"run_command": lambda a: run_command(a["command"], a.get("cwd")),
|
|
1510
|
+
"build_project": lambda a: build_project(a.get("cwd"), a.get("script", "build")),
|
|
1511
|
+
"deploy_project": lambda a: deploy_project(a.get("cwd"), a.get("script", "deploy")),
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def registered_tools() -> frozenset:
|
|
1516
|
+
"""Names dispatchable through ``execute_tool`` — the seam other modules verify against."""
|
|
1517
|
+
return frozenset(TOOL_HANDLERS)
|
|
1518
|
+
|
|
1519
|
+
|
|
1438
1520
|
def execute_tool(action: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
if action == "read_file":
|
|
1444
|
-
return read_file(
|
|
1445
|
-
args["path"],
|
|
1446
|
-
offset=args.get("offset", 0),
|
|
1447
|
-
limit=args.get("limit", 0),
|
|
1448
|
-
line_numbers=args.get("line_numbers", True),
|
|
1449
|
-
)
|
|
1450
|
-
if action == "write_file":
|
|
1451
|
-
return write_file(args["path"], args.get("content", ""))
|
|
1452
|
-
if action == "edit_file":
|
|
1453
|
-
return edit_file(
|
|
1454
|
-
args["path"],
|
|
1455
|
-
args["old_string"],
|
|
1456
|
-
args["new_string"],
|
|
1457
|
-
replace_all=bool(args.get("replace_all", False)),
|
|
1458
|
-
)
|
|
1459
|
-
if action == "grep":
|
|
1460
|
-
return grep(
|
|
1461
|
-
args["pattern"],
|
|
1462
|
-
path=args.get("path", "."),
|
|
1463
|
-
glob=args.get("glob"),
|
|
1464
|
-
max_results=args.get("max_results", 50),
|
|
1465
|
-
case_insensitive=bool(args.get("case_insensitive", False)),
|
|
1466
|
-
context_lines=args.get("context_lines", 0),
|
|
1467
|
-
)
|
|
1468
|
-
if action == "search_files":
|
|
1469
|
-
return search_files(args["query"], args.get("path", "."), args.get("max_results", 20))
|
|
1470
|
-
if action == "todo_read":
|
|
1471
|
-
return todo_read()
|
|
1472
|
-
if action == "todo_write":
|
|
1473
|
-
return todo_write(args.get("todos") or [])
|
|
1474
|
-
if action == "inspect_html":
|
|
1475
|
-
return inspect_html(args["path"])
|
|
1476
|
-
if action == "preview_url":
|
|
1477
|
-
return preview_url(args.get("path", "index.html"))
|
|
1478
|
-
if action == "create_docx":
|
|
1479
|
-
return create_docx(args.get("title", ""), args.get("body", ""), args.get("filename", "document.docx"))
|
|
1480
|
-
if action == "create_xlsx":
|
|
1481
|
-
rows = args.get("rows", [])
|
|
1482
|
-
if isinstance(rows, str):
|
|
1483
|
-
rows = json.loads(rows)
|
|
1484
|
-
return create_xlsx(rows, args.get("filename", "spreadsheet.xlsx"), args.get("sheet_name", "Sheet1"))
|
|
1485
|
-
if action == "create_pptx":
|
|
1486
|
-
slides = args.get("slides", [])
|
|
1487
|
-
if isinstance(slides, str):
|
|
1488
|
-
slides = json.loads(slides)
|
|
1489
|
-
return create_pptx(args.get("title", ""), slides, args.get("filename", "presentation.pptx"))
|
|
1490
|
-
if action == "create_pdf":
|
|
1491
|
-
return create_pdf(args.get("title", ""), args.get("body", ""), args.get("filename", "document.pdf"))
|
|
1492
|
-
if action == "create_web_project":
|
|
1493
|
-
return create_web_project(args.get("path", ""), args.get("framework", "react"), args.get("template", "vite"))
|
|
1494
|
-
if action == "local_list":
|
|
1495
|
-
return local_list(args["path"])
|
|
1496
|
-
if action == "local_read":
|
|
1497
|
-
return local_read(args["path"])
|
|
1498
|
-
if action == "local_write":
|
|
1499
|
-
return local_write(args["path"], args.get("content", ""))
|
|
1500
|
-
if action == "read_document":
|
|
1501
|
-
return read_document(args["path"])
|
|
1502
|
-
if action == "network_status":
|
|
1503
|
-
return network_status()
|
|
1504
|
-
if action == "computer_screenshot":
|
|
1505
|
-
return computer_screenshot()
|
|
1506
|
-
if action == "computer_open_app":
|
|
1507
|
-
return computer_open_app(args.get("app", "Google Chrome"))
|
|
1508
|
-
if action == "computer_open_url":
|
|
1509
|
-
return computer_open_url(args["url"], args.get("app", "Google Chrome"))
|
|
1510
|
-
if action == "computer_click":
|
|
1511
|
-
return computer_click(args.get("x", 0), args.get("y", 0), args.get("button", "left"), args.get("double", False))
|
|
1512
|
-
if action == "computer_type":
|
|
1513
|
-
return computer_type(args["text"], args.get("interval", 0.04))
|
|
1514
|
-
if action == "computer_key":
|
|
1515
|
-
return computer_key(args["key"])
|
|
1516
|
-
if action == "computer_scroll":
|
|
1517
|
-
return computer_scroll(args.get("x", 0), args.get("y", 0), args.get("direction", "down"), args.get("clicks", 3))
|
|
1518
|
-
if action == "computer_move":
|
|
1519
|
-
return computer_move(args.get("x", 0), args.get("y", 0))
|
|
1520
|
-
if action == "computer_drag":
|
|
1521
|
-
return computer_drag(args.get("x1", 0), args.get("y1", 0), args.get("x2", 0), args.get("y2", 0))
|
|
1522
|
-
if action == "computer_status":
|
|
1523
|
-
return computer_status()
|
|
1524
|
-
if action in {"chrome_status", "computer_use_status"}:
|
|
1525
|
-
return desktop_bridge_status()
|
|
1526
|
-
if action == "knowledge_save":
|
|
1527
|
-
return knowledge_save(args["content"], args.get("folder", "00_Raw"), args.get("title"))
|
|
1528
|
-
if action == "knowledge_search":
|
|
1529
|
-
return knowledge_search(args["query"], args.get("max_results", 5))
|
|
1530
|
-
if action == "knowledge_tree":
|
|
1531
|
-
return knowledge_tree()
|
|
1532
|
-
if action == "obsidian_save":
|
|
1533
|
-
return obsidian_save(args["content"], args.get("folder", "00_Raw"), args.get("title"))
|
|
1534
|
-
if action == "obsidian_search":
|
|
1535
|
-
return obsidian_search(args["query"], args.get("max_results", 5))
|
|
1536
|
-
if action == "obsidian_tree":
|
|
1537
|
-
return obsidian_tree()
|
|
1538
|
-
if action == "git_status":
|
|
1539
|
-
return git_status(args.get("cwd"))
|
|
1540
|
-
if action == "git_diff":
|
|
1541
|
-
return git_diff(args.get("path"), args.get("cwd"))
|
|
1542
|
-
if action == "git_log":
|
|
1543
|
-
return git_log(args.get("max_count", 5), args.get("cwd"))
|
|
1544
|
-
if action == "git_show":
|
|
1545
|
-
return git_show(args.get("revision", "HEAD"), args.get("cwd"))
|
|
1546
|
-
if action == "run_command":
|
|
1547
|
-
return run_command(args["command"], args.get("cwd"))
|
|
1548
|
-
if action == "build_project":
|
|
1549
|
-
return build_project(args.get("cwd"), args.get("script", "build"))
|
|
1550
|
-
if action == "deploy_project":
|
|
1551
|
-
return deploy_project(args.get("cwd"), args.get("script", "deploy"))
|
|
1552
|
-
raise ToolError(f"Unknown action: {action}")
|
|
1521
|
+
handler = TOOL_HANDLERS.get(action)
|
|
1522
|
+
if handler is None:
|
|
1523
|
+
raise ToolError(f"Unknown action: {action}")
|
|
1524
|
+
return handler(args)
|