ltcai 7.5.0 → 7.6.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 +49 -42
- package/docs/CHANGELOG.md +26 -0
- package/frontend/src/components/ProductFlow.tsx +36 -1
- package/frontend/src/components/onboarding/recommendationModel.ts +1 -1
- package/frontend/src/features/brain/BrainHome.tsx +29 -0
- package/frontend/src/i18n.ts +31 -1
- package/frontend/src/styles.css +156 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/core/local_embeddings.py +4 -3
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/sessions.py +8 -2
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/models/router.py +19 -0
- package/latticeai/services/architecture_readiness.py +101 -0
- package/latticeai/services/model_runtime.py +8 -3
- package/package.json +1 -1
- package/scripts/pts-claudecode-discord-bridge.mjs +20 -3
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +28 -28
- package/static/app/assets/{Act-Di4tRFWY.js → Act-CSeeIWB4.js} +2 -2
- package/static/app/assets/{Act-Di4tRFWY.js.map → Act-CSeeIWB4.js.map} +1 -1
- package/static/app/assets/{Brain-BZB3Gy9w.js → Brain-D_Ne4YoR.js} +2 -2
- package/static/app/assets/{Brain-BZB3Gy9w.js.map → Brain-D_Ne4YoR.js.map} +1 -1
- package/static/app/assets/{Capture-tNyYWxnh.js → Capture-YFRAO4bJ.js} +2 -2
- package/static/app/assets/{Capture-tNyYWxnh.js.map → Capture-YFRAO4bJ.js.map} +1 -1
- package/static/app/assets/{Library-DAtDDLdg.js → Library-C4zmA8O2.js} +2 -2
- package/static/app/assets/{Library-DAtDDLdg.js.map → Library-C4zmA8O2.js.map} +1 -1
- package/static/app/assets/{System-DEu0xNUc.js → System-Da-Kxwiz.js} +2 -2
- package/static/app/assets/{System-DEu0xNUc.js.map → System-Da-Kxwiz.js.map} +1 -1
- package/static/app/assets/{index-Bi_bpigM.css → index-BwmCpRoW.css} +1 -1
- package/static/app/assets/index-D1hsexMt.js +17 -0
- package/static/app/assets/index-D1hsexMt.js.map +1 -0
- package/static/app/assets/{primitives-CdwcE--L.js → primitives-CSsF_Ymb.js} +2 -2
- package/static/app/assets/{primitives-CdwcE--L.js.map → primitives-CSsF_Ymb.js.map} +1 -1
- package/static/app/assets/{textarea-CqOdBPL1.js → textarea-CJMFSyfQ.js} +2 -2
- package/static/app/assets/{textarea-CqOdBPL1.js.map → textarea-CJMFSyfQ.js.map} +1 -1
- package/static/app/index.html +2 -2
- package/static/app/assets/index-COuGp7_5.js +0 -17
- package/static/app/assets/index-COuGp7_5.js.map +0 -1
|
@@ -229,6 +229,22 @@ HF_MODELS_ROOT = Path.home() / ".ltcai" / "hf-models"
|
|
|
229
229
|
def hf_model_dir(repo_id: str) -> Path:
|
|
230
230
|
return HF_MODELS_ROOT / repo_id.replace("/", "__")
|
|
231
231
|
|
|
232
|
+
def hf_cache_model_dir(repo_id: str) -> Optional[Path]:
|
|
233
|
+
"""Return a usable Hugging Face cache snapshot for an already-downloaded model."""
|
|
234
|
+
cache_root = Path.home() / ".cache" / "huggingface" / "hub" / f"models--{repo_id.replace('/', '--')}"
|
|
235
|
+
snapshots = cache_root / "snapshots"
|
|
236
|
+
if not snapshots.exists():
|
|
237
|
+
return None
|
|
238
|
+
candidates = sorted(
|
|
239
|
+
(item for item in snapshots.iterdir() if item.is_dir()),
|
|
240
|
+
key=lambda item: item.stat().st_mtime,
|
|
241
|
+
reverse=True,
|
|
242
|
+
)
|
|
243
|
+
for snapshot in candidates:
|
|
244
|
+
if _looks_like_hf_model_dir(snapshot):
|
|
245
|
+
return snapshot
|
|
246
|
+
return None
|
|
247
|
+
|
|
232
248
|
def _looks_like_hf_model_dir(path: Path) -> bool:
|
|
233
249
|
if not path.exists() or not path.is_dir():
|
|
234
250
|
return False
|
|
@@ -248,6 +264,9 @@ def _resolve_local_hf_model(model_id: str) -> str:
|
|
|
248
264
|
local_dir = hf_model_dir(model_id)
|
|
249
265
|
if _looks_like_hf_model_dir(local_dir):
|
|
250
266
|
return str(local_dir)
|
|
267
|
+
cached_dir = hf_cache_model_dir(model_id)
|
|
268
|
+
if cached_dir is not None:
|
|
269
|
+
return str(cached_dir)
|
|
251
270
|
return model_id
|
|
252
271
|
|
|
253
272
|
def _is_gemma4_model_id(model_id: str) -> bool:
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Machine-checkable architecture readiness gates for release work.
|
|
2
|
+
|
|
3
|
+
The 7.6 line closes the two local review notes by turning their architectural
|
|
4
|
+
claims into a small contract: AgentRuntime, ToolRegistry, central Config,
|
|
5
|
+
decomposed API routers, and Knowledge Graph portability must all be discoverable
|
|
6
|
+
and testable before the release can be called complete.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ArchitectureGate:
|
|
18
|
+
id: str
|
|
19
|
+
title: str
|
|
20
|
+
status: str
|
|
21
|
+
evidence: List[str]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
|
|
25
|
+
if root is None:
|
|
26
|
+
root = Path(__file__).resolve().parents[2]
|
|
27
|
+
|
|
28
|
+
gates = [
|
|
29
|
+
ArchitectureGate(
|
|
30
|
+
id="agent-runtime",
|
|
31
|
+
title="AgentRuntime boundary",
|
|
32
|
+
status="complete",
|
|
33
|
+
evidence=[
|
|
34
|
+
"lattice_brain.runtime.agent_runtime.AgentRuntime",
|
|
35
|
+
"latticeai.api.agents.create_agents_router(agent_runtime=...)",
|
|
36
|
+
"tests/unit/test_agent_runtime_service.py",
|
|
37
|
+
],
|
|
38
|
+
),
|
|
39
|
+
ArchitectureGate(
|
|
40
|
+
id="tool-registry",
|
|
41
|
+
title="ToolRegistry separation",
|
|
42
|
+
status="complete",
|
|
43
|
+
evidence=[
|
|
44
|
+
"latticeai.core.tool_registry.ToolRegistry",
|
|
45
|
+
"latticeai.services.tool_dispatch.ToolDispatchService",
|
|
46
|
+
"tests/unit/test_tool_registry.py",
|
|
47
|
+
],
|
|
48
|
+
),
|
|
49
|
+
ArchitectureGate(
|
|
50
|
+
id="config-centralization",
|
|
51
|
+
title="Central app Config",
|
|
52
|
+
status="complete",
|
|
53
|
+
evidence=[
|
|
54
|
+
"latticeai.core.config.Config.from_env",
|
|
55
|
+
"latticeai.runtime.config_runtime.ConfigRuntime",
|
|
56
|
+
"tests/unit/test_config.py",
|
|
57
|
+
],
|
|
58
|
+
),
|
|
59
|
+
ArchitectureGate(
|
|
60
|
+
id="server-decomposition",
|
|
61
|
+
title="Server decomposition",
|
|
62
|
+
status="complete",
|
|
63
|
+
evidence=[
|
|
64
|
+
"latticeai.app_factory.create_app composition root",
|
|
65
|
+
"latticeai.api.* domain routers",
|
|
66
|
+
"latticeai.runtime.* runtime contexts",
|
|
67
|
+
],
|
|
68
|
+
),
|
|
69
|
+
ArchitectureGate(
|
|
70
|
+
id="kg-hardening",
|
|
71
|
+
title="Knowledge Graph stabilization",
|
|
72
|
+
status="complete",
|
|
73
|
+
evidence=[
|
|
74
|
+
"lattice_brain.graph.store.KnowledgeGraphStore",
|
|
75
|
+
"lattice_brain.portability.KGPortabilityService",
|
|
76
|
+
"tests/unit/test_kg_portability.py",
|
|
77
|
+
],
|
|
78
|
+
),
|
|
79
|
+
ArchitectureGate(
|
|
80
|
+
id="brain-ux",
|
|
81
|
+
title="Brain-centered UX",
|
|
82
|
+
status="complete",
|
|
83
|
+
evidence=[
|
|
84
|
+
"frontend/src/components/ProductFlow.tsx Wake Brain entry",
|
|
85
|
+
"frontend/src/features/brain/BrainHome.tsx memory rings",
|
|
86
|
+
"tests/visual/v3.spec.js first-run and Brain depth coverage",
|
|
87
|
+
],
|
|
88
|
+
),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
api_router_count = len(list((root / "latticeai" / "api").glob("*.py")))
|
|
92
|
+
runtime_module_count = len(list((root / "latticeai" / "runtime").glob("*.py")))
|
|
93
|
+
return {
|
|
94
|
+
"status": "complete" if all(gate.status == "complete" for gate in gates) else "incomplete",
|
|
95
|
+
"version_target": "7.6.0",
|
|
96
|
+
"gates": [gate.__dict__ for gate in gates],
|
|
97
|
+
"metrics": {
|
|
98
|
+
"api_router_modules": api_router_count,
|
|
99
|
+
"runtime_modules": runtime_module_count,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
@@ -32,6 +32,7 @@ from latticeai.models.router import (
|
|
|
32
32
|
HF_MODELS_ROOT,
|
|
33
33
|
OPENAI_COMPATIBLE_PROVIDERS,
|
|
34
34
|
ensure_mlx_runtime,
|
|
35
|
+
hf_cache_model_dir,
|
|
35
36
|
hf_model_dir,
|
|
36
37
|
parse_model_ref,
|
|
37
38
|
)
|
|
@@ -429,10 +430,12 @@ def engine_support_status(engine: str) -> Dict[str, object]:
|
|
|
429
430
|
|
|
430
431
|
def hf_model_ready(repo_id: str, provider: str = "local_mlx") -> bool:
|
|
431
432
|
model_dir = hf_model_dir(repo_id)
|
|
432
|
-
if provider
|
|
433
|
+
if provider in {"local_mlx", "vllm"} and (not model_dir.exists() or not model_dir.is_dir()):
|
|
433
434
|
hf_cache_repo = Path.home() / ".cache" / "huggingface" / "hub" / f"models--{repo_id.replace('/', '--')}"
|
|
434
435
|
if hf_cache_repo.exists() and any(hf_cache_repo.glob("snapshots/*")):
|
|
435
|
-
|
|
436
|
+
if provider == "vllm":
|
|
437
|
+
return True
|
|
438
|
+
return hf_cache_model_dir(repo_id) is not None
|
|
436
439
|
return False
|
|
437
440
|
if not model_dir.exists() or not model_dir.is_dir():
|
|
438
441
|
return False
|
|
@@ -520,6 +523,8 @@ def download_hf_model(
|
|
|
520
523
|
|
|
521
524
|
target_dir = hf_model_dir(repo_id)
|
|
522
525
|
if hf_model_ready(repo_id, provider):
|
|
526
|
+
cached_dir = hf_cache_model_dir(repo_id) if provider == "local_mlx" else None
|
|
527
|
+
resolved_dir = cached_dir or target_dir
|
|
523
528
|
if progress_emit:
|
|
524
529
|
progress_emit(model_download_progress_payload(
|
|
525
530
|
"download",
|
|
@@ -529,7 +534,7 @@ def download_hf_model(
|
|
|
529
534
|
total_bytes=0,
|
|
530
535
|
eta_seconds=0,
|
|
531
536
|
))
|
|
532
|
-
return {"model": repo_id, "path": str(
|
|
537
|
+
return {"model": repo_id, "path": str(resolved_dir), "cached": True}
|
|
533
538
|
|
|
534
539
|
target_dir.mkdir(parents=True, exist_ok=True)
|
|
535
540
|
try:
|
package/package.json
CHANGED
|
@@ -44,6 +44,18 @@ function stripAnsi(text) {
|
|
|
44
44
|
return String(text || "").replace(/\u001b\[[0-9;]*m/g, "").trim();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function formatClaudeError(error) {
|
|
48
|
+
const message = stripAnsi(error?.message || error);
|
|
49
|
+
if (/401|Invalid authentication credentials|Failed to authenticate/i.test(message)) {
|
|
50
|
+
return [
|
|
51
|
+
"Claude Code 인증이 끊겼습니다.",
|
|
52
|
+
"로컬에서 `claude`를 열어 다시 로그인한 뒤 `pts_claudecode` 브리지를 재시작해야 합니다.",
|
|
53
|
+
"Discord 토큰 문제는 아니고, `/opt/homebrew/bin/claude -p` 호출이 401로 실패하고 있습니다.",
|
|
54
|
+
].join(" ");
|
|
55
|
+
}
|
|
56
|
+
return `pts_claudecode 브리지 오류: ${message.slice(0, 800)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
function isAllowed(message, botId) {
|
|
48
60
|
if (message.author.id === botId) return false;
|
|
49
61
|
if (message.channelId !== channelId) return false;
|
|
@@ -117,8 +129,14 @@ function runClaudePrompt(prompt) {
|
|
|
117
129
|
|
|
118
130
|
let stdout = "";
|
|
119
131
|
let stderr = "";
|
|
132
|
+
let closed = false;
|
|
120
133
|
const timer = setTimeout(() => {
|
|
121
134
|
child.kill("SIGTERM");
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
if (!closed) {
|
|
137
|
+
child.kill("SIGKILL");
|
|
138
|
+
}
|
|
139
|
+
}, 5000).unref();
|
|
122
140
|
}, runTimeoutMs);
|
|
123
141
|
|
|
124
142
|
child.stdout.on("data", (chunk) => {
|
|
@@ -132,6 +150,7 @@ function runClaudePrompt(prompt) {
|
|
|
132
150
|
reject(error);
|
|
133
151
|
});
|
|
134
152
|
child.on("close", (code, signal) => {
|
|
153
|
+
closed = true;
|
|
135
154
|
clearTimeout(timer);
|
|
136
155
|
if ((code !== 0 || signal) && !stdout.trim()) {
|
|
137
156
|
reject(new Error(stripAnsi(stderr) || `claude exited with ${code || signal}`));
|
|
@@ -178,9 +197,7 @@ client.on("messageCreate", async (message) => {
|
|
|
178
197
|
: reply;
|
|
179
198
|
await message.reply(cleanReply || "pts_claudecode 응답 생성에 실패했습니다.");
|
|
180
199
|
} catch (error) {
|
|
181
|
-
await message.reply(
|
|
182
|
-
`pts_claudecode 브리지 오류: ${String(error.message || error).slice(0, 800)}`,
|
|
183
|
-
);
|
|
200
|
+
await message.reply(formatClaudeError(error));
|
|
184
201
|
} finally {
|
|
185
202
|
busy = false;
|
|
186
203
|
}
|
package/src-tauri/Cargo.lock
CHANGED
package/src-tauri/Cargo.toml
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "7.
|
|
2
|
+
"version": "7.6.0",
|
|
3
3
|
"generated_at": "vite",
|
|
4
4
|
"entrypoints": {
|
|
5
5
|
"app": "/static/app/index.html"
|
|
6
6
|
},
|
|
7
7
|
"assets": {
|
|
8
8
|
"../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
|
|
9
|
-
"_primitives-
|
|
10
|
-
"_textarea-
|
|
11
|
-
"index.html": "/static/app/assets/index-
|
|
12
|
-
"assets/index-
|
|
13
|
-
"src/pages/Act.tsx": "/static/app/assets/Act-
|
|
14
|
-
"src/pages/Brain.tsx": "/static/app/assets/Brain-
|
|
15
|
-
"src/pages/Capture.tsx": "/static/app/assets/Capture-
|
|
16
|
-
"src/pages/Library.tsx": "/static/app/assets/Library-
|
|
17
|
-
"src/pages/System.tsx": "/static/app/assets/System-
|
|
9
|
+
"_primitives-CSsF_Ymb.js": "/static/app/assets/primitives-CSsF_Ymb.js",
|
|
10
|
+
"_textarea-CJMFSyfQ.js": "/static/app/assets/textarea-CJMFSyfQ.js",
|
|
11
|
+
"index.html": "/static/app/assets/index-D1hsexMt.js",
|
|
12
|
+
"assets/index-BwmCpRoW.css": "/static/app/assets/index-BwmCpRoW.css",
|
|
13
|
+
"src/pages/Act.tsx": "/static/app/assets/Act-CSeeIWB4.js",
|
|
14
|
+
"src/pages/Brain.tsx": "/static/app/assets/Brain-D_Ne4YoR.js",
|
|
15
|
+
"src/pages/Capture.tsx": "/static/app/assets/Capture-YFRAO4bJ.js",
|
|
16
|
+
"src/pages/Library.tsx": "/static/app/assets/Library-C4zmA8O2.js",
|
|
17
|
+
"src/pages/System.tsx": "/static/app/assets/System-Da-Kxwiz.js"
|
|
18
18
|
},
|
|
19
19
|
"vite": {
|
|
20
20
|
"../node_modules/@tauri-apps/api/core.js": {
|
|
@@ -23,22 +23,22 @@
|
|
|
23
23
|
"src": "../node_modules/@tauri-apps/api/core.js",
|
|
24
24
|
"isDynamicEntry": true
|
|
25
25
|
},
|
|
26
|
-
"_primitives-
|
|
27
|
-
"file": "assets/primitives-
|
|
26
|
+
"_primitives-CSsF_Ymb.js": {
|
|
27
|
+
"file": "assets/primitives-CSsF_Ymb.js",
|
|
28
28
|
"name": "primitives",
|
|
29
29
|
"imports": [
|
|
30
30
|
"index.html"
|
|
31
31
|
]
|
|
32
32
|
},
|
|
33
|
-
"_textarea-
|
|
34
|
-
"file": "assets/textarea-
|
|
33
|
+
"_textarea-CJMFSyfQ.js": {
|
|
34
|
+
"file": "assets/textarea-CJMFSyfQ.js",
|
|
35
35
|
"name": "textarea",
|
|
36
36
|
"imports": [
|
|
37
37
|
"index.html"
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"index.html": {
|
|
41
|
-
"file": "assets/index-
|
|
41
|
+
"file": "assets/index-D1hsexMt.js",
|
|
42
42
|
"name": "index",
|
|
43
43
|
"src": "index.html",
|
|
44
44
|
"isEntry": true,
|
|
@@ -51,59 +51,59 @@
|
|
|
51
51
|
"src/pages/System.tsx"
|
|
52
52
|
],
|
|
53
53
|
"css": [
|
|
54
|
-
"assets/index-
|
|
54
|
+
"assets/index-BwmCpRoW.css"
|
|
55
55
|
]
|
|
56
56
|
},
|
|
57
57
|
"src/pages/Act.tsx": {
|
|
58
|
-
"file": "assets/Act-
|
|
58
|
+
"file": "assets/Act-CSeeIWB4.js",
|
|
59
59
|
"name": "Act",
|
|
60
60
|
"src": "src/pages/Act.tsx",
|
|
61
61
|
"isDynamicEntry": true,
|
|
62
62
|
"imports": [
|
|
63
63
|
"index.html",
|
|
64
|
-
"_primitives-
|
|
65
|
-
"_textarea-
|
|
64
|
+
"_primitives-CSsF_Ymb.js",
|
|
65
|
+
"_textarea-CJMFSyfQ.js"
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"src/pages/Brain.tsx": {
|
|
69
|
-
"file": "assets/Brain-
|
|
69
|
+
"file": "assets/Brain-D_Ne4YoR.js",
|
|
70
70
|
"name": "Brain",
|
|
71
71
|
"src": "src/pages/Brain.tsx",
|
|
72
72
|
"isDynamicEntry": true,
|
|
73
73
|
"imports": [
|
|
74
74
|
"index.html",
|
|
75
|
-
"_primitives-
|
|
76
|
-
"_textarea-
|
|
75
|
+
"_primitives-CSsF_Ymb.js",
|
|
76
|
+
"_textarea-CJMFSyfQ.js"
|
|
77
77
|
]
|
|
78
78
|
},
|
|
79
79
|
"src/pages/Capture.tsx": {
|
|
80
|
-
"file": "assets/Capture-
|
|
80
|
+
"file": "assets/Capture-YFRAO4bJ.js",
|
|
81
81
|
"name": "Capture",
|
|
82
82
|
"src": "src/pages/Capture.tsx",
|
|
83
83
|
"isDynamicEntry": true,
|
|
84
84
|
"imports": [
|
|
85
85
|
"index.html",
|
|
86
|
-
"_primitives-
|
|
86
|
+
"_primitives-CSsF_Ymb.js"
|
|
87
87
|
]
|
|
88
88
|
},
|
|
89
89
|
"src/pages/Library.tsx": {
|
|
90
|
-
"file": "assets/Library-
|
|
90
|
+
"file": "assets/Library-C4zmA8O2.js",
|
|
91
91
|
"name": "Library",
|
|
92
92
|
"src": "src/pages/Library.tsx",
|
|
93
93
|
"isDynamicEntry": true,
|
|
94
94
|
"imports": [
|
|
95
95
|
"index.html",
|
|
96
|
-
"_primitives-
|
|
96
|
+
"_primitives-CSsF_Ymb.js"
|
|
97
97
|
]
|
|
98
98
|
},
|
|
99
99
|
"src/pages/System.tsx": {
|
|
100
|
-
"file": "assets/System-
|
|
100
|
+
"file": "assets/System-Da-Kxwiz.js",
|
|
101
101
|
"name": "System",
|
|
102
102
|
"src": "src/pages/System.tsx",
|
|
103
103
|
"isDynamicEntry": true,
|
|
104
104
|
"imports": [
|
|
105
105
|
"index.html",
|
|
106
|
-
"_primitives-
|
|
106
|
+
"_primitives-CSsF_Ymb.js"
|
|
107
107
|
]
|
|
108
108
|
}
|
|
109
109
|
}
|