ltcai 8.8.0 → 9.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 +33 -37
- package/auto_setup.py +84 -70
- package/docs/CHANGELOG.md +113 -237
- package/docs/CODE_REVIEW_2026-07-06.md +764 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +3 -3
- package/docs/DEVELOPMENT.md +10 -10
- package/docs/LEGACY_COMPATIBILITY.md +1 -1
- package/docs/ONBOARDING.md +2 -2
- package/docs/PRODUCT_DIRECTION_REVIEW.md +3 -2
- package/docs/ROADMAP_RECOMMENDATIONS.md +61 -36
- package/docs/TRUST_MODEL.md +5 -1
- package/docs/WHY_LATTICE.md +4 -3
- package/docs/architecture.md +4 -0
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +4 -9
- package/lattice_brain/conversations.py +156 -21
- package/lattice_brain/embeddings.py +38 -2
- package/lattice_brain/graph/_kg_common.py +39 -496
- package/lattice_brain/graph/_kg_constants.py +243 -0
- package/lattice_brain/graph/_kg_fsutil.py +297 -0
- package/lattice_brain/graph/discovery.py +0 -948
- package/lattice_brain/graph/discovery_index.py +972 -0
- package/lattice_brain/graph/json_utils.py +25 -0
- package/lattice_brain/graph/retrieval.py +66 -597
- package/lattice_brain/graph/retrieval_docgen.py +210 -0
- package/lattice_brain/graph/retrieval_vector.py +460 -0
- package/lattice_brain/graph/runtime.py +16 -0
- package/lattice_brain/graph/store.py +6 -0
- package/lattice_brain/ingestion.py +68 -0
- package/lattice_brain/portability.py +1 -9
- package/lattice_brain/quality.py +98 -4
- package/lattice_brain/runtime/agent_runtime.py +166 -0
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/utils.py +28 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/chat.py +368 -418
- package/latticeai/api/chat_helpers.py +227 -0
- package/latticeai/api/computer_use.py +149 -31
- package/latticeai/api/marketplace.py +11 -0
- package/latticeai/api/mcp.py +3 -2
- package/latticeai/api/models.py +4 -1
- package/latticeai/api/permissions.py +72 -33
- package/latticeai/api/setup.py +17 -2
- package/latticeai/api/tools.py +105 -62
- package/latticeai/app_factory.py +101 -296
- package/latticeai/core/agent.py +25 -7
- package/latticeai/core/io_utils.py +37 -0
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/local_embeddings.py +2 -4
- package/latticeai/core/marketplace.py +33 -2
- package/latticeai/core/mcp_catalog.py +450 -0
- package/latticeai/core/mcp_registry.py +2 -441
- package/latticeai/core/sessions.py +11 -3
- package/latticeai/core/tool_registry.py +15 -4
- package/latticeai/core/users.py +4 -9
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/core/workspace_os_utils.py +3 -17
- package/latticeai/integrations/telegram_bot.py +7 -2
- package/latticeai/models/model_providers.py +111 -0
- package/latticeai/models/router.py +58 -136
- package/latticeai/runtime/audit_runtime.py +27 -16
- package/latticeai/runtime/automation_runtime.py +9 -0
- package/latticeai/runtime/bootstrap.py +1 -1
- package/latticeai/runtime/history_runtime.py +163 -0
- package/latticeai/runtime/namespace_runtime.py +173 -0
- package/latticeai/runtime/network_config_runtime.py +56 -0
- package/latticeai/runtime/sso_config_runtime.py +128 -0
- package/latticeai/runtime/user_key_runtime.py +106 -0
- package/latticeai/services/app_context.py +1 -0
- package/latticeai/services/architecture_readiness.py +2 -2
- package/latticeai/services/memory_service.py +213 -0
- package/latticeai/services/model_engines.py +79 -12
- package/latticeai/services/model_runtime.py +24 -4
- package/latticeai/services/platform_runtime.py +9 -1
- package/latticeai/services/process_audit.py +208 -0
- package/latticeai/services/product_readiness.py +11 -11
- package/latticeai/services/review_queue.py +64 -11
- package/latticeai/services/run_executor.py +21 -0
- package/latticeai/services/search_service.py +106 -30
- package/latticeai/services/setup_detection.py +80 -0
- package/latticeai/services/tool_dispatch.py +66 -0
- package/latticeai/services/workspace_service.py +15 -0
- package/package.json +1 -1
- package/scripts/check_i18n_literals.mjs +20 -8
- package/scripts/i18n_literal_allowlist.json +34 -0
- package/scripts/lint_frontend.mjs +6 -2
- package/setup_wizard.py +196 -74
- 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 +11 -11
- package/static/app/assets/{Act-C7K9wsO9.js → Act-21lIXx2E.js} +1 -1
- package/static/app/assets/Brain-BqUd5UJJ.js +321 -0
- package/static/app/assets/{Capture-B3V4_5Xp.js → Capture-BA7Z2Q1u.js} +1 -1
- package/static/app/assets/{Library-Cgj-EF50.js → Library-bFMtyni3.js} +1 -1
- package/static/app/assets/System-K6krGCqn.js +1 -0
- package/static/app/assets/index-C4R3ws30.js +17 -0
- package/static/app/assets/index-ChSeOB02.css +2 -0
- package/static/app/assets/primitives-sQU3it5I.js +1 -0
- package/static/app/assets/{textarea-CVQkN2Tk.js → textarea-DK3Fd_lR.js} +1 -1
- package/static/app/index.html +2 -2
- package/static/css/tokens.css +4 -2
- package/static/sw.js +1 -1
- package/tools/local_files.py +6 -0
- package/latticeai/runtime/sso_runtime.py +0 -52
- package/static/app/assets/Brain-I1OSzxJu.js +0 -321
- package/static/app/assets/System-D1Lkei3I.js +0 -1
- package/static/app/assets/index--P0ksosz.js +0 -17
- package/static/app/assets/index-DCh5AoXt.css +0 -2
- package/static/app/assets/primitives-BLqaKk5g.js +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lattice AI
|
|
2
2
|
|
|
3
|
-
**Lattice AI
|
|
3
|
+
**Lattice AI 9.0.0 is the local-first Digital Brain platform. This release closes the July 8 code-review follow-up, hardens chat/runtime reliability, pays down cleanup debt across runtime utilities and setup detection, and keeps package, desktop, extension, static app, and documentation metadata synchronized to the 9.0.0 line.**
|
|
4
4
|
|
|
5
5
|
**Lattice AI는 모델이 바뀌어도 내 지식과 맥락을 보존하는 로컬 우선 AI 브레인입니다.**
|
|
6
6
|
|
|
@@ -67,7 +67,9 @@ You need Lattice AI when:
|
|
|
67
67
|
The screenshots below are the latest checked-in visual evidence captures. They
|
|
68
68
|
keep the first-run Brain flow, memory graph, source capture, model library,
|
|
69
69
|
system view, admin console, and review center visible as release gates while
|
|
70
|
-
|
|
70
|
+
9.0.0 focuses on code-review closure, chat/runtime reliability,
|
|
71
|
+
AgentRuntime and ToolRegistry maintainability, release artifact correctness,
|
|
72
|
+
and frontend/runtime cleanup.
|
|
71
73
|
|
|
72
74
|
### 1. Wake Brain
|
|
73
75
|
|
|
@@ -169,7 +171,9 @@ pip install "ltcai[local]"
|
|
|
169
171
|
- **Backend**: FastAPI on localhost is the UI source of truth.
|
|
170
172
|
- **Brain Core**: independent `lattice_brain` package for graph, memory,
|
|
171
173
|
context, conversations, ingestion, runtime, workflow, storage, and portability.
|
|
172
|
-
- **Storage**: SQLite
|
|
174
|
+
- **Storage**: SQLite is the live local Brain store; PostgreSQL/pgvector tooling
|
|
175
|
+
is optional scale-mode planning/migration support, not the default live graph
|
|
176
|
+
backend.
|
|
173
177
|
- **Portability**: encrypted `.latticebrain` archives plus backup, restore,
|
|
174
178
|
inspect, verify, import dry-run, and confirmed restore/import flows.
|
|
175
179
|
- **Trust boundary**: local-first by default; cloud calls, downloads, Telegram,
|
|
@@ -206,38 +210,38 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
|
|
|
206
210
|
|
|
207
211
|
## Current Release
|
|
208
212
|
|
|
209
|
-
The current release is **
|
|
210
|
-
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
- `dist/ltcai-
|
|
227
|
-
- `
|
|
228
|
-
- `ltcai-
|
|
229
|
-
- `
|
|
230
|
-
- `src-tauri/target/release/bundle/dmg/Lattice AI_8.8.0_aarch64.dmg`
|
|
213
|
+
The current release is **9.0.0 — Code Review Closure & Runtime Cleanup**:
|
|
214
|
+
|
|
215
|
+
- July 8 code-review follow-ups are fixed across chat, agent runs, permissions,
|
|
216
|
+
Telegram integration, runtime audit timestamps, embeddings, and Brain UI copy.
|
|
217
|
+
- Chat/document streaming now preserves terminal SSE events and history/trace
|
|
218
|
+
persistence when generation fails mid-stream.
|
|
219
|
+
- Runtime audit appends use JSONL while legacy JSON reads remain compatible.
|
|
220
|
+
- Shared JSON/ISO/hash, setup detection, and frontend helper utilities replace
|
|
221
|
+
duplicated implementations across runtime, Brain Core, setup, and React code.
|
|
222
|
+
- The legacy `server_app` namespace is allowlist-based instead of exposing all
|
|
223
|
+
app-factory locals.
|
|
224
|
+
- The main `/chat` handler now routes through focused intent handlers and shared
|
|
225
|
+
response epilogues for lower future regression risk.
|
|
226
|
+
|
|
227
|
+
Expected artifacts for 9.0.0 release must use exact filenames:
|
|
228
|
+
|
|
229
|
+
- `dist/ltcai-9.0.0-py3-none-any.whl`
|
|
230
|
+
- `dist/ltcai-9.0.0.tar.gz`
|
|
231
|
+
- `ltcai-9.0.0.tgz`
|
|
232
|
+
- `dist/ltcai-9.0.0.vsix`
|
|
233
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_9.0.0_aarch64.dmg`
|
|
231
234
|
|
|
232
235
|
Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
|
|
233
236
|
|
|
234
237
|
See [docs/ROADMAP_RECOMMENDATIONS.md](docs/ROADMAP_RECOMMENDATIONS.md) for the
|
|
235
|
-
strategic roadmap slices applied through
|
|
238
|
+
strategic roadmap slices applied through 9.0.0 and the follow-up tracks.
|
|
236
239
|
|
|
237
240
|
## Known Limitations
|
|
238
241
|
|
|
239
242
|
- External package registries are owner-published and can lag behind GitHub.
|
|
240
|
-
- PostgreSQL/pgvector is optional scale
|
|
243
|
+
- PostgreSQL/pgvector is optional scale/migration tooling. SQLite remains the
|
|
244
|
+
live local Brain store in 9.0.0.
|
|
241
245
|
- Docker, model downloads, cloud model calls, Telegram, Brain Network, and update
|
|
242
246
|
checks require explicit user action.
|
|
243
247
|
- Conversation does not fabricate answers when no model is loaded.
|
|
@@ -249,6 +253,8 @@ strategic roadmap slices applied through 8.8.0 and the follow-up tracks.
|
|
|
249
253
|
|
|
250
254
|
| Version | Theme |
|
|
251
255
|
| --- | --- |
|
|
256
|
+
| 9.0.0 | Code Review Closure & Runtime Cleanup: July 8 code-review follow-ups fixed, chat/runtime reliability improved, duplicated utility surfaces consolidated, runtime audit append paths moved to JSONL, and release metadata/artifacts synchronized |
|
|
257
|
+
| 8.9.0 | Scoped Memory & Tool Policy Hardening: authenticated history/KG reads are workspace-scoped, direct Tool API paths enforce registry policy, local approvals hash tokens at rest, AgentRuntime approval semantics are explicit, and frontend/runtime seams are split |
|
|
252
258
|
| 8.8.0 | Brain Core Extraction & Recall Proof Hardening: internal-only Brain shim layers are removed, AgentRuntime run contracts/retry budgets are tighter, Brain Chat gains conversation controls, and citation recall exposes matched evidence |
|
|
253
259
|
| 8.7.0 | Runtime State Hygiene & Release Evidence Refresh: model-runtime internals prefer typed state over legacy globals, compatibility sync is deprecated, 8.7.0 visual evidence is refreshed, and all release metadata/docs are synchronized |
|
|
254
260
|
| 8.6.0 | Desktop Capture & Navigation Reliability: native folder selection works from the Tauri localhost app, picker failures surface in Capture, web saving remains one-action, and the Brain shell sidebar/admin flow is CI-covered |
|
|
@@ -258,16 +264,6 @@ strategic roadmap slices applied through 8.8.0 and the follow-up tracks.
|
|
|
258
264
|
| 8.2.0 | Brain Brief: evidence-backed home briefing, honest empty-state guidance, recall/graph/model-proof next actions, and continued model/workspace runtime extraction |
|
|
259
265
|
| 8.1.0 | Intuitive Brain Home: living Brain, recent memory, connected topic, next action, and composer are visible in one product-first screen with refreshed 8.1.0 evidence and artifacts |
|
|
260
266
|
| 8.0.0 | Runtime Architecture Contract: AgentRuntime, ToolRegistry, central Config, server decomposition, and KG hardening are captured as machine-checkable release boundaries with exact 8.0.0 artifacts |
|
|
261
|
-
| 7.9.0 | Agent Runtime Boundary Hardening: explicit `SingleAgentRuntime`, compatibility alias preservation, injected rollback port, and release/readiness docs aligned to the product AgentRuntime facade |
|
|
262
|
-
| 7.8.0 | Brain Chat Home UX Simplification: chat-first first viewport, visible workspace navigation, collapsed source/status utilities, hidden default depth controls, and removal of obsolete Brain UX components |
|
|
263
|
-
| 7.7.0 | Complete Product Polish: command-center Brain Home, repaired Review Center evidence, exact 7.7 docs/artifacts, product readiness gate, and stronger CI/release checks |
|
|
264
|
-
| 7.6.0 | Brain-Centered UX & Architecture Closure: Wake Brain first-run surface, concentric memory rings with direct depth controls, and machine-checkable closure of the two architecture/UX review files |
|
|
265
|
-
| 7.5.0 | Runtime Debt Burn-down & Release Risk Cleanup: API consumers get normalized contract views, retrieval quality uses a 250+ record corpus fixture, stale artifact risk is removed, npm audit is clean, and Tauri is updated past the old block warning |
|
|
266
|
-
| 7.4.0 | Runtime Contract Convergence & Corpus Retrieval: agent/workflow/audit/realtime records share the agent-run-contract/v1 family, and retrieval quality gates run against a real corpus-scale fixture |
|
|
267
|
-
| 7.3.0 | Runtime Contract & Retrieval Quality: shared agent-run contract across runtimes plus deterministic hybrid recall/ranking regression gates |
|
|
268
|
-
| 7.2.0 | Runtime Trust Baseline: agent run preview/readiness, simulation-mode guardrails, live ToolRegistry manifest/diagnostics, and tests for dispatch/governance/catalog drift |
|
|
269
|
-
| 7.1.0 | Brain Usability Completion: clearer first-run onboarding, ingestion progress/emergence, richer graph controls, inline answer proof, workspace/profile/admin discovery, empty/error/consent feedback, and VS Code sync status |
|
|
270
|
-
| 7.0.0 | Brain Productization Loop: first-screen ingestion for files/folders/notes/web, answer-level memory proof and source citations, model-continuity demo flow, five-minute first-run loop, and recall/KG quality eval in CI |
|
|
271
267
|
|
|
272
268
|
## Documentation
|
|
273
269
|
|
package/auto_setup.py
CHANGED
|
@@ -25,7 +25,7 @@ Lattice AI — Zero-Config Auto Setup
|
|
|
25
25
|
python3 auto_setup.py probe # 1단계만
|
|
26
26
|
python3 auto_setup.py recommend # 1+2단계
|
|
27
27
|
python3 auto_setup.py plan # 1+2+3 (설치 계획 출력)
|
|
28
|
-
python3 auto_setup.py plan --apply
|
|
28
|
+
python3 auto_setup.py plan --apply --confirm-token <token>
|
|
29
29
|
python3 auto_setup.py verify # 4단계 단독
|
|
30
30
|
python3 auto_setup.py preset # 5단계
|
|
31
31
|
python3 auto_setup.py all # 전체 흐름
|
|
@@ -47,6 +47,20 @@ from dataclasses import asdict, dataclass, field
|
|
|
47
47
|
from pathlib import Path
|
|
48
48
|
from typing import Any, Dict, List, Optional, Tuple
|
|
49
49
|
|
|
50
|
+
from latticeai.services.process_audit import (
|
|
51
|
+
CommandConfirmationError,
|
|
52
|
+
append_process_audit_event,
|
|
53
|
+
command_plan,
|
|
54
|
+
command_plan_for_commands,
|
|
55
|
+
require_command_confirmation,
|
|
56
|
+
)
|
|
57
|
+
from latticeai.services.setup_detection import (
|
|
58
|
+
detect_cuda,
|
|
59
|
+
detect_tools,
|
|
60
|
+
detect_wsl_from_text,
|
|
61
|
+
parse_windows_video_controllers as _parse_windows_video_controllers,
|
|
62
|
+
)
|
|
63
|
+
|
|
50
64
|
__all__ = [
|
|
51
65
|
"SystemProfile", "Recommendation", "InstallPlan",
|
|
52
66
|
"probe", "recommend", "plan", "verify", "preset", "run_all",
|
|
@@ -145,44 +159,6 @@ def _which(binary: str) -> Optional[str]:
|
|
|
145
159
|
return None
|
|
146
160
|
|
|
147
161
|
|
|
148
|
-
def _parse_windows_video_controllers(raw: str) -> List[Dict[str, Any]]:
|
|
149
|
-
controllers: List[Dict[str, Any]] = []
|
|
150
|
-
if not raw:
|
|
151
|
-
return controllers
|
|
152
|
-
try:
|
|
153
|
-
data = json.loads(raw)
|
|
154
|
-
if isinstance(data, dict):
|
|
155
|
-
data = [data]
|
|
156
|
-
if isinstance(data, list):
|
|
157
|
-
for item in data:
|
|
158
|
-
name = str(item.get("Name") or "").strip()
|
|
159
|
-
if not name:
|
|
160
|
-
continue
|
|
161
|
-
try:
|
|
162
|
-
ram_mb = int(item.get("AdapterRAM") or 0) // (1024 * 1024)
|
|
163
|
-
except Exception:
|
|
164
|
-
ram_mb = 0
|
|
165
|
-
controllers.append({"name": name, "vram_mb": ram_mb})
|
|
166
|
-
if controllers:
|
|
167
|
-
return controllers
|
|
168
|
-
except Exception:
|
|
169
|
-
pass
|
|
170
|
-
current: Dict[str, Any] = {}
|
|
171
|
-
for line in raw.splitlines():
|
|
172
|
-
if line.startswith("Name="):
|
|
173
|
-
if current:
|
|
174
|
-
controllers.append(current)
|
|
175
|
-
current = {"name": line.split("=", 1)[-1].strip(), "vram_mb": 0}
|
|
176
|
-
elif line.startswith("AdapterRAM=") and current:
|
|
177
|
-
try:
|
|
178
|
-
current["vram_mb"] = int(line.split("=", 1)[-1].strip()) // (1024 * 1024)
|
|
179
|
-
except ValueError:
|
|
180
|
-
current["vram_mb"] = 0
|
|
181
|
-
if current:
|
|
182
|
-
controllers.append(current)
|
|
183
|
-
return controllers
|
|
184
|
-
|
|
185
|
-
|
|
186
162
|
def _detect_gpu(prof_os: str, arch: str) -> GPUInfo:
|
|
187
163
|
"""OS별 휴리스틱으로 GPU 감지. 외부 라이브러리 없이 가능한 만큼만."""
|
|
188
164
|
gpu = GPUInfo()
|
|
@@ -268,36 +244,17 @@ def _detect_package_manager(prof_os: str) -> Optional[str]:
|
|
|
268
244
|
|
|
269
245
|
|
|
270
246
|
def _detect_tools() -> Dict[str, str]:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
found = _which(binary)
|
|
274
|
-
if found:
|
|
275
|
-
tools[binary] = found
|
|
276
|
-
return tools
|
|
247
|
+
detected = detect_tools(_which, ("ollama", "lms", "nvidia-smi", "nvcc", "winget", "brew", "apt", "git", "node", "python", "python3"))
|
|
248
|
+
return {binary: path for binary, path in detected.items() if path}
|
|
277
249
|
|
|
278
250
|
|
|
279
251
|
def _detect_wsl(prof_os: str) -> Tuple[bool, str]:
|
|
280
|
-
|
|
281
|
-
return False, ""
|
|
282
|
-
raw = _read_text("/proc/version")
|
|
283
|
-
is_wsl = "microsoft" in raw.lower() or "wsl" in raw.lower()
|
|
284
|
-
version = "2" if "microsoft-standard" in raw.lower() or "wsl2" in raw.lower() else ("1" if is_wsl else "")
|
|
285
|
-
return is_wsl, version
|
|
252
|
+
return detect_wsl_from_text(prof_os, _read_text("/proc/version"))
|
|
286
253
|
|
|
287
254
|
|
|
288
255
|
def _detect_cuda() -> Tuple[bool, str]:
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
version = ""
|
|
292
|
-
if nvidia_smi:
|
|
293
|
-
raw = _run([nvidia_smi, "--query-gpu=driver_version", "--format=csv,noheader"], timeout=4.0)
|
|
294
|
-
version = raw.splitlines()[0].strip() if raw.splitlines() else ""
|
|
295
|
-
if nvcc:
|
|
296
|
-
raw = _run([nvcc, "--version"], timeout=4.0)
|
|
297
|
-
m = re.search(r"release\s+([\d.]+)", raw)
|
|
298
|
-
if m:
|
|
299
|
-
version = m.group(1)
|
|
300
|
-
return bool(nvidia_smi or nvcc), version
|
|
256
|
+
available, version, _nvidia_smi, _nvcc = detect_cuda(_which, lambda args: _run(args, timeout=4.0))
|
|
257
|
+
return available, version
|
|
301
258
|
|
|
302
259
|
|
|
303
260
|
def _detect_cpu_details(prof_os: str) -> Tuple[str, int, int, List[str]]:
|
|
@@ -547,6 +504,21 @@ class InstallStep:
|
|
|
547
504
|
command: List[str]
|
|
548
505
|
requires_admin: bool = False
|
|
549
506
|
|
|
507
|
+
def to_json(self) -> Dict[str, Any]:
|
|
508
|
+
return {
|
|
509
|
+
"name": self.name,
|
|
510
|
+
"why": self.why,
|
|
511
|
+
"command": self.command,
|
|
512
|
+
"requires_admin": self.requires_admin,
|
|
513
|
+
"command_plan": command_plan(
|
|
514
|
+
self.command,
|
|
515
|
+
name=self.name,
|
|
516
|
+
purpose="auto_setup_install",
|
|
517
|
+
requires_admin=self.requires_admin,
|
|
518
|
+
metadata={"why": self.why},
|
|
519
|
+
),
|
|
520
|
+
}
|
|
521
|
+
|
|
550
522
|
|
|
551
523
|
@dataclass
|
|
552
524
|
class InstallPlan:
|
|
@@ -555,10 +527,18 @@ class InstallPlan:
|
|
|
555
527
|
notes: List[str] = field(default_factory=list)
|
|
556
528
|
|
|
557
529
|
def to_json(self) -> Dict[str, Any]:
|
|
530
|
+
plan_summary = command_plan_for_commands(
|
|
531
|
+
[step.command for step in self.steps],
|
|
532
|
+
name="auto_setup_plan",
|
|
533
|
+
purpose="auto_setup_install",
|
|
534
|
+
metadata={"package_manager": self.package_manager},
|
|
535
|
+
)
|
|
558
536
|
return {
|
|
559
537
|
"package_manager": self.package_manager,
|
|
560
|
-
"steps": [
|
|
538
|
+
"steps": [s.to_json() for s in self.steps],
|
|
561
539
|
"notes": self.notes,
|
|
540
|
+
"command_plan": plan_summary,
|
|
541
|
+
"confirmation_token": plan_summary["confirmation_token"],
|
|
562
542
|
}
|
|
563
543
|
|
|
564
544
|
|
|
@@ -661,23 +641,55 @@ def plan(profile: SystemProfile, rec: Recommendation) -> InstallPlan:
|
|
|
661
641
|
return InstallPlan(package_manager=pm, steps=steps, notes=notes)
|
|
662
642
|
|
|
663
643
|
|
|
664
|
-
def apply_plan(
|
|
665
|
-
|
|
644
|
+
def apply_plan(
|
|
645
|
+
plan_obj: InstallPlan,
|
|
646
|
+
*,
|
|
647
|
+
confirm: bool = False,
|
|
648
|
+
confirmation_token: Optional[str] = None,
|
|
649
|
+
) -> List[Dict[str, Any]]:
|
|
650
|
+
"""위험: 실제로 설치 명령을 실행한다. ``confirm`` and token are required."""
|
|
666
651
|
if not confirm:
|
|
667
652
|
raise RuntimeError("refuse to apply: pass confirm=True")
|
|
653
|
+
expected = plan_obj.to_json().get("confirmation_token")
|
|
654
|
+
if str(confirmation_token or "").strip() != expected:
|
|
655
|
+
raise CommandConfirmationError("refuse to apply: confirmation token mismatch")
|
|
668
656
|
results: List[Dict[str, Any]] = []
|
|
669
657
|
for step in plan_obj.steps:
|
|
658
|
+
step_plan = command_plan(
|
|
659
|
+
step.command,
|
|
660
|
+
name=step.name,
|
|
661
|
+
purpose="auto_setup_install",
|
|
662
|
+
requires_admin=step.requires_admin,
|
|
663
|
+
metadata={"why": step.why},
|
|
664
|
+
)
|
|
670
665
|
try:
|
|
666
|
+
require_command_confirmation(
|
|
667
|
+
step.command,
|
|
668
|
+
step_plan["confirmation_token"],
|
|
669
|
+
purpose="auto_setup_install",
|
|
670
|
+
)
|
|
671
|
+
append_process_audit_event("installer_command", plan=step_plan, status="started")
|
|
671
672
|
r = subprocess.run(step.command, capture_output=True, text=True,
|
|
672
673
|
timeout=300, check=False)
|
|
674
|
+
append_process_audit_event(
|
|
675
|
+
"installer_command",
|
|
676
|
+
plan=step_plan,
|
|
677
|
+
status="finished",
|
|
678
|
+
returncode=r.returncode,
|
|
679
|
+
stdout=r.stdout,
|
|
680
|
+
stderr=r.stderr,
|
|
681
|
+
)
|
|
673
682
|
results.append({
|
|
674
683
|
"name": step.name,
|
|
684
|
+
"command_hash": step_plan["command_hash"],
|
|
685
|
+
"command_preview": step_plan["command_preview"],
|
|
675
686
|
"returncode": r.returncode,
|
|
676
687
|
"stdout_tail": (r.stdout or "")[-2000:],
|
|
677
688
|
"stderr_tail": (r.stderr or "")[-2000:],
|
|
678
689
|
})
|
|
679
690
|
except Exception as exc:
|
|
680
|
-
|
|
691
|
+
append_process_audit_event("installer_command", plan=step_plan, status="error", error=str(exc))
|
|
692
|
+
results.append({"name": step.name, "command_hash": step_plan["command_hash"], "error": str(exc)})
|
|
681
693
|
return results
|
|
682
694
|
|
|
683
695
|
|
|
@@ -780,13 +792,13 @@ def preset(profile: SystemProfile, rec: Recommendation) -> Dict[str, Any]:
|
|
|
780
792
|
|
|
781
793
|
|
|
782
794
|
# ── orchestrator ────────────────────────────────────────────────────────────
|
|
783
|
-
def run_all(*, apply_install: bool = False) -> Dict[str, Any]:
|
|
795
|
+
def run_all(*, apply_install: bool = False, confirmation_token: Optional[str] = None) -> Dict[str, Any]:
|
|
784
796
|
p = probe()
|
|
785
797
|
r = recommend(p)
|
|
786
798
|
pl = plan(p, r)
|
|
787
799
|
install_results = None
|
|
788
800
|
if apply_install:
|
|
789
|
-
install_results = apply_plan(pl, confirm=True)
|
|
801
|
+
install_results = apply_plan(pl, confirm=True, confirmation_token=confirmation_token)
|
|
790
802
|
v = verify(p, r)
|
|
791
803
|
ps = preset(p, r)
|
|
792
804
|
return {
|
|
@@ -809,6 +821,8 @@ def _main() -> int:
|
|
|
809
821
|
sp_plan = sub.add_parser("plan")
|
|
810
822
|
sp_plan.add_argument("--apply", action="store_true",
|
|
811
823
|
help="actually run the install commands (DANGER)")
|
|
824
|
+
sp_plan.add_argument("--confirm-token", default=None,
|
|
825
|
+
help="confirmation token from the dry-run plan output")
|
|
812
826
|
sub.add_parser("verify")
|
|
813
827
|
sub.add_parser("preset")
|
|
814
828
|
sub.add_parser("all")
|
|
@@ -825,7 +839,7 @@ def _main() -> int:
|
|
|
825
839
|
p = probe(); r = recommend(p); pl = plan(p, r)
|
|
826
840
|
out: Dict[str, Any] = {"plan": pl.to_json()}
|
|
827
841
|
if args.apply:
|
|
828
|
-
out["install"] = apply_plan(pl, confirm=True)
|
|
842
|
+
out["install"] = apply_plan(pl, confirm=True, confirmation_token=args.confirm_token)
|
|
829
843
|
print(json.dumps(out, indent=2, ensure_ascii=False)); return 0
|
|
830
844
|
if args.cmd == "verify":
|
|
831
845
|
p = probe(); r = recommend(p)
|