ltcai 6.0.0 → 6.1.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 +38 -35
- package/docs/CHANGELOG.md +36 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +1 -1
- package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +1 -1
- package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +1 -1
- package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +1 -1
- package/frontend/src/App.tsx +5 -0
- package/frontend/src/components/ProductFlow.tsx +7 -0
- package/frontend/src/features/review/ReviewCard.tsx +15 -10
- package/frontend/src/i18n.ts +10 -0
- package/frontend/src/styles.css +20 -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/api/chat.py +52 -33
- package/latticeai/app_factory.py +29 -2
- package/latticeai/cli/__init__.py +1 -0
- package/latticeai/cli/runtime.py +37 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/app_context.py +1 -0
- package/latticeai/services/tool_dispatch.py +82 -25
- package/ltcai_cli.py +5 -31
- 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-xRn29gI8.css → index-B744yblP.css} +1 -1
- package/static/app/assets/{index-D2zafMYb.js → index-DYaUKNfl.js} +2 -2
- package/static/app/assets/index-DYaUKNfl.js.map +1 -0
- package/static/app/index.html +2 -2
- package/telegram_bot.py +3 -9
- package/static/app/assets/index-D2zafMYb.js.map +0 -1
package/README.md
CHANGED
|
@@ -69,42 +69,42 @@ You need Lattice AI when:
|
|
|
69
69
|
Choose the owner of the Brain. The profile is not a SaaS account by default; it
|
|
70
70
|
is the local identity for the knowledge you keep.
|
|
71
71
|
|
|
72
|
-

|
|
73
73
|
|
|
74
74
|
### 2. Environment Analysis
|
|
75
75
|
|
|
76
76
|
See what kind of local AI experience this computer can support before choosing a
|
|
77
77
|
model.
|
|
78
78
|
|
|
79
|
-

|
|
80
80
|
|
|
81
81
|
### 3. Recommended Models
|
|
82
82
|
|
|
83
83
|
Start with a short list: safest recommendation, faster model, stronger model.
|
|
84
84
|
Advanced details stay available without overwhelming first-time users.
|
|
85
85
|
|
|
86
|
-

|
|
87
87
|
|
|
88
88
|
### 4. Install And Load
|
|
89
89
|
|
|
90
90
|
Download and load only after consent. Lattice explains model size, local
|
|
91
91
|
execution, and network use before work starts.
|
|
92
92
|
|
|
93
|
-

|
|
94
94
|
|
|
95
95
|
### 5. Brain Chat
|
|
96
96
|
|
|
97
97
|
Talk normally. Useful decisions and context become memory, then appear later as
|
|
98
98
|
topics, relationships, and graph structure.
|
|
99
99
|
|
|
100
|
-

|
|
101
101
|
|
|
102
102
|
### 6. Review Center
|
|
103
103
|
|
|
104
104
|
Automation results are staged for review before they become durable decisions.
|
|
105
105
|
Snooze, unsnooze, run now, approve, and dismiss actions stay explicit.
|
|
106
106
|
|
|
107
|
-

|
|
108
108
|
|
|
109
109
|
## Brain Depths
|
|
110
110
|
|
|
@@ -120,10 +120,10 @@ The user travels inward from everyday memory to deeper structure:
|
|
|
120
120
|
|
|
121
121
|
Walkthrough:
|
|
122
122
|
|
|
123
|
-

