ltcai 6.1.0 → 6.3.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 +36 -38
- package/docs/CHANGELOG.md +75 -1
- package/frontend/src/App.tsx +3 -1286
- package/frontend/src/components/LanguageSwitcher.tsx +23 -0
- package/frontend/src/components/ProductFlow.tsx +32 -669
- package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
- package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
- package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
- package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
- package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
- package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -0
- package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
- package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
- package/frontend/src/features/admin/AdminConsole.tsx +294 -0
- package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
- package/frontend/src/features/brain/BrainComposer.tsx +66 -0
- package/frontend/src/features/brain/BrainConversation.tsx +131 -0
- package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
- package/frontend/src/features/brain/BrainHome.tsx +232 -0
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
- package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
- package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
- package/frontend/src/features/brain/brainData.ts +98 -0
- package/frontend/src/features/brain/graphLayout.ts +26 -0
- package/frontend/src/features/brain/types.ts +44 -0
- package/frontend/src/features/review/ReviewCard.tsx +3 -1
- package/frontend/src/features/review/ReviewInbox.tsx +11 -1
- package/frontend/src/i18n.ts +290 -0
- package/frontend/src/pages/Brain.tsx +63 -5
- package/frontend/src/pages/Capture.tsx +102 -13
- package/frontend/src/pages/Library.tsx +72 -6
- package/frontend/src/styles.css +220 -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/tools.py +50 -23
- package/latticeai/app_factory.py +59 -76
- package/latticeai/cli/entrypoint.py +283 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot.py +1009 -0
- package/latticeai/runtime/chat_wiring.py +119 -0
- package/latticeai/runtime/lifespan_runtime.py +1 -1
- package/latticeai/runtime/model_wiring.py +40 -0
- package/latticeai/runtime/platform_runtime_wiring.py +86 -0
- package/latticeai/runtime/review_wiring.py +37 -0
- package/latticeai/runtime/router_registration.py +49 -30
- package/latticeai/runtime/tail_wiring.py +21 -0
- package/latticeai/services/p_reinforce.py +258 -0
- package/latticeai/services/router_context.py +52 -0
- package/ltcai_cli.py +7 -279
- package/p_reinforce.py +4 -255
- package/package.json +3 -2
- package/scripts/check_i18n_literals.mjs +52 -0
- package/scripts/release_smoke.py +133 -0
- package/scripts/wheel_smoke.py +4 -0
- 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-D76dWuQk.js +16 -0
- package/static/app/assets/index-D76dWuQk.js.map +1 -0
- package/static/app/assets/index-Div5vMlq.css +2 -0
- package/static/app/index.html +2 -2
- package/telegram_bot.py +9 -1002
- package/static/app/assets/index-B744yblP.css +0 -2
- package/static/app/assets/index-DYaUKNfl.js +0 -16
- package/static/app/assets/index-DYaUKNfl.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { FolderPlus, Globe2, HardDrive, Upload } from "lucide-react";
|
|
3
|
+
import { AlertCircle, CheckCircle2, FolderPlus, Globe2, HardDrive, Loader2, RotateCcw, Upload } from "lucide-react";
|
|
4
4
|
import { latticeApi } from "@/api/client";
|
|
5
5
|
import { ActionButton, DataPanel, EntityList, OperationResult, StructuredView, Tabs } from "@/components/primitives";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
@@ -41,10 +41,20 @@ export function CapturePage({ initialTab }: { initialTab?: string }) {
|
|
|
41
41
|
function FilesPanel() {
|
|
42
42
|
const qc = useQueryClient();
|
|
43
43
|
const docs = useQuery({ queryKey: ["documents"], queryFn: () => latticeApi.documents(200) });
|
|
44
|
+
const [queue, setQueue] = React.useState<UploadQueueItem[]>([]);
|
|
44
45
|
const upload = useMutation({
|
|
45
|
-
mutationFn: (files:
|
|
46
|
-
onSuccess: () =>
|
|
46
|
+
mutationFn: (files: File[]) => uploadFiles(files, setQueue),
|
|
47
|
+
onSuccess: () => {
|
|
48
|
+
void qc.invalidateQueries({ queryKey: ["documents"] });
|
|
49
|
+
void qc.invalidateQueries({ queryKey: ["graphStats"] });
|
|
50
|
+
void qc.invalidateQueries({ queryKey: ["memoryManager"] });
|
|
51
|
+
},
|
|
47
52
|
});
|
|
53
|
+
const beginUpload = React.useCallback((files: FileList | File[]) => {
|
|
54
|
+
const nextFiles = Array.from(files);
|
|
55
|
+
if (!nextFiles.length) return;
|
|
56
|
+
upload.mutate(nextFiles);
|
|
57
|
+
}, [upload]);
|
|
48
58
|
return (
|
|
49
59
|
<div className="grid gap-4 xl:grid-cols-[0.75fr_1.25fr]">
|
|
50
60
|
<Card>
|
|
@@ -53,26 +63,105 @@ function FilesPanel() {
|
|
|
53
63
|
<CardDescription>Choose files and Lattice will prepare them for search and memory.</CardDescription>
|
|
54
64
|
</CardHeader>
|
|
55
65
|
<CardContent>
|
|
56
|
-
<label
|
|
66
|
+
<label
|
|
67
|
+
className="flex min-h-56 cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-muted/30 p-6 text-center transition hover:bg-muted/50"
|
|
68
|
+
onDragOver={(event) => event.preventDefault()}
|
|
69
|
+
onDrop={(event) => {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
beginUpload(event.dataTransfer.files);
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
57
74
|
<Upload className="h-7 w-7 text-primary" />
|
|
58
|
-
<span className="text-lg font-semibold">
|
|
59
|
-
<span className="max-w-sm text-sm leading-6 text-muted-foreground">
|
|
60
|
-
<input type="file" multiple className="sr-only" onChange={(e) => e.target.files &&
|
|
75
|
+
<span className="text-lg font-semibold">Drop files or choose documents</span>
|
|
76
|
+
<span className="max-w-sm text-sm leading-6 text-muted-foreground">Each file is queued, parsed, written to Memory, and linked into the Brain graph with source metadata.</span>
|
|
77
|
+
<input type="file" multiple className="sr-only" onChange={(e) => e.target.files && beginUpload(e.target.files)} />
|
|
61
78
|
</label>
|
|
62
|
-
{
|
|
63
|
-
<div className="mt-3 space-y-2">
|
|
64
|
-
{upload.data.map((item, index) => <OperationResult key={index} result={item} successLabel="Upload completed" />)}
|
|
65
|
-
</div>
|
|
66
|
-
) : null}
|
|
79
|
+
<DocumentUploadQueue queue={queue} onRetry={(file) => beginUpload([file])} />
|
|
67
80
|
</CardContent>
|
|
68
81
|
</Card>
|
|
69
82
|
<DataPanel title="Uploaded documents" result={docs.data}>
|
|
70
|
-
{(data) =>
|
|
83
|
+
{(data) => (
|
|
84
|
+
<div className="space-y-3">
|
|
85
|
+
<EntityList items={(data as Record<string, unknown>).documents || data} titleKey="filename" metaKey="ingest_state" limit={12} />
|
|
86
|
+
<div className="rounded-md border border-border bg-background/55 p-3 text-sm text-muted-foreground">
|
|
87
|
+
Completed uploads appear here after they enter Memory. Graph links may continue building briefly after parsing finishes.
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
71
91
|
</DataPanel>
|
|
72
92
|
</div>
|
|
73
93
|
);
|
|
74
94
|
}
|
|
75
95
|
|
|
96
|
+
type UploadQueueItem = {
|
|
97
|
+
id: string;
|
|
98
|
+
file: File;
|
|
99
|
+
name: string;
|
|
100
|
+
size: number;
|
|
101
|
+
status: "queued" | "uploading" | "done" | "failed";
|
|
102
|
+
result?: Awaited<ReturnType<typeof latticeApi.uploadDocument>>;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
async function uploadFiles(files: File[], setQueue: React.Dispatch<React.SetStateAction<UploadQueueItem[]>>) {
|
|
106
|
+
const rows = files.map((file) => ({
|
|
107
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Date.now()}`,
|
|
108
|
+
file,
|
|
109
|
+
name: file.name,
|
|
110
|
+
size: file.size,
|
|
111
|
+
status: "queued" as const,
|
|
112
|
+
}));
|
|
113
|
+
setQueue((items) => [...rows, ...items].slice(0, 12));
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
setQueue((items) => items.map((item) => item.id === row.id ? { ...item, status: "uploading" } : item));
|
|
117
|
+
const result = await latticeApi.uploadDocument(row.file);
|
|
118
|
+
results.push(result);
|
|
119
|
+
setQueue((items) => items.map((item) => item.id === row.id ? { ...item, status: result.ok ? "done" : "failed", result } : item));
|
|
120
|
+
}
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function DocumentUploadQueue({ queue, onRetry }: { queue: UploadQueueItem[]; onRetry: (file: File) => void }) {
|
|
125
|
+
if (!queue.length) return null;
|
|
126
|
+
return (
|
|
127
|
+
<div className="mt-4 space-y-2">
|
|
128
|
+
{queue.map((item) => {
|
|
129
|
+
const detail = uploadResultDetail(item);
|
|
130
|
+
return (
|
|
131
|
+
<div key={item.id} className="rounded-md border border-border bg-background/55 p-3 text-sm">
|
|
132
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
133
|
+
<div>
|
|
134
|
+
<div className="flex items-center gap-2 font-medium">
|
|
135
|
+
{item.status === "done" ? <CheckCircle2 className="h-4 w-4 text-emerald-400" /> : item.status === "failed" ? <AlertCircle className="h-4 w-4 text-amber-400" /> : <Loader2 className="h-4 w-4 animate-spin text-primary" />}
|
|
136
|
+
{item.name}
|
|
137
|
+
</div>
|
|
138
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
139
|
+
{Math.max(1, Math.round(item.size / 1024))} KB · {detail}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
{item.status === "failed" ? (
|
|
143
|
+
<Button size="sm" variant="outline" onClick={() => onRetry(item.file)}>
|
|
144
|
+
<RotateCcw className="h-3.5 w-3.5" /> Retry
|
|
145
|
+
</Button>
|
|
146
|
+
) : null}
|
|
147
|
+
</div>
|
|
148
|
+
{item.result ? <OperationResult result={item.result} successLabel="Entered Brain and graph queue" /> : null}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function uploadResultDetail(item: UploadQueueItem) {
|
|
157
|
+
if (item.status === "queued") return "Waiting in the ingest queue";
|
|
158
|
+
if (item.status === "uploading") return "Parsing and sending to Memory";
|
|
159
|
+
if (!item.result?.ok) return item.result?.error || "Ingest failed before it entered the Brain";
|
|
160
|
+
const data = item.result.data || {};
|
|
161
|
+
const node = String(data.node_id || data.graph_node || data.provenance_id || "");
|
|
162
|
+
return node ? `Captured with source metadata · ${node}` : "Captured with source metadata";
|
|
163
|
+
}
|
|
164
|
+
|
|
76
165
|
function LocalPanel() {
|
|
77
166
|
const qc = useQueryClient();
|
|
78
167
|
const [path, setPath] = React.useState("");
|
|
@@ -109,12 +109,20 @@ function ModelsPanel() {
|
|
|
109
109
|
const profile = (data as Record<string, unknown>).profile as Record<string, unknown> | undefined;
|
|
110
110
|
return (
|
|
111
111
|
<div className="space-y-4">
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
{
|
|
116
|
-
{
|
|
117
|
-
|
|
112
|
+
<ModelRuntimeSummary
|
|
113
|
+
profile={profile || {}}
|
|
114
|
+
recommendation={recommendation || {}}
|
|
115
|
+
current={current}
|
|
116
|
+
currentId={currentId}
|
|
117
|
+
latestProgress={latestProgress}
|
|
118
|
+
lastResult={lastResult}
|
|
119
|
+
onReload={() => {
|
|
120
|
+
if (current) void prepareModel(String(current.recommended_load_id || current.id || currentId), String(current.recommended_engine || current.engine || "local_mlx"), consent);
|
|
121
|
+
}}
|
|
122
|
+
onUnload={() => {
|
|
123
|
+
if (currentId) void latticeApi.unloadModel(currentId).then(() => qc.invalidateQueries({ queryKey: ["models"] }));
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
118
126
|
<div className="grid gap-2 md:grid-cols-3 xl:grid-cols-6">
|
|
119
127
|
{[
|
|
120
128
|
["Environment Analysis", true, Cpu],
|
|
@@ -296,6 +304,64 @@ function AlternativeModels({ compatibility }: { compatibility: Record<string, un
|
|
|
296
304
|
);
|
|
297
305
|
}
|
|
298
306
|
|
|
307
|
+
function ModelRuntimeSummary({
|
|
308
|
+
profile,
|
|
309
|
+
recommendation,
|
|
310
|
+
current,
|
|
311
|
+
currentId,
|
|
312
|
+
latestProgress,
|
|
313
|
+
lastResult,
|
|
314
|
+
onReload,
|
|
315
|
+
onUnload,
|
|
316
|
+
}: {
|
|
317
|
+
profile: Record<string, unknown>;
|
|
318
|
+
recommendation: Record<string, unknown>;
|
|
319
|
+
current?: Record<string, unknown>;
|
|
320
|
+
currentId: string;
|
|
321
|
+
latestProgress: Record<string, unknown> | null;
|
|
322
|
+
lastResult: Record<string, unknown> | null;
|
|
323
|
+
onReload: () => void;
|
|
324
|
+
onUnload: () => void;
|
|
325
|
+
}) {
|
|
326
|
+
const loadedName = String(current?.name || current?.id || currentId || "");
|
|
327
|
+
const engine = String(current?.engine || current?.recommended_engine || latestProgress?.engine || "local_mlx");
|
|
328
|
+
const cachePath = String(current?.local_path || current?.storage_location || recommendation.cache_path || recommendation.storage_location || "~/.cache/huggingface / ~/.latticeai/models");
|
|
329
|
+
const progressStage = String(latestProgress?.stage || lastResult?.stage || (loadedName ? "ready" : "idle"));
|
|
330
|
+
return (
|
|
331
|
+
<div className="space-y-3">
|
|
332
|
+
<StatGrid stats={[
|
|
333
|
+
{ label: "Computer", value: profile?.os ? `${String(profile.os)} ${String(profile.arch || "")}` : "detected" },
|
|
334
|
+
{ label: "Engine", value: engine },
|
|
335
|
+
{ label: "Loaded model", value: loadedName || "No local model loaded" },
|
|
336
|
+
{ label: "Runtime state", value: progressStage },
|
|
337
|
+
]} />
|
|
338
|
+
<div className="rounded-lg border border-border bg-background/55 p-3 text-sm">
|
|
339
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
340
|
+
<div>
|
|
341
|
+
<div className="font-medium">{loadedName ? "Local model runtime is available" : "No model is loaded yet"}</div>
|
|
342
|
+
<div className="mt-1 text-muted-foreground">Cache/storage: {cachePath}</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div className="flex flex-wrap gap-2">
|
|
345
|
+
<Button variant="outline" size="sm" disabled={!loadedName} onClick={onReload}>Reload</Button>
|
|
346
|
+
<Button variant="outline" size="sm" disabled={!loadedName} onClick={onUnload}>Unload</Button>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
{latestProgress ? (
|
|
350
|
+
<div className="mt-3">
|
|
351
|
+
<div className="flex justify-between text-xs text-muted-foreground">
|
|
352
|
+
<span>{String(latestProgress.message || "Preparing model")}</span>
|
|
353
|
+
<span>{Number(latestProgress.percent || 0)}%</span>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="mt-1 h-2 overflow-hidden rounded-full bg-muted">
|
|
356
|
+
<div className="h-full bg-primary" style={{ width: `${Number(latestProgress.percent || 8)}%` }} />
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
) : null}
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
299
365
|
function ModelRecovery({ error }: { error: Record<string, unknown> }) {
|
|
300
366
|
const guidance = asArray<string>(error.recovery_guidance);
|
|
301
367
|
return (
|
package/frontend/src/styles.css
CHANGED
|
@@ -2982,6 +2982,58 @@ body {
|
|
|
2982
2982
|
box-shadow: 0 0 0 1px hsl(var(--brain-core) / 0.25);
|
|
2983
2983
|
}
|
|
2984
2984
|
|
|
2985
|
+
.ritual-form {
|
|
2986
|
+
max-width: 420px;
|
|
2987
|
+
margin: 0 auto;
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
.ritual-field-stack {
|
|
2991
|
+
display: grid;
|
|
2992
|
+
gap: 0.85rem;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
.ritual-field-label {
|
|
2996
|
+
margin-bottom: 0.25rem;
|
|
2997
|
+
color: hsl(var(--fg-muted));
|
|
2998
|
+
font-size: 0.75rem;
|
|
2999
|
+
font-weight: 760;
|
|
3000
|
+
letter-spacing: 0;
|
|
3001
|
+
text-transform: uppercase;
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
.ritual-error {
|
|
3005
|
+
margin-top: 0.85rem;
|
|
3006
|
+
padding: 0.6rem 0.85rem;
|
|
3007
|
+
border: 1px solid hsl(var(--destructive) / 0.4);
|
|
3008
|
+
border-radius: 8px;
|
|
3009
|
+
background: hsl(var(--destructive) / 0.12);
|
|
3010
|
+
font-size: 0.9rem;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
.ritual-full-button,
|
|
3014
|
+
.ritual-primary-model-button,
|
|
3015
|
+
.ritual-model-card {
|
|
3016
|
+
width: 100%;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
.ritual-full-button {
|
|
3020
|
+
margin-top: 1rem;
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
.ritual-note,
|
|
3024
|
+
.ritual-local-note {
|
|
3025
|
+
color: hsl(var(--fg-muted));
|
|
3026
|
+
font-size: 0.75rem;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
.ritual-note {
|
|
3030
|
+
margin-top: 0.6rem;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
.ritual-local-note {
|
|
3034
|
+
margin-top: 0.9rem;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
2985
3037
|
.ritual-fact-grid {
|
|
2986
3038
|
display: grid;
|
|
2987
3039
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
@@ -3011,6 +3063,59 @@ body {
|
|
|
3011
3063
|
color: hsl(var(--fg));
|
|
3012
3064
|
}
|
|
3013
3065
|
|
|
3066
|
+
.ritual-fact-detail,
|
|
3067
|
+
.ritual-muted-text,
|
|
3068
|
+
.ritual-muted-hint,
|
|
3069
|
+
.ritual-status-card {
|
|
3070
|
+
color: hsl(var(--fg-muted));
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
.ritual-fact-detail {
|
|
3074
|
+
margin-top: 0.2rem;
|
|
3075
|
+
font-size: 0.8rem;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
.ritual-analysis-card,
|
|
3079
|
+
.ritual-centered-actions {
|
|
3080
|
+
margin-top: 1rem;
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
.ritual-centered-actions {
|
|
3084
|
+
display: flex;
|
|
3085
|
+
justify-content: center;
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
.ritual-inline-row {
|
|
3089
|
+
display: flex;
|
|
3090
|
+
align-items: center;
|
|
3091
|
+
gap: 0.6rem;
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
.ritual-core-icon {
|
|
3095
|
+
color: hsl(var(--brain-core));
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
.ritual-strong-text {
|
|
3099
|
+
font-weight: 620;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
.ritual-muted-text {
|
|
3103
|
+
font-size: 0.9rem;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
.ritual-wide-button {
|
|
3107
|
+
min-width: 260px;
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
.ritual-model-list {
|
|
3111
|
+
max-width: 560px;
|
|
3112
|
+
margin: 0 auto;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
.ritual-primary-model-button {
|
|
3116
|
+
margin-bottom: 0.85rem;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3014
3119
|
.ritual-model-card {
|
|
3015
3120
|
text-align: left;
|
|
3016
3121
|
padding: 1.15rem 1.35rem;
|
|
@@ -3046,6 +3151,80 @@ body {
|
|
|
3046
3151
|
margin-top: 0.25rem;
|
|
3047
3152
|
}
|
|
3048
3153
|
|
|
3154
|
+
.ritual-model-warning {
|
|
3155
|
+
margin-top: 0.35rem;
|
|
3156
|
+
color: hsl(var(--destructive));
|
|
3157
|
+
font-size: 0.85rem;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
.ritual-action-row,
|
|
3161
|
+
.ritual-button-row {
|
|
3162
|
+
display: flex;
|
|
3163
|
+
align-items: center;
|
|
3164
|
+
justify-content: center;
|
|
3165
|
+
gap: 0.75rem;
|
|
3166
|
+
flex-wrap: wrap;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
.ritual-action-row {
|
|
3170
|
+
margin-top: 1.1rem;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
.ritual-muted-hint {
|
|
3174
|
+
font-size: 0.82rem;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
.ritual-install-brain {
|
|
3178
|
+
margin: 0.6rem auto 1rem;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
.ritual-consent-panel {
|
|
3182
|
+
display: grid;
|
|
3183
|
+
grid-template-columns: minmax(0, 1fr) minmax(220px, 0.9fr);
|
|
3184
|
+
gap: 1rem;
|
|
3185
|
+
max-width: 620px;
|
|
3186
|
+
margin: 0 auto 1rem;
|
|
3187
|
+
padding: 1rem;
|
|
3188
|
+
border: 1px solid hsl(var(--border) / 0.6);
|
|
3189
|
+
border-radius: 8px;
|
|
3190
|
+
background: hsl(var(--bg) / 0.48);
|
|
3191
|
+
text-align: left;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
.ritual-consent-panel strong {
|
|
3195
|
+
display: block;
|
|
3196
|
+
margin-bottom: 0.25rem;
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
.ritual-consent-panel p {
|
|
3200
|
+
margin: 0;
|
|
3201
|
+
color: hsl(var(--fg-muted));
|
|
3202
|
+
font-size: 0.9rem;
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
.ritual-consent-panel dl {
|
|
3206
|
+
display: grid;
|
|
3207
|
+
gap: 0.45rem;
|
|
3208
|
+
margin: 0;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
.ritual-consent-panel dl div {
|
|
3212
|
+
display: grid;
|
|
3213
|
+
grid-template-columns: 110px minmax(0, 1fr);
|
|
3214
|
+
gap: 0.5rem;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
.ritual-consent-panel dt {
|
|
3218
|
+
color: hsl(var(--fg-muted));
|
|
3219
|
+
font-size: 0.78rem;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
.ritual-consent-panel dd {
|
|
3223
|
+
margin: 0;
|
|
3224
|
+
overflow-wrap: anywhere;
|
|
3225
|
+
font-size: 0.86rem;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3049
3228
|
.ritual-progress {
|
|
3050
3229
|
margin: 1.5rem 0;
|
|
3051
3230
|
}
|
|
@@ -3077,6 +3256,11 @@ body {
|
|
|
3077
3256
|
color: hsl(var(--fg));
|
|
3078
3257
|
}
|
|
3079
3258
|
|
|
3259
|
+
.ritual-stage-icon {
|
|
3260
|
+
width: 15px;
|
|
3261
|
+
height: 15px;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3080
3264
|
.ritual-bar {
|
|
3081
3265
|
height: 6px;
|
|
3082
3266
|
background: hsl(var(--border) / 0.6);
|
|
@@ -3092,12 +3276,48 @@ body {
|
|
|
3092
3276
|
transition: width 280ms ease;
|
|
3093
3277
|
}
|
|
3094
3278
|
|
|
3279
|
+
.ritual-bar-fill.progress-0 { width: 4%; }
|
|
3280
|
+
.ritual-bar-fill.progress-10 { width: 10%; }
|
|
3281
|
+
.ritual-bar-fill.progress-20 { width: 20%; }
|
|
3282
|
+
.ritual-bar-fill.progress-30 { width: 30%; }
|
|
3283
|
+
.ritual-bar-fill.progress-40 { width: 40%; }
|
|
3284
|
+
.ritual-bar-fill.progress-50 { width: 50%; }
|
|
3285
|
+
.ritual-bar-fill.progress-60 { width: 60%; }
|
|
3286
|
+
.ritual-bar-fill.progress-70 { width: 70%; }
|
|
3287
|
+
.ritual-bar-fill.progress-80 { width: 80%; }
|
|
3288
|
+
.ritual-bar-fill.progress-90 { width: 90%; }
|
|
3289
|
+
.ritual-bar-fill.progress-100 { width: 100%; }
|
|
3290
|
+
|
|
3095
3291
|
.ritual-status {
|
|
3096
3292
|
font-size: 0.95rem;
|
|
3097
3293
|
color: hsl(var(--fg-muted));
|
|
3098
3294
|
margin: 0.8rem 0;
|
|
3099
3295
|
}
|
|
3100
3296
|
|
|
3297
|
+
.ritual-status-card {
|
|
3298
|
+
max-width: 540px;
|
|
3299
|
+
margin: 0.8rem auto 0;
|
|
3300
|
+
font-size: 0.86rem;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
.ritual-error-card {
|
|
3304
|
+
border-color: hsl(var(--destructive) / 0.45);
|
|
3305
|
+
background: hsl(var(--destructive) / 0.07);
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
.ritual-install-error {
|
|
3309
|
+
margin-bottom: 1rem;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
.ritual-error-detail {
|
|
3313
|
+
margin-top: 0.5rem;
|
|
3314
|
+
font-size: 0.85rem;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
.ritual-button-row {
|
|
3318
|
+
margin-top: 1rem;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3101
3321
|
.ritual-success {
|
|
3102
3322
|
text-align: center;
|
|
3103
3323
|
padding: 1.5rem;
|
|
@@ -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.3.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/tools.py
CHANGED
|
@@ -26,7 +26,8 @@ from latticeai.services.tool_dispatch import (
|
|
|
26
26
|
get_tool_permission,
|
|
27
27
|
list_tool_permissions,
|
|
28
28
|
)
|
|
29
|
-
from
|
|
29
|
+
from latticeai.services.router_context import ToolRouterContext
|
|
30
|
+
from latticeai.services.p_reinforce import BRAIN_DIR
|
|
30
31
|
from tools import (
|
|
31
32
|
AGENT_ROOT,
|
|
32
33
|
ToolError,
|
|
@@ -177,30 +178,56 @@ class ToolGitShowRequest(BaseModel):
|
|
|
177
178
|
|
|
178
179
|
def create_tools_router(
|
|
179
180
|
*,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
181
|
+
tool_context: ToolRouterContext | None = None,
|
|
182
|
+
config=None,
|
|
183
|
+
ingestion_pipeline=None,
|
|
184
|
+
data_dir: Path | None = None,
|
|
185
|
+
static_dir: Path | None = None,
|
|
186
|
+
model_router=None,
|
|
187
|
+
require_user=None,
|
|
188
|
+
require_admin=None,
|
|
189
|
+
get_current_user=None,
|
|
190
|
+
clear_history=None,
|
|
191
|
+
append_audit_event=None,
|
|
192
|
+
enforce_rate_limit=None,
|
|
193
|
+
bytes_match_extension=None,
|
|
194
|
+
classify_sensitive_message=None,
|
|
195
|
+
save_to_history=None,
|
|
196
|
+
enable_graph: bool | None = None,
|
|
197
|
+
knowledge_graph=None,
|
|
198
|
+
require_graph=None,
|
|
199
|
+
local_kg_watcher=None,
|
|
200
|
+
load_mcp_installs=None,
|
|
201
|
+
recommend_mcps=None,
|
|
202
|
+
install_mcp=None,
|
|
203
|
+
mcp_public_item=None,
|
|
202
204
|
hooks=None,
|
|
203
205
|
) -> APIRouter:
|
|
206
|
+
if tool_context is not None:
|
|
207
|
+
config = tool_context.config
|
|
208
|
+
ingestion_pipeline = tool_context.ingestion_pipeline
|
|
209
|
+
data_dir = tool_context.data_dir
|
|
210
|
+
static_dir = tool_context.static_dir
|
|
211
|
+
model_router = tool_context.model_router
|
|
212
|
+
require_user = tool_context.require_user
|
|
213
|
+
require_admin = tool_context.require_admin
|
|
214
|
+
get_current_user = tool_context.get_current_user
|
|
215
|
+
clear_history = tool_context.clear_history
|
|
216
|
+
append_audit_event = tool_context.append_audit_event
|
|
217
|
+
enforce_rate_limit = tool_context.enforce_rate_limit
|
|
218
|
+
bytes_match_extension = tool_context.bytes_match_extension
|
|
219
|
+
classify_sensitive_message = tool_context.classify_sensitive_message
|
|
220
|
+
save_to_history = tool_context.save_to_history
|
|
221
|
+
enable_graph = tool_context.enable_graph
|
|
222
|
+
knowledge_graph = tool_context.knowledge_graph
|
|
223
|
+
require_graph = tool_context.require_graph
|
|
224
|
+
local_kg_watcher = tool_context.local_kg_watcher
|
|
225
|
+
load_mcp_installs = tool_context.load_mcp_installs
|
|
226
|
+
recommend_mcps = tool_context.recommend_mcps
|
|
227
|
+
install_mcp = tool_context.install_mcp
|
|
228
|
+
mcp_public_item = tool_context.mcp_public_item
|
|
229
|
+
hooks = tool_context.hooks
|
|
230
|
+
|
|
204
231
|
api_router = APIRouter()
|
|
205
232
|
HOOKS = hooks
|
|
206
233
|
CONFIG = config
|