ltcai 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -16
- package/docs/CHANGELOG.md +46 -0
- package/frontend/src/App.tsx +8 -2
- package/frontend/src/api/client.ts +2 -0
- package/frontend/src/pages/Act.tsx +82 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/__init__.py +53 -0
- package/lattice_brain/runtime/agent_runtime.py +7 -0
- package/lattice_brain/runtime/hooks.py +6 -0
- package/lattice_brain/runtime/multi_agent.py +7 -2
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/workflow_designer.py +60 -0
- package/latticeai/app_factory.py +1 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/brain_automation.py +214 -0
- package/latticeai/services/triggers.py +61 -4
- package/package.json +1 -1
- 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 +5 -5
- package/static/app/assets/index-C7vzwUjU.js +16 -0
- package/static/app/assets/index-C7vzwUjU.js.map +1 -0
- package/static/app/assets/index-HN4f2wbe.css +2 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-CQmHhk8Q.css +0 -2
- package/static/app/assets/index-sOXTFUQc.js +0 -16
- package/static/app/assets/index-sOXTFUQc.js.map +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Consent-first Brain automation recipes.
|
|
2
|
+
|
|
3
|
+
The recipes here are product-level starter workflows, not hidden background
|
|
4
|
+
jobs. Installing one creates a disabled draft workflow so the user can inspect
|
|
5
|
+
and enable it deliberately.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from copy import deepcopy
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Dict, List
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class BrainAutomationRecipe:
|
|
17
|
+
id: str
|
|
18
|
+
name: str
|
|
19
|
+
summary: str
|
|
20
|
+
user_value: str
|
|
21
|
+
cadence: str
|
|
22
|
+
trigger: Dict[str, Any]
|
|
23
|
+
prompt: str
|
|
24
|
+
creates: List[str]
|
|
25
|
+
|
|
26
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
27
|
+
return {
|
|
28
|
+
"id": self.id,
|
|
29
|
+
"name": self.name,
|
|
30
|
+
"summary": self.summary,
|
|
31
|
+
"user_value": self.user_value,
|
|
32
|
+
"cadence": self.cadence,
|
|
33
|
+
"trigger": deepcopy(self.trigger),
|
|
34
|
+
"creates": list(self.creates),
|
|
35
|
+
"consent": {
|
|
36
|
+
"default_state": "draft_disabled",
|
|
37
|
+
"local_only": True,
|
|
38
|
+
"external_actions": False,
|
|
39
|
+
"requires_user_enable": True,
|
|
40
|
+
"review_before_run": True,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_RECIPES: List[BrainAutomationRecipe] = [
|
|
46
|
+
BrainAutomationRecipe(
|
|
47
|
+
id="daily-memory-digest",
|
|
48
|
+
name="Daily Memory Digest",
|
|
49
|
+
summary="Collects the day's new memories into a short review draft.",
|
|
50
|
+
user_value="Users see what the Brain kept today without searching through chats.",
|
|
51
|
+
cadence="daily",
|
|
52
|
+
trigger={"trigger": "interval", "interval_seconds": 86_400},
|
|
53
|
+
prompt=(
|
|
54
|
+
"Review today's new Brain memories and draft a concise digest with "
|
|
55
|
+
"important decisions, unresolved questions, and suggested next actions. "
|
|
56
|
+
"Do not contact external services."
|
|
57
|
+
),
|
|
58
|
+
creates=["memory digest", "decision summary", "next-action suggestions"],
|
|
59
|
+
),
|
|
60
|
+
BrainAutomationRecipe(
|
|
61
|
+
id="weekly-project-review",
|
|
62
|
+
name="Weekly Project Review",
|
|
63
|
+
summary="Turns project context into a weekly checkpoint draft.",
|
|
64
|
+
user_value="Users can restart a project without explaining the week again.",
|
|
65
|
+
cadence="weekly",
|
|
66
|
+
trigger={"trigger": "interval", "interval_seconds": 604_800},
|
|
67
|
+
prompt=(
|
|
68
|
+
"Review this workspace's recent memories, workflow runs, and decisions. "
|
|
69
|
+
"Draft a project checkpoint with progress, risks, blockers, and next steps. "
|
|
70
|
+
"Keep it local and ask before any external action."
|
|
71
|
+
),
|
|
72
|
+
creates=["project checkpoint", "risk list", "next-week plan"],
|
|
73
|
+
),
|
|
74
|
+
BrainAutomationRecipe(
|
|
75
|
+
id="follow-up-radar",
|
|
76
|
+
name="Follow-up Radar",
|
|
77
|
+
summary="Looks for follow-up candidates when new knowledge enters the Brain.",
|
|
78
|
+
user_value="Users get gentle reminders for loose ends without a noisy task system.",
|
|
79
|
+
cadence="when new memory is saved",
|
|
80
|
+
trigger={"trigger": "brain_event"},
|
|
81
|
+
prompt=(
|
|
82
|
+
"Inspect the new Brain memory for follow-up signals such as decisions, "
|
|
83
|
+
"promises, deadlines, unresolved questions, or 'later' language. "
|
|
84
|
+
"Return suggestions only; do not create tasks without approval."
|
|
85
|
+
),
|
|
86
|
+
creates=["follow-up suggestions", "open-question list", "approval-ready task drafts"],
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
_RECIPE_BY_ID = {recipe.id: recipe for recipe in _RECIPES}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def list_brain_automation_recipes() -> Dict[str, Any]:
|
|
94
|
+
"""Return user-facing, consent-first automation recipe metadata."""
|
|
95
|
+
return {
|
|
96
|
+
"recipes": [recipe.as_dict() for recipe in _RECIPES],
|
|
97
|
+
"principles": {
|
|
98
|
+
"local_first": True,
|
|
99
|
+
"drafts_before_automation": True,
|
|
100
|
+
"no_external_actions_without_consent": True,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def find_installed_recipe_workflow(
|
|
106
|
+
workflows: Any, recipe_id: str
|
|
107
|
+
) -> Dict[str, Any] | None:
|
|
108
|
+
"""Return an existing draft installed from ``recipe_id``, if any.
|
|
109
|
+
|
|
110
|
+
Installing a recipe is idempotent: clicking "Create reviewable draft" twice
|
|
111
|
+
should surface the existing draft instead of accumulating duplicates. We
|
|
112
|
+
match on the ``brain_automation_recipe`` provenance metadata stamped by
|
|
113
|
+
:func:`build_brain_automation_workflow`.
|
|
114
|
+
"""
|
|
115
|
+
for workflow in workflows or []:
|
|
116
|
+
metadata = (workflow or {}).get("metadata") or {}
|
|
117
|
+
if (
|
|
118
|
+
metadata.get("created_from") == "brain_automation_recipe"
|
|
119
|
+
and metadata.get("recipe_id") == recipe_id
|
|
120
|
+
):
|
|
121
|
+
return workflow
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def build_brain_automation_workflow(recipe_id: str, *, enabled: bool = False) -> Dict[str, Any]:
|
|
126
|
+
"""Build a workflow definition for a recipe.
|
|
127
|
+
|
|
128
|
+
``enabled`` defaults to ``False`` so installing a recipe creates an
|
|
129
|
+
inspectable draft. The trigger service treats explicit ``enabled: false`` as
|
|
130
|
+
disarmed, while legacy workflows without the field keep their behavior.
|
|
131
|
+
"""
|
|
132
|
+
recipe = _RECIPE_BY_ID.get(recipe_id)
|
|
133
|
+
if recipe is None:
|
|
134
|
+
raise KeyError(recipe_id)
|
|
135
|
+
|
|
136
|
+
trigger_config = {
|
|
137
|
+
**deepcopy(recipe.trigger),
|
|
138
|
+
"enabled": bool(enabled),
|
|
139
|
+
"consent_required": True,
|
|
140
|
+
"local_only": True,
|
|
141
|
+
"external_actions": False,
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
"name": recipe.name,
|
|
145
|
+
"nodes": [
|
|
146
|
+
{
|
|
147
|
+
"id": "trigger",
|
|
148
|
+
"type": "trigger",
|
|
149
|
+
"name": "User-enabled schedule" if recipe.trigger["trigger"] == "interval" else "New Brain memory",
|
|
150
|
+
"config": trigger_config,
|
|
151
|
+
"next": "draft",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "draft",
|
|
155
|
+
"type": "agent",
|
|
156
|
+
"name": "Draft Brain review",
|
|
157
|
+
"config": {
|
|
158
|
+
"agent": "agent:planner",
|
|
159
|
+
"prompt": recipe.prompt,
|
|
160
|
+
"mode": "draft",
|
|
161
|
+
"local_only": True,
|
|
162
|
+
"external_actions": False,
|
|
163
|
+
"requires_review": True,
|
|
164
|
+
},
|
|
165
|
+
"next": "output",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "output",
|
|
169
|
+
"type": "output",
|
|
170
|
+
"name": "Review before saving",
|
|
171
|
+
"config": {
|
|
172
|
+
"value": "Draft ready for review. Save, edit, or discard it before it becomes durable memory.",
|
|
173
|
+
},
|
|
174
|
+
"next": None,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
"metadata": {
|
|
178
|
+
"created_from": "brain_automation_recipe",
|
|
179
|
+
"recipe_id": recipe.id,
|
|
180
|
+
"recipe_summary": recipe.summary,
|
|
181
|
+
"recipe_user_value": recipe.user_value,
|
|
182
|
+
"automation_state": "enabled" if enabled else "draft_disabled",
|
|
183
|
+
"local_only": True,
|
|
184
|
+
"external_actions": False,
|
|
185
|
+
"requires_user_enable": not enabled,
|
|
186
|
+
"creates": list(recipe.creates),
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# === A방향 (Act/automation + BrainAutomationPanel) E2E 시나리오 초안 ===
|
|
192
|
+
# (backend 인터페이스 list_brain_automation_recipes / find_installed... / build_... 완료 후 즉시 작성)
|
|
193
|
+
# 1. Recipe 목록 노출: frontend BrainAutomationPanel이 list_brain_automation_recipes() 호출 → recipes + consent metadata 표시.
|
|
194
|
+
# 2. "Create reviewable draft" 클릭:
|
|
195
|
+
# - build_brain_automation_workflow(recipe_id, enabled=False) 로 draft 생성 (metadata.recipe_id + created_from=brain_automation_recipe)
|
|
196
|
+
# - find_installed_recipe_workflow 로 사전 dedup 체크 → 이미 있으면 기존 반환, UI disabled + "✓ Reviewable draft ready" 피드백.
|
|
197
|
+
# - 생성된 workflow는 automation_state="draft_disabled", trigger.enabled=False → TriggerService 무시.
|
|
198
|
+
# 3. Dedup guard (UI + backend): metadata.recipe_id + created_from 기준. 중복 클릭 가드 (no-dup) 로 double submit 방지.
|
|
199
|
+
# 4. User가 draft를 review/수정 후 enabled=True 로 전환 → TriggerService._triggered_workflows 에서 enabled True인 것만 arm.
|
|
200
|
+
# 5. Interval trigger E2E:
|
|
201
|
+
# - reconcile_missed() : 다운타임 중 missed → "skipped" 이벤트 기록 (catch-up storm 없음).
|
|
202
|
+
# - tick_intervals() : last_fired_at + interval + last_attempt_at dedup 가드 (10s cooldown) 로 중복 실행 방지.
|
|
203
|
+
# - LATTICE_TZ 환경변수: describe()에 "tz" 노출, 이벤트 at 값은 epoch이지만 클라이언트가 LATTICE_TZ로 현지화.
|
|
204
|
+
# 6. Brain event trigger E2E: kg_ingest.* post_tool hook → on_brain_event → matching source_type 필터 → _fire (dedup 5s).
|
|
205
|
+
# 7. Failure degraded:
|
|
206
|
+
# - _fire 에서 run_workflow 예외 → _record_fire_outcome → consecutive_failures++ , describe() "status":"degraded" ( >=3 ).
|
|
207
|
+
# - 성공 시 reset. (실행 내부 실패는 workflow run record에 남음, scheduler는 launch health만).
|
|
208
|
+
# 8. Run provenance: fired run의 inputs["__trigger__"] = {"type": "interval"|"brain_event", ...} 로 감사/디버그 가능.
|
|
209
|
+
# 9. Consent-first: draft_disabled 기본, user enable 전까지 절대 실행 안 됨. "review_before_run": True.
|
|
210
|
+
# 10. End-to-end Act: draft → enable → trigger fire → agent:planner "Draft Brain review" 노드 → output (requires_review) → user review → save or discard.
|
|
211
|
+
#
|
|
212
|
+
# 다음: 실제 API (e.g. POST /brain/automation/install-draft) 가 UI에서 호출되면 위 시나리오에 대한 통합 테스트 + RunExecutor 경로 검증 즉시 추가.
|
|
213
|
+
# 현재 상태: backend recipe 인터페이스 + TriggerService edge 하드닝 + AgentRuntime wiring 완료. UI (App.tsx + styles) + test_brain_automation.py 는 별도 완료 보고됨.
|
|
214
|
+
|
|
@@ -20,11 +20,17 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import json
|
|
22
22
|
import logging
|
|
23
|
+
import os
|
|
23
24
|
import threading
|
|
24
25
|
import time
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
from typing import Any, Callable, Dict, List, Optional
|
|
27
28
|
|
|
29
|
+
try:
|
|
30
|
+
from zoneinfo import ZoneInfo
|
|
31
|
+
except Exception: # pragma: no cover
|
|
32
|
+
ZoneInfo = None # type: ignore
|
|
33
|
+
|
|
28
34
|
DEFAULT_TICK_SECONDS = 5.0
|
|
29
35
|
MIN_INTERVAL_SECONDS = 60
|
|
30
36
|
|
|
@@ -51,6 +57,15 @@ class TriggerService:
|
|
|
51
57
|
self._stop_event = threading.Event()
|
|
52
58
|
self._thread: Optional[threading.Thread] = None
|
|
53
59
|
self._lock = threading.Lock()
|
|
60
|
+
# LATTICE_TZ: wall-clock / display 용. interval 계산은 여전히 unix seconds (duration 기반, drift 방지).
|
|
61
|
+
# describe()와 이벤트에 tz 정보 노출. calendar "daily at HH:MM" semantics 는 추후 cron 확장 시 사용.
|
|
62
|
+
self._tz_name = os.environ.get("LATTICE_TZ") or "UTC"
|
|
63
|
+
self._tz = None
|
|
64
|
+
if ZoneInfo is not None:
|
|
65
|
+
try:
|
|
66
|
+
self._tz = ZoneInfo(self._tz_name)
|
|
67
|
+
except Exception:
|
|
68
|
+
self._tz = ZoneInfo("UTC") if ZoneInfo else None
|
|
54
69
|
|
|
55
70
|
# ── durable state ──────────────────────────────────────────────────────
|
|
56
71
|
def _load_state(self) -> Dict[str, Any]:
|
|
@@ -86,27 +101,37 @@ class TriggerService:
|
|
|
86
101
|
continue
|
|
87
102
|
cfg = node.get("config") or {}
|
|
88
103
|
kind = str(cfg.get("trigger") or "manual")
|
|
104
|
+
if cfg.get("enabled") is False:
|
|
105
|
+
continue
|
|
89
106
|
if kind in ("interval", "brain_event"):
|
|
90
107
|
found.append({"workflow": wf, "node": node, "kind": kind, "config": cfg})
|
|
91
108
|
return found
|
|
92
109
|
|
|
93
110
|
def describe(self) -> Dict[str, Any]:
|
|
94
|
-
"""Honest status surface: what is armed, when it last fired/skipped.
|
|
111
|
+
"""Honest status surface: what is armed, when it last fired/skipped.
|
|
112
|
+
Includes LATTICE_TZ, per-trigger status (armed|degraded), consecutive_failures.
|
|
113
|
+
"""
|
|
95
114
|
state = self._load_state()
|
|
96
115
|
armed = []
|
|
97
116
|
for item in self._triggered_workflows():
|
|
98
117
|
wf_id = item["workflow"].get("id")
|
|
118
|
+
entry = state.get(wf_id) or {}
|
|
119
|
+
fails = int(entry.get("consecutive_failures", 0))
|
|
120
|
+
status = "degraded" if fails >= 3 else "armed"
|
|
99
121
|
armed.append({
|
|
100
122
|
"workflow_id": wf_id,
|
|
101
123
|
"name": item["workflow"].get("name"),
|
|
102
124
|
"kind": item["kind"],
|
|
103
125
|
"config": {k: v for k, v in item["config"].items() if k != "trigger"},
|
|
104
|
-
"last_fired_at":
|
|
105
|
-
"
|
|
126
|
+
"last_fired_at": entry.get("last_fired_at"),
|
|
127
|
+
"status": status,
|
|
128
|
+
"consecutive_failures": fails,
|
|
129
|
+
"recent_events": entry.get("events", [])[-5:],
|
|
106
130
|
})
|
|
107
131
|
return {
|
|
108
132
|
"running": bool(self._thread and self._thread.is_alive()),
|
|
109
133
|
"tick_seconds": self._tick,
|
|
134
|
+
"tz": self._tz_name,
|
|
110
135
|
"armed": armed,
|
|
111
136
|
}
|
|
112
137
|
|
|
@@ -133,6 +158,7 @@ class TriggerService:
|
|
|
133
158
|
skipped += missed
|
|
134
159
|
# Reset the cadence from now — no catch-up storm.
|
|
135
160
|
entry["last_fired_at"] = now if last is not None else entry.get("last_fired_at")
|
|
161
|
+
entry["last_attempt_at"] = now
|
|
136
162
|
self._save_state(state)
|
|
137
163
|
return skipped
|
|
138
164
|
|
|
@@ -152,9 +178,16 @@ class TriggerService:
|
|
|
152
178
|
if last is None:
|
|
153
179
|
# First sighting arms the schedule; it fires one interval later.
|
|
154
180
|
entry["last_fired_at"] = now
|
|
181
|
+
entry["last_attempt_at"] = now
|
|
155
182
|
continue
|
|
156
183
|
if now - float(last) < interval:
|
|
157
184
|
continue
|
|
185
|
+
# Dedup guard (edge case): short cooldown + last_attempt prevents rapid re-fire on
|
|
186
|
+
# clock skew, tick jitter, or restart races. Interval 자체 + 이 가드로 중복 실행 방지.
|
|
187
|
+
last_attempt = float(entry.get("last_attempt_at") or 0)
|
|
188
|
+
if now - last_attempt < 10:
|
|
189
|
+
continue
|
|
190
|
+
entry["last_attempt_at"] = now
|
|
158
191
|
entry["last_fired_at"] = now
|
|
159
192
|
self._record_event(state, wf_id, {"type": "fired", "trigger": "interval"})
|
|
160
193
|
fired += 1
|
|
@@ -181,7 +214,14 @@ class TriggerService:
|
|
|
181
214
|
if wanted and wanted != source_type:
|
|
182
215
|
continue
|
|
183
216
|
wf_id = item["workflow"].get("id")
|
|
184
|
-
state.setdefault(wf_id, {})
|
|
217
|
+
entry = state.setdefault(wf_id, {})
|
|
218
|
+
now = self._clock()
|
|
219
|
+
# Dedup guard for brain_event too (rapid ingest burst 등).
|
|
220
|
+
last_attempt = float(entry.get("last_attempt_at") or 0)
|
|
221
|
+
if now - last_attempt < 5:
|
|
222
|
+
continue
|
|
223
|
+
entry["last_attempt_at"] = now
|
|
224
|
+
entry["last_fired_at"] = now
|
|
185
225
|
self._record_event(state, wf_id, {
|
|
186
226
|
"type": "fired", "trigger": "brain_event", "source_type": source_type,
|
|
187
227
|
})
|
|
@@ -211,11 +251,28 @@ class TriggerService:
|
|
|
211
251
|
def _run():
|
|
212
252
|
try:
|
|
213
253
|
self._run_workflow(workflow_id, {"__trigger__": trigger_info})
|
|
254
|
+
self._record_fire_outcome(workflow_id, ok=True)
|
|
214
255
|
except Exception as exc:
|
|
215
256
|
logging.warning("trigger run failed for %s: %s", workflow_id, exc)
|
|
257
|
+
self._record_fire_outcome(workflow_id, ok=False, detail=str(exc))
|
|
216
258
|
|
|
217
259
|
threading.Thread(target=_run, name=f"trigger-{workflow_id}", daemon=True).start()
|
|
218
260
|
|
|
261
|
+
def _record_fire_outcome(self, wf_id: str, *, ok: bool, detail: str = "") -> None:
|
|
262
|
+
"""Track consecutive launch failures for degraded status in describe().
|
|
263
|
+
(Deep execution failures are visible via workflow run records; 여기서는 scheduler fire 자체 실패를 카운트.)
|
|
264
|
+
"""
|
|
265
|
+
with self._lock:
|
|
266
|
+
state = self._load_state()
|
|
267
|
+
entry = state.setdefault(wf_id, {})
|
|
268
|
+
if ok:
|
|
269
|
+
entry["consecutive_failures"] = 0
|
|
270
|
+
else:
|
|
271
|
+
fails = int(entry.get("consecutive_failures", 0)) + 1
|
|
272
|
+
entry["consecutive_failures"] = fails
|
|
273
|
+
self._record_event(state, wf_id, {"type": "failed", "detail": detail[:200]})
|
|
274
|
+
self._save_state(state)
|
|
275
|
+
|
|
219
276
|
def start(self) -> None:
|
|
220
277
|
if self._thread and self._thread.is_alive():
|
|
221
278
|
return
|
package/package.json
CHANGED
package/src-tauri/Cargo.lock
CHANGED
package/src-tauri/Cargo.toml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "5.
|
|
2
|
+
"version": "5.5.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
|
-
"index.html": "/static/app/assets/index-
|
|
10
|
-
"assets/index-
|
|
9
|
+
"index.html": "/static/app/assets/index-C7vzwUjU.js",
|
|
10
|
+
"assets/index-HN4f2wbe.css": "/static/app/assets/index-HN4f2wbe.css"
|
|
11
11
|
},
|
|
12
12
|
"vite": {
|
|
13
13
|
"../node_modules/@tauri-apps/api/core.js": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"isDynamicEntry": true
|
|
18
18
|
},
|
|
19
19
|
"index.html": {
|
|
20
|
-
"file": "assets/index-
|
|
20
|
+
"file": "assets/index-C7vzwUjU.js",
|
|
21
21
|
"name": "index",
|
|
22
22
|
"src": "index.html",
|
|
23
23
|
"isEntry": true,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"../node_modules/@tauri-apps/api/core.js"
|
|
26
26
|
],
|
|
27
27
|
"css": [
|
|
28
|
-
"assets/index-
|
|
28
|
+
"assets/index-HN4f2wbe.css"
|
|
29
29
|
]
|
|
30
30
|
}
|
|
31
31
|
}
|