@cryptiklemur/lattice 1.11.3 → 1.11.5
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/client/src/router.tsx +113 -20
- package/package.json +1 -1
- package/server/src/daemon.ts +2 -1
- package/server/src/project/sdk-bridge.ts +23 -0
package/client/src/router.tsx
CHANGED
|
@@ -83,7 +83,10 @@ function LoadingScreen() {
|
|
|
83
83
|
var logoRight = centerX + logoHalf;
|
|
84
84
|
var logoBottom = centerY + logoHalf;
|
|
85
85
|
|
|
86
|
-
var
|
|
86
|
+
var computedPrimary = getComputedStyle(document.documentElement).getPropertyValue("--color-primary").trim();
|
|
87
|
+
var primary = computedPrimary ? "oklch(" + computedPrimary + ")" : "oklch(55% 0.25 280)";
|
|
88
|
+
|
|
89
|
+
var prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
87
90
|
|
|
88
91
|
type Dot = { x: number; y: number; col: number; row: number; hidden: boolean; brightness: number };
|
|
89
92
|
var dots: Dot[] = [];
|
|
@@ -123,6 +126,37 @@ function LoadingScreen() {
|
|
|
123
126
|
return result;
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
function drawStatic() {
|
|
130
|
+
ctx!.clearRect(0, 0, canvasSize, canvasSize);
|
|
131
|
+
for (var di = 0; di < dots.length; di++) {
|
|
132
|
+
var dot = dots[di];
|
|
133
|
+
if (dot.hidden) continue;
|
|
134
|
+
ctx!.beginPath();
|
|
135
|
+
ctx!.arc(dot.x, dot.y, dotRadius, 0, Math.PI * 2);
|
|
136
|
+
ctx!.fillStyle = primary;
|
|
137
|
+
ctx!.globalAlpha = 0.15;
|
|
138
|
+
ctx!.fill();
|
|
139
|
+
ctx!.globalAlpha = 1.0;
|
|
140
|
+
}
|
|
141
|
+
var squares = [
|
|
142
|
+
[logoLeft, logoTop],
|
|
143
|
+
[logoLeft + logoSquare + logoGap, logoTop],
|
|
144
|
+
[logoLeft, logoTop + logoSquare + logoGap],
|
|
145
|
+
[logoLeft + logoSquare + logoGap, logoTop + logoSquare + logoGap],
|
|
146
|
+
];
|
|
147
|
+
for (var si = 0; si < squares.length; si++) {
|
|
148
|
+
ctx!.fillStyle = primary;
|
|
149
|
+
ctx!.globalAlpha = 0.9;
|
|
150
|
+
ctx!.fillRect(squares[si][0], squares[si][1], logoSquare, logoSquare);
|
|
151
|
+
ctx!.globalAlpha = 1.0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (prefersReducedMotion) {
|
|
156
|
+
drawStatic();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
function animate() {
|
|
127
161
|
now = performance.now();
|
|
128
162
|
ctx!.clearRect(0, 0, canvasSize, canvasSize);
|
|
@@ -277,8 +311,17 @@ function RemoveProjectConfirm() {
|
|
|
277
311
|
}
|
|
278
312
|
})();
|
|
279
313
|
|
|
314
|
+
useEffect(function () {
|
|
315
|
+
if (!slug) return;
|
|
316
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
317
|
+
if (e.key === "Escape") sidebar.closeConfirmRemove();
|
|
318
|
+
}
|
|
319
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
320
|
+
return function () { document.removeEventListener("keydown", handleKeyDown); };
|
|
321
|
+
}, [slug]);
|
|
322
|
+
|
|
280
323
|
return (
|
|
281
|
-
<div className="fixed inset-0 z-[9999] flex items-center justify-center">
|
|
324
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center" role="dialog" aria-modal="true" aria-label="Remove Project">
|
|
282
325
|
<div className="absolute inset-0 bg-black/50" onClick={sidebar.closeConfirmRemove} />
|
|
283
326
|
<div className="relative bg-base-200 border border-base-content/15 rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden">
|
|
284
327
|
<div className="px-5 py-4 border-b border-base-content/15">
|
|
@@ -369,6 +412,9 @@ function RootLayout() {
|
|
|
369
412
|
|
|
370
413
|
return (
|
|
371
414
|
<div className="flex w-full h-full overflow-hidden bg-base-100">
|
|
415
|
+
<a href="#main-content" className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[99999] focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-content focus:rounded-lg focus:text-sm focus:font-semibold">
|
|
416
|
+
Skip to content
|
|
417
|
+
</a>
|
|
372
418
|
<LoadingScreen />
|
|
373
419
|
<div className="drawer lg:drawer-open h-full w-full">
|
|
374
420
|
<input
|
|
@@ -376,12 +422,12 @@ function RootLayout() {
|
|
|
376
422
|
type="checkbox"
|
|
377
423
|
className="drawer-toggle"
|
|
378
424
|
checked={sidebar.drawerOpen}
|
|
379
|
-
|
|
425
|
+
readOnly
|
|
380
426
|
/>
|
|
381
427
|
|
|
382
|
-
<
|
|
428
|
+
<main id="main-content" className="drawer-content flex flex-col h-full min-w-0 overflow-hidden">
|
|
383
429
|
<Outlet />
|
|
384
|
-
</
|
|
430
|
+
</main>
|
|
385
431
|
|
|
386
432
|
<div ref={drawerSideRef} className="drawer-side z-50 h-full">
|
|
387
433
|
<label
|
|
@@ -390,9 +436,9 @@ function RootLayout() {
|
|
|
390
436
|
className="drawer-overlay"
|
|
391
437
|
onClick={closeDrawer}
|
|
392
438
|
/>
|
|
393
|
-
<
|
|
439
|
+
<nav aria-label="Sidebar navigation" className="h-full w-full lg:w-[284px] flex flex-col overflow-hidden">
|
|
394
440
|
<Sidebar onSessionSelect={closeDrawer} />
|
|
395
|
-
</
|
|
441
|
+
</nav>
|
|
396
442
|
</div>
|
|
397
443
|
</div>
|
|
398
444
|
<NodeSettingsModal
|
|
@@ -408,24 +454,71 @@ function RootLayout() {
|
|
|
408
454
|
);
|
|
409
455
|
}
|
|
410
456
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
457
|
+
import { Component } from "react";
|
|
458
|
+
import type { ReactNode, ErrorInfo } from "react";
|
|
459
|
+
import { AlertTriangle, RefreshCw } from "lucide-react";
|
|
460
|
+
|
|
461
|
+
class ViewErrorBoundary extends Component<{ children: ReactNode; viewName: string }, { hasError: boolean; error: Error | null }> {
|
|
462
|
+
constructor(props: { children: ReactNode; viewName: string }) {
|
|
463
|
+
super(props);
|
|
464
|
+
this.state = { hasError: false, error: null };
|
|
415
465
|
}
|
|
416
|
-
|
|
417
|
-
return
|
|
466
|
+
static getDerivedStateFromError(error: Error) {
|
|
467
|
+
return { hasError: true, error: error };
|
|
418
468
|
}
|
|
419
|
-
|
|
420
|
-
|
|
469
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
470
|
+
console.error("[lattice] View error in " + this.props.viewName + ":", error, info.componentStack);
|
|
421
471
|
}
|
|
422
|
-
|
|
423
|
-
|
|
472
|
+
render() {
|
|
473
|
+
if (this.state.hasError) {
|
|
474
|
+
var self = this;
|
|
475
|
+
return (
|
|
476
|
+
<div className="flex flex-col items-center justify-center h-full bg-base-100 bg-lattice-grid gap-4 p-8">
|
|
477
|
+
<AlertTriangle size={32} className="text-warning/50" />
|
|
478
|
+
<div className="text-center max-w-[500px]">
|
|
479
|
+
<p className="text-[14px] font-mono text-base-content/60 mb-1">
|
|
480
|
+
Error in {this.props.viewName}
|
|
481
|
+
</p>
|
|
482
|
+
<p className="text-[12px] text-base-content/30 mb-4 font-mono break-all">
|
|
483
|
+
{this.state.error?.message || "Unknown error"}
|
|
484
|
+
</p>
|
|
485
|
+
<button
|
|
486
|
+
onClick={function () { self.setState({ hasError: false, error: null }); }}
|
|
487
|
+
className="btn btn-ghost btn-sm text-[12px] gap-1.5"
|
|
488
|
+
>
|
|
489
|
+
<RefreshCw size={12} />
|
|
490
|
+
Retry
|
|
491
|
+
</button>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return this.props.children;
|
|
424
497
|
}
|
|
425
|
-
|
|
426
|
-
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function IndexPage() {
|
|
501
|
+
var sidebar = useSidebar();
|
|
502
|
+
var viewName = sidebar.activeView.type;
|
|
503
|
+
var content;
|
|
504
|
+
if (viewName === "dashboard") {
|
|
505
|
+
content = <DashboardView />;
|
|
506
|
+
} else if (viewName === "settings") {
|
|
507
|
+
content = <SettingsView />;
|
|
508
|
+
} else if (viewName === "project-settings") {
|
|
509
|
+
content = <ProjectSettingsView />;
|
|
510
|
+
} else if (viewName === "project-dashboard") {
|
|
511
|
+
content = <ProjectDashboardView />;
|
|
512
|
+
} else if (viewName === "analytics") {
|
|
513
|
+
content = <AnalyticsView />;
|
|
514
|
+
} else {
|
|
515
|
+
content = <WorkspaceView />;
|
|
427
516
|
}
|
|
428
|
-
return
|
|
517
|
+
return (
|
|
518
|
+
<ViewErrorBoundary viewName={viewName}>
|
|
519
|
+
{content}
|
|
520
|
+
</ViewErrorBoundary>
|
|
521
|
+
);
|
|
429
522
|
}
|
|
430
523
|
|
|
431
524
|
var rootRoute = createRootRoute({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.5",
|
|
4
4
|
"description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aaron Scherer <me@aaronscherer.me>",
|
package/server/src/daemon.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { ClientMessage, MeshMessage } from "@lattice/shared";
|
|
|
15
15
|
import "./handlers/session";
|
|
16
16
|
import "./handlers/chat";
|
|
17
17
|
import "./handlers/attachment";
|
|
18
|
-
import { loadInterruptedSessions, unwatchSessionLock } from "./project/sdk-bridge";
|
|
18
|
+
import { loadInterruptedSessions, unwatchSessionLock, cleanupClientPermissions } from "./project/sdk-bridge";
|
|
19
19
|
import { clearActiveSession, getActiveSession } from "./handlers/chat";
|
|
20
20
|
import "./handlers/fs";
|
|
21
21
|
import "./handlers/terminal";
|
|
@@ -312,6 +312,7 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
|
|
|
312
312
|
removeClient(ws.data.id);
|
|
313
313
|
cleanupClientTerminals(ws.data.id);
|
|
314
314
|
cleanupClientAttachments(ws.data.id);
|
|
315
|
+
cleanupClientPermissions(ws.data.id);
|
|
315
316
|
console.log(`[lattice] Client disconnected: ${ws.data.id}`);
|
|
316
317
|
},
|
|
317
318
|
},
|
|
@@ -141,6 +141,22 @@ export function deletePendingPermission(requestId: string): void {
|
|
|
141
141
|
pendingPermissions.delete(requestId);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
export function cleanupClientPermissions(clientId: string): void {
|
|
145
|
+
var toRemove: string[] = [];
|
|
146
|
+
pendingPermissions.forEach(function (entry, requestId) {
|
|
147
|
+
if (entry.clientId === clientId) {
|
|
148
|
+
toRemove.push(requestId);
|
|
149
|
+
entry.resolve({ behavior: "deny", message: "Client disconnected.", toolUseID: entry.toolUseID });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
for (var i = 0; i < toRemove.length; i++) {
|
|
153
|
+
pendingPermissions.delete(toRemove[i]);
|
|
154
|
+
}
|
|
155
|
+
if (toRemove.length > 0) {
|
|
156
|
+
console.log("[lattice] Cleaned up " + toRemove.length + " pending permission(s) for disconnected client " + clientId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
144
160
|
export function addAutoApprovedTool(sessionId: string, toolName: string): void {
|
|
145
161
|
var tools = autoApprovedTools.get(sessionId);
|
|
146
162
|
if (!tools) {
|
|
@@ -423,6 +439,13 @@ export function startChatStream(options: ChatStreamOptions): void {
|
|
|
423
439
|
}
|
|
424
440
|
}
|
|
425
441
|
|
|
442
|
+
if (toolName === "Bash") {
|
|
443
|
+
var cmd = ((input.command || "") as string).trim();
|
|
444
|
+
if (cmd.startsWith("cd ")) {
|
|
445
|
+
return Promise.resolve({ behavior: "allow", updatedInput: input, toolUseID: options.toolUseID } as PermissionResult);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
426
449
|
var allowRules: string[] = [];
|
|
427
450
|
if (existsSync(projectSettingsPath)) {
|
|
428
451
|
try {
|