|
|
124
124
|
|
|
125
125
|
Screenshot index and capture notes:
|
|
126
|
-
[output/release/v6.
|
|
126
|
+
[output/release/v6.1.0/SCREENSHOT_INDEX.md](output/release/v6.1.0/SCREENSHOT_INDEX.md)
|
|
127
127
|
|
|
128
128
|
## Install
|
|
129
129
|
|
|
@@ -200,33 +200,36 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
|
|
|
200
200
|
|
|
201
201
|
## Current Release Preparation
|
|
202
202
|
|
|
203
|
-
The current development target is **6.
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
-
- Review Center
|
|
210
|
-
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- `dist/ltcai-6.
|
|
229
|
-
- `
|
|
203
|
+
The current development target is **6.1.0 Product Hardening / Digital Brain Completion**:
|
|
204
|
+
|
|
205
|
+
- First-run now lets users create or open the local Brain even when they defer
|
|
206
|
+
model setup, keeping the Brain durable while models stay replaceable.
|
|
207
|
+
- Brain Home explains the first memory loop: save useful context, see it return
|
|
208
|
+
in the Brain state, then protect it with backup/export.
|
|
209
|
+
- Review Center keeps Pending, Snoozed, and All filters while clarifying that
|
|
210
|
+
Run now is a preview/regenerate action, not approval.
|
|
211
|
+
- `lattice_brain` has an AST import guard so Brain Core does not import
|
|
212
|
+
`latticeai` or `ltcai`.
|
|
213
|
+
- Model download consent has a unit guard: token presence alone does not start
|
|
214
|
+
an external download path.
|
|
215
|
+
- `ltcai_cli.py` keeps the root entrypoint while pure CLI runtime helpers live
|
|
216
|
+
under `latticeai.cli.runtime`.
|
|
217
|
+
- Tool dispatch authorization now has an injectable service boundary around
|
|
218
|
+
the shared ToolRegistry, reducing app-factory reliance on module globals.
|
|
219
|
+
- Chat agent runtime construction moved into app-factory assembly and is passed
|
|
220
|
+
through `AppContext`, so the chat router no longer owns production runtime
|
|
221
|
+
construction.
|
|
222
|
+
- v6.1 documentation tracks backend hardening, frontend UX hardening, and the
|
|
223
|
+
baseline scan for version metadata, root legacy modules, Brain Core boundary,
|
|
224
|
+
and local-first trust gates.
|
|
225
|
+
|
|
226
|
+
Expected artifacts for 6.1.0 release must use exact filenames:
|
|
227
|
+
|
|
228
|
+
- `dist/ltcai-6.1.0-py3-none-any.whl`
|
|
229
|
+
- `dist/ltcai-6.1.0.tar.gz`
|
|
230
|
+
- `ltcai-6.1.0.tgz`
|
|
231
|
+
- `dist/ltcai-6.1.0.vsix`
|
|
232
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_6.1.0_aarch64.dmg`
|
|
230
233
|
|
|
231
234
|
Do not upload `dist/*`. Package registry publishing remains owner-run.
|
|
232
235
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,42 @@
|
|
|
3
3
|
The top entry is the current release-preparation target. Older entries are
|
|
4
4
|
historical and may describe behavior as it existed at that release.
|
|
5
5
|
|
|
6
|
+
## [6.1.0] - 2026-06-16
|
|
7
|
+
|
|
8
|
+
> Product Hardening / Digital Brain Completion. Tightens the local-first Brain
|
|
9
|
+
> flow, Brain Core boundary, backend trust gates, and release documentation
|
|
10
|
+
> without adding broad new product surfaces.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- First-run path to open the Brain without installing or loading a model first.
|
|
14
|
+
- Brain Home empty-state loop that shows first memory save, Brain state return,
|
|
15
|
+
and backup ownership.
|
|
16
|
+
- AST import guard proving `lattice_brain` does not import `latticeai` or
|
|
17
|
+
`ltcai`.
|
|
18
|
+
- Unit coverage for model download consent blocking in `model_runtime`.
|
|
19
|
+
- `latticeai.cli.runtime` for pure CLI runtime helpers while preserving the root
|
|
20
|
+
`ltcai_cli.py` entrypoint.
|
|
21
|
+
- v6.1 baseline, frontend UX, backend hardening, and instruction documents under
|
|
22
|
+
`docs/v6.1/`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Review Center cards clarify that Run now previews/executes without approval.
|
|
26
|
+
- Static app assets are refreshed for the v6.1 frontend hardening changes.
|
|
27
|
+
- Tool dispatch authorization now uses an injectable service boundary around
|
|
28
|
+
the shared ToolRegistry while preserving the legacy module-level API.
|
|
29
|
+
- Chat agent runtime construction moved out of `create_chat_router` for
|
|
30
|
+
production app assembly and is now injected through `AppContext`.
|
|
31
|
+
- README release evidence screenshots and walkthrough GIF are refreshed under
|
|
32
|
+
`output/release/v6.1.0/`, including the Review Center surface.
|
|
33
|
+
- README and release notes now describe v6.1.0 as the current hardening target
|
|
34
|
+
with exact expected artifact filenames.
|
|
35
|
+
|
|
36
|
+
### Preserved
|
|
37
|
+
- Package publish, deployment, tag creation, and main merge remain out of scope
|
|
38
|
+
unless explicitly requested.
|
|
39
|
+
- Package/runtime version metadata is synchronized to 6.1.0; package publish,
|
|
40
|
+
deployment, tag creation, and main merge remain owner-run only.
|
|
41
|
+
|
|
6
42
|
## [6.0.0] - 2026-06-15
|
|
7
43
|
|
|
8
44
|
> Product Reset / Review Center Completion. Raises the Review Center from a
|
|
@@ -61,7 +61,7 @@ contracts, and local-first/offline operation.
|
|
|
61
61
|
- Legacy frontend build pipeline.
|
|
62
62
|
- Legacy v3 frontend lint script.
|
|
63
63
|
- Static v3 asset tree in release packages.
|
|
64
|
-
- Direct current
|
|
64
|
+
- Direct then-current release references to v4.0.1 in then-latest docs.
|
|
65
65
|
|
|
66
66
|
## Data Preservation
|
|
67
67
|
|
|
@@ -8,7 +8,7 @@ architecture or add unrelated product features.
|
|
|
8
8
|
|
|
9
9
|
## Completed Refresh Areas
|
|
10
10
|
|
|
11
|
-
- Synchronized current
|
|
11
|
+
- Synchronized then-current release version metadata to `4.6.1`.
|
|
12
12
|
- Reframed README around the current Living Brain flow:
|
|
13
13
|
Login -> Environment Analysis -> Recommended Models -> Install & Load ->
|
|
14
14
|
Brain Chat.
|
|
@@ -19,7 +19,7 @@ maintenance. This release does not redesign Brain Core or the storage layer.
|
|
|
19
19
|
- Added frontend API helpers for `/admin/stats` and `/admin/security/events`.
|
|
20
20
|
- Updated visual validation to confirm the admin console is separate from the
|
|
21
21
|
user Brain surface.
|
|
22
|
-
- Synchronized current
|
|
22
|
+
- Synchronized then-current release version metadata to `4.7.0`.
|
|
23
23
|
- Updated README, RELEASE.md, release notes, changelog, architecture, security,
|
|
24
24
|
feature status, VS Code extension docs, and release evidence references.
|
|
25
25
|
|
|
@@ -26,7 +26,7 @@ maintenance. This release does not redesign Brain Core or the storage layer.
|
|
|
26
26
|
admin observability state remain separate in the frontend runtime.
|
|
27
27
|
- Updated visual validation to confirm the admin console is separate from the
|
|
28
28
|
user Brain surface.
|
|
29
|
-
- Synchronized current
|
|
29
|
+
- Synchronized then-current release version metadata to `4.7.1`.
|
|
30
30
|
- Updated README, RELEASE.md, release notes, changelog, architecture, security,
|
|
31
31
|
feature status, VS Code extension docs, and release evidence references.
|
|
32
32
|
|
package/frontend/src/App.tsx
CHANGED
|
@@ -382,6 +382,11 @@ function BrainHome({
|
|
|
382
382
|
</button>
|
|
383
383
|
))}
|
|
384
384
|
</div>
|
|
385
|
+
<div className="mind-empty-trail" aria-label={t(language, "brain.empty.trail.label")}>
|
|
386
|
+
<span>{t(language, "brain.empty.trail.save")}</span>
|
|
387
|
+
<span>{t(language, "brain.empty.trail.recall")}</span>
|
|
388
|
+
<span>{t(language, "brain.empty.trail.backup")}</span>
|
|
389
|
+
</div>
|
|
385
390
|
</div>
|
|
386
391
|
) : (
|
|
387
392
|
messages.map((message, index) => (
|
|
@@ -123,6 +123,10 @@ export function ProductFlow({ onComplete }: { onComplete: () => void }) {
|
|
|
123
123
|
<RecommendationScreen
|
|
124
124
|
recommendations={recommendations}
|
|
125
125
|
onBack={() => setStep("analysis")}
|
|
126
|
+
onSkipModel={() => {
|
|
127
|
+
try { localStorage.setItem(FLOW_COMPLETE_KEY, "true"); } catch {}
|
|
128
|
+
onComplete();
|
|
129
|
+
}}
|
|
126
130
|
onSelect={(model) => {
|
|
127
131
|
setSelected(model);
|
|
128
132
|
setStep("install");
|
|
@@ -333,10 +337,12 @@ function AnalysisScreen({
|
|
|
333
337
|
function RecommendationScreen({
|
|
334
338
|
recommendations,
|
|
335
339
|
onBack,
|
|
340
|
+
onSkipModel,
|
|
336
341
|
onSelect,
|
|
337
342
|
}: {
|
|
338
343
|
recommendations: RecommendedModel[];
|
|
339
344
|
onBack: () => void;
|
|
345
|
+
onSkipModel: () => void;
|
|
340
346
|
onSelect: (model: RecommendedModel) => void;
|
|
341
347
|
}) {
|
|
342
348
|
const language = useAppStore((state) => state.language);
|
|
@@ -370,6 +376,7 @@ function RecommendationScreen({
|
|
|
370
376
|
|
|
371
377
|
<div style={{ marginTop: "1.1rem", display: "flex", justifyContent: "center", gap: "1rem", alignItems: "center" }}>
|
|
372
378
|
<Button variant="ghost" onClick={onBack}>{t(language, "flow.recommend.back")}</Button>
|
|
379
|
+
<Button variant="outline" onClick={onSkipModel}>{t(language, "flow.recommend.skip")}</Button>
|
|
373
380
|
<div style={{ fontSize: "0.82rem", color: "hsl(var(--fg-muted))" }}>{t(language, "flow.recommend.hint")}</div>
|
|
374
381
|
</div>
|
|
375
382
|
</div>
|
|
@@ -71,16 +71,21 @@ export function ReviewCard({ item, feedback, onAction }: ReviewCardProps) {
|
|
|
71
71
|
) : null}
|
|
72
72
|
|
|
73
73
|
{actionable ? (
|
|
74
|
-
<div className="mt-4
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
<div className="mt-4 grid gap-2">
|
|
75
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
76
|
+
Run now previews the action without approving it. Approve or dismiss when the result looks right.
|
|
77
|
+
</p>
|
|
78
|
+
<div className="flex flex-wrap gap-2" aria-label="Review actions">
|
|
79
|
+
<ActionButton
|
|
80
|
+
label="Run now"
|
|
81
|
+
successLabel={hadRun ? "Regenerated" : "Executed"}
|
|
82
|
+
action={() => onAction(item, "run_now", hadRun)}
|
|
83
|
+
invalidate={[]}
|
|
84
|
+
/>
|
|
85
|
+
<ActionButton label="Approve" action={() => onAction(item, "approve")} invalidate={[]} />
|
|
86
|
+
{!snoozed ? <ActionButton label="Snooze 1 day" action={() => onAction(item, "snooze")} invalidate={[]} /> : null}
|
|
87
|
+
<ActionButton label="Dismiss" action={() => onAction(item, "dismiss")} invalidate={[]} variant="destructive" />
|
|
88
|
+
</div>
|
|
84
89
|
</div>
|
|
85
90
|
) : null}
|
|
86
91
|
{feedback ? (
|
package/frontend/src/i18n.ts
CHANGED
|
@@ -31,6 +31,10 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
31
31
|
"brain.empty.kicker": "내 오래가는 기억",
|
|
32
32
|
"brain.empty.title": "잊으면 안 되는 맥락부터 말해 주세요.",
|
|
33
33
|
"brain.empty.body": "문서, 대화, 프로젝트, 결정이 이 컴퓨터의 Brain에 쌓이고 나중에 주제와 관계로 다시 보입니다.",
|
|
34
|
+
"brain.empty.trail.label": "첫 Brain 흐름",
|
|
35
|
+
"brain.empty.trail.save": "1. 첫 기억 저장",
|
|
36
|
+
"brain.empty.trail.recall": "2. Brain Home에서 확인",
|
|
37
|
+
"brain.empty.trail.backup": "3. 백업으로 소유",
|
|
34
38
|
"brain.prompt.remember": "이 프로젝트 목표를 기억해줘: ",
|
|
35
39
|
"brain.prompt.know": "이 문서를 내 Brain에 넣고 요약해줘: ",
|
|
36
40
|
"brain.prompt.plan": "지난 결정들을 나중에 찾을 수 있게 정리해줘: ",
|
|
@@ -107,6 +111,7 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
107
111
|
"flow.recommend.primary": "추천대로 시작하기",
|
|
108
112
|
"flow.recommend.unsupported": "이 컴퓨터에서 추가 확인이 필요합니다",
|
|
109
113
|
"flow.recommend.back": "뒤로",
|
|
114
|
+
"flow.recommend.skip": "모델 없이 Brain 열기",
|
|
110
115
|
"flow.recommend.hint": "잘 모르겠다면 추천대로 시작하면 됩니다.",
|
|
111
116
|
"flow.install.title": "모델을 설치하고 시작합니다.",
|
|
112
117
|
"flow.install.body": "이 모델이 Brain의 로컬 목소리가 됩니다. 인터넷은 다운로드할 때만 필요하고, 실행과 기억은 이 컴퓨터에서 유지됩니다.",
|
|
@@ -144,6 +149,10 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
144
149
|
"brain.empty.kicker": "Durable memory",
|
|
145
150
|
"brain.empty.title": "Start with context that should not be forgotten.",
|
|
146
151
|
"brain.empty.body": "Documents, conversations, projects, and decisions accumulate in the Brain on this computer, then reappear as topics and relationships.",
|
|
152
|
+
"brain.empty.trail.label": "First Brain flow",
|
|
153
|
+
"brain.empty.trail.save": "1. Save first memory",
|
|
154
|
+
"brain.empty.trail.recall": "2. See it in Brain Home",
|
|
155
|
+
"brain.empty.trail.backup": "3. Own it with backup",
|
|
147
156
|
"brain.prompt.remember": "Remember this project goal: ",
|
|
148
157
|
"brain.prompt.know": "Put this document into my Brain and summarize it: ",
|
|
149
158
|
"brain.prompt.plan": "Organize past decisions so I can find them later: ",
|
|
@@ -220,6 +229,7 @@ export const COPY: Record<Language, TextMap> = {
|
|
|
220
229
|
"flow.recommend.primary": "Start with recommendation",
|
|
221
230
|
"flow.recommend.unsupported": "This computer needs one more check",
|
|
222
231
|
"flow.recommend.back": "Back",
|
|
232
|
+
"flow.recommend.skip": "Open Brain without a model",
|
|
223
233
|
"flow.recommend.hint": "If unsure, start with the recommendation.",
|
|
224
234
|
"flow.install.title": "Install the model and start.",
|
|
225
235
|
"flow.install.body": "This model becomes your Brain's local voice. Internet is needed only for download; execution and memory stay on this computer.",
|
package/frontend/src/styles.css
CHANGED
|
@@ -2774,6 +2774,26 @@ body {
|
|
|
2774
2774
|
transform: translateY(-1px);
|
|
2775
2775
|
}
|
|
2776
2776
|
|
|
2777
|
+
.mind-empty-trail {
|
|
2778
|
+
display: flex;
|
|
2779
|
+
flex-wrap: wrap;
|
|
2780
|
+
justify-content: center;
|
|
2781
|
+
gap: 0.45rem;
|
|
2782
|
+
margin: 1rem auto 0;
|
|
2783
|
+
max-width: 40rem;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
.mind-empty-trail span {
|
|
2787
|
+
border: 1px solid hsl(var(--border) / 0.55);
|
|
2788
|
+
border-radius: 999px;
|
|
2789
|
+
background: hsl(var(--surface-glass));
|
|
2790
|
+
color: hsl(var(--fg-muted));
|
|
2791
|
+
padding: 0.34rem 0.62rem;
|
|
2792
|
+
font-size: 0.72rem;
|
|
2793
|
+
font-weight: 720;
|
|
2794
|
+
line-height: 1.15;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2777
2797
|
.mind-empty .icon {
|
|
2778
2798
|
opacity: 0.5;
|
|
2779
2799
|
margin-bottom: 0.8rem;
|
|
@@ -19,7 +19,7 @@ from datetime import datetime
|
|
|
19
19
|
from typing import Any, Callable, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
MULTI_AGENT_VERSION = "6.
|
|
22
|
+
MULTI_AGENT_VERSION = "6.1.0"
|
|
23
23
|
|
|
24
24
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
25
25
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
package/latticeai/api/chat.py
CHANGED
|
@@ -150,6 +150,35 @@ async def single_text_stream(text: str, model: str = "system") -> AsyncIterator[
|
|
|
150
150
|
yield "data: [DONE]\n\n"
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
def build_recent_chat_context(
|
|
154
|
+
*,
|
|
155
|
+
get_history,
|
|
156
|
+
limit: int = 10,
|
|
157
|
+
include_image_missing_replies: bool = True,
|
|
158
|
+
user_email: Optional[str] = None,
|
|
159
|
+
conversation_id: Optional[str] = None,
|
|
160
|
+
) -> str:
|
|
161
|
+
history = get_history()
|
|
162
|
+
if conversation_id:
|
|
163
|
+
history = [item for item in history if item.get("conversation_id") == conversation_id]
|
|
164
|
+
if user_email:
|
|
165
|
+
history = pair_user_history(history, user_email)
|
|
166
|
+
history = history[-limit:]
|
|
167
|
+
lines = []
|
|
168
|
+
for item in history:
|
|
169
|
+
role = item.get("role", "user")
|
|
170
|
+
content = item.get("content", "")
|
|
171
|
+
if not include_image_missing_replies and role == "assistant":
|
|
172
|
+
if "이미지" in content and any(word in content for word in ["업로드", "제공", "올려"]):
|
|
173
|
+
continue
|
|
174
|
+
source = item.get("source")
|
|
175
|
+
label = role
|
|
176
|
+
if source:
|
|
177
|
+
label = f"{role} ({source})"
|
|
178
|
+
lines.append(f"{label}: {content}")
|
|
179
|
+
return "\n".join(lines)
|
|
180
|
+
|
|
181
|
+
|
|
153
182
|
def create_chat_router(context: AppContext) -> APIRouter:
|
|
154
183
|
"""Build the chat/history/agent router from the typed application context.
|
|
155
184
|
|
|
@@ -201,31 +230,19 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
201
230
|
except Exception as exc:
|
|
202
231
|
logging.warning("chat message bridge failed: %s", exc)
|
|
203
232
|
|
|
204
|
-
def
|
|
233
|
+
def recent_chat_context(
|
|
205
234
|
limit: int = 10,
|
|
206
235
|
include_image_missing_replies: bool = True,
|
|
207
236
|
user_email: Optional[str] = None,
|
|
208
237
|
conversation_id: Optional[str] = None,
|
|
209
238
|
) -> str:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
for item in history:
|
|
218
|
-
role = item.get("role", "user")
|
|
219
|
-
content = item.get("content", "")
|
|
220
|
-
if not include_image_missing_replies and role == "assistant":
|
|
221
|
-
if "이미지" in content and any(word in content for word in ["업로드", "제공", "올려"]):
|
|
222
|
-
continue
|
|
223
|
-
source = item.get("source")
|
|
224
|
-
label = role
|
|
225
|
-
if source:
|
|
226
|
-
label = f"{role} ({source})"
|
|
227
|
-
lines.append(f"{label}: {content}")
|
|
228
|
-
return "\n".join(lines)
|
|
239
|
+
return build_recent_chat_context(
|
|
240
|
+
get_history=get_history,
|
|
241
|
+
limit=limit,
|
|
242
|
+
include_image_missing_replies=include_image_missing_replies,
|
|
243
|
+
user_email=user_email,
|
|
244
|
+
conversation_id=conversation_id,
|
|
245
|
+
)
|
|
229
246
|
|
|
230
247
|
def extract_screenshot_context(image_data: Optional[str]) -> str:
|
|
231
248
|
if not image_data:
|
|
@@ -283,16 +300,18 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
283
300
|
|
|
284
301
|
return "\n".join(lines)
|
|
285
302
|
|
|
286
|
-
_AGENT_RUNTIME =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
303
|
+
_AGENT_RUNTIME = context.chat_agent_runtime
|
|
304
|
+
if _AGENT_RUNTIME is None:
|
|
305
|
+
_AGENT_RUNTIME = build_agent_runtime(
|
|
306
|
+
model_router=router,
|
|
307
|
+
execute_tool=execute_tool,
|
|
308
|
+
recent_chat_context=recent_chat_context,
|
|
309
|
+
clear_history=clear_history,
|
|
310
|
+
knowledge_save=knowledge_save,
|
|
311
|
+
audit=append_audit_event,
|
|
312
|
+
hooks=hooks,
|
|
313
|
+
brain_memory=context.brain_memory,
|
|
314
|
+
)
|
|
296
315
|
|
|
297
316
|
@api_router.post("/chat")
|
|
298
317
|
async def chat(req: ChatRequest, request: Request):
|
|
@@ -546,7 +565,7 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
546
565
|
return JSONResponse(content={"response": str(result), "trace_id": trace_record["id"], "trace": trace_record})
|
|
547
566
|
|
|
548
567
|
if req.stream:
|
|
549
|
-
recent_context =
|
|
568
|
+
recent_context = recent_chat_context(user_email=effective_email, conversation_id=req.conversation_id)
|
|
550
569
|
stream_context = context
|
|
551
570
|
if recent_context:
|
|
552
571
|
stream_context = f"[RECENT CONVERSATION]\n{recent_context}\n\n{context}".strip()
|
|
@@ -564,7 +583,7 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
564
583
|
)
|
|
565
584
|
else:
|
|
566
585
|
if req.image_data:
|
|
567
|
-
recent_context =
|
|
586
|
+
recent_context = recent_chat_context(
|
|
568
587
|
limit=6,
|
|
569
588
|
include_image_missing_replies=False,
|
|
570
589
|
user_email=effective_email,
|
|
@@ -572,7 +591,7 @@ def create_chat_router(context: AppContext) -> APIRouter:
|
|
|
572
591
|
)
|
|
573
592
|
full_context = f"[RECENT CONVERSATION]\n{recent_context}\n\n{context}".strip() if recent_context else context
|
|
574
593
|
else:
|
|
575
|
-
history_context =
|
|
594
|
+
history_context = recent_chat_context(user_email=effective_email, conversation_id=req.conversation_id)
|
|
576
595
|
full_context = f"{history_context}\n{context}" if context else history_context
|
|
577
596
|
|
|
578
597
|
result = await router.generate(req.message, full_context, req.max_tokens, req.temperature, req.image_data)
|
package/latticeai/app_factory.py
CHANGED
|
@@ -153,7 +153,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
153
153
|
from latticeai.api.invitations import create_invitations_router
|
|
154
154
|
from latticeai.api.marketplace import create_marketplace_router
|
|
155
155
|
from latticeai.api.models import create_models_router
|
|
156
|
-
from latticeai.api.chat import create_chat_router
|
|
156
|
+
from latticeai.api.chat import build_recent_chat_context, create_chat_router
|
|
157
157
|
from latticeai.api.search import create_search_router
|
|
158
158
|
from latticeai.api.tools import create_tools_router
|
|
159
159
|
from latticeai.api.static_routes import create_static_routes_router
|
|
@@ -181,6 +181,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
181
181
|
configure_tool_dispatch,
|
|
182
182
|
get_tool_permission,
|
|
183
183
|
list_tool_permissions,
|
|
184
|
+
build_agent_runtime,
|
|
184
185
|
tool_response as _tool_response,
|
|
185
186
|
)
|
|
186
187
|
from latticeai.core.tool_registry import TOOL_CATALOG_BRIEF as _TOOL_CATALOG_BRIEF # noqa: F401
|
|
@@ -190,7 +191,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
190
191
|
)
|
|
191
192
|
from p_reinforce import PReinforceGardener
|
|
192
193
|
from setup_wizard import get_recommendations, scan_environment
|
|
193
|
-
from tools import ensure_agent_root
|
|
194
|
+
from tools import ensure_agent_root, execute_tool, knowledge_save
|
|
194
195
|
|
|
195
196
|
try:
|
|
196
197
|
import keyring
|
|
@@ -1242,6 +1243,31 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1242
1243
|
_spawn(broadcast_web_chat(role, text), name="telegram_broadcast")
|
|
1243
1244
|
on_chat_message = _telegram_chat_mirror
|
|
1244
1245
|
|
|
1246
|
+
def _recent_chat_context(
|
|
1247
|
+
limit: int = 10,
|
|
1248
|
+
include_image_missing_replies: bool = True,
|
|
1249
|
+
user_email: Optional[str] = None,
|
|
1250
|
+
conversation_id: Optional[str] = None,
|
|
1251
|
+
) -> str:
|
|
1252
|
+
return build_recent_chat_context(
|
|
1253
|
+
get_history=get_history,
|
|
1254
|
+
limit=limit,
|
|
1255
|
+
include_image_missing_replies=include_image_missing_replies,
|
|
1256
|
+
user_email=user_email,
|
|
1257
|
+
conversation_id=conversation_id,
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
CHAT_AGENT_RUNTIME = build_agent_runtime(
|
|
1261
|
+
model_router=router,
|
|
1262
|
+
execute_tool=execute_tool,
|
|
1263
|
+
recent_chat_context=_recent_chat_context,
|
|
1264
|
+
clear_history=clear_history,
|
|
1265
|
+
knowledge_save=knowledge_save,
|
|
1266
|
+
audit=append_audit_event,
|
|
1267
|
+
hooks=HOOKS_REGISTRY,
|
|
1268
|
+
brain_memory=BRAIN_MEMORY,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1245
1271
|
# ── Typed dependency context (latticeai.services.app_context) ────────────────
|
|
1246
1272
|
# One context object replaces the historical 25-30-kwarg router wiring.
|
|
1247
1273
|
context = build_app_context(
|
|
@@ -1258,6 +1284,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1258
1284
|
chat_service=CHAT_SERVICE,
|
|
1259
1285
|
context_assembler=CONTEXT_ASSEMBLER,
|
|
1260
1286
|
brain_memory=BRAIN_MEMORY,
|
|
1287
|
+
chat_agent_runtime=CHAT_AGENT_RUNTIME,
|
|
1261
1288
|
gardener=gardener,
|
|
1262
1289
|
hooks=HOOKS_REGISTRY,
|
|
1263
1290
|
realtime_bus=REALTIME_BUS,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LatticeAI CLI package."""
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Pure runtime helpers extracted from ltcai_cli entrypoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_env_file(path: Path) -> None:
|
|
11
|
+
if not path.exists():
|
|
12
|
+
return
|
|
13
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
14
|
+
line = raw_line.strip()
|
|
15
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
16
|
+
continue
|
|
17
|
+
key, value = line.split("=", 1)
|
|
18
|
+
key = key.strip()
|
|
19
|
+
value = value.strip().strip('"').strip("'")
|
|
20
|
+
if key and key not in os.environ:
|
|
21
|
+
os.environ[key] = value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _apply_extra_path() -> None:
|
|
25
|
+
extra = os.getenv("LATTICEAI_EXTRA_PATH", "")
|
|
26
|
+
if not extra:
|
|
27
|
+
return
|
|
28
|
+
current = [p for p in os.environ.get("PATH", "").split(os.pathsep) if p]
|
|
29
|
+
for item in reversed([p for p in extra.split(os.pathsep) if p]):
|
|
30
|
+
expanded = str(Path(item).expanduser())
|
|
31
|
+
if Path(expanded).exists() and expanded not in current:
|
|
32
|
+
current.insert(0, expanded)
|
|
33
|
+
os.environ["PATH"] = os.pathsep.join(current)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _has_module(name: str) -> bool:
|
|
37
|
+
return importlib.util.find_spec(name) is not None
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
WORKSPACE_OS_VERSION = "6.
|
|
22
|
+
WORKSPACE_OS_VERSION = "6.1.0"
|
|
23
23
|
|
|
24
24
|
# Workspace types separate single-user Personal workspaces from shared
|
|
25
25
|
# Organization workspaces. Both keep the same local-first JSON store; the type
|