@cryptiklemur/lattice 1.46.7 → 1.47.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.
@@ -1,4 +1,7 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { Loader2, Check, X } from "lucide-react";
1
3
  import type { SaveState } from "../../hooks/useSaveState";
4
+ import { showToast } from "./Toast";
2
5
 
3
6
  interface SaveFooterProps {
4
7
  dirty: boolean;
@@ -10,6 +13,18 @@ interface SaveFooterProps {
10
13
 
11
14
  export function SaveFooter({ dirty, saving, saveState, onSave, extraStatus }: SaveFooterProps) {
12
15
  var disabled = saving || (!dirty && saveState !== "error");
16
+ var prevStateRef = useRef<SaveState>("idle");
17
+
18
+ useEffect(function () {
19
+ if (prevStateRef.current !== saveState) {
20
+ if (saveState === "saved") {
21
+ showToast("Settings saved", "success");
22
+ } else if (saveState === "error") {
23
+ showToast("Failed to save settings — server did not respond within 5 seconds", "error");
24
+ }
25
+ prevStateRef.current = saveState;
26
+ }
27
+ }, [saveState]);
13
28
 
14
29
  return (
15
30
  <div className="flex items-center justify-end gap-3">
@@ -20,18 +35,28 @@ export function SaveFooter({ dirty, saving, saveState, onSave, extraStatus }: Sa
20
35
  <div className="text-[11px] text-warning/70">Unsaved changes</div>
21
36
  )}
22
37
  {saveState === "error" && (
23
- <div className="text-[11px] text-error">Save failed — try again</div>
38
+ <div className="flex items-center gap-1.5 text-[11px] text-error">
39
+ <X size={11} />
40
+ Save failed — server did not respond
41
+ </div>
42
+ )}
43
+ {saveState === "saved" && (
44
+ <div className="flex items-center gap-1.5 text-[11px] text-success/70">
45
+ <Check size={11} />
46
+ Saved
47
+ </div>
24
48
  )}
25
49
  <button
26
50
  onClick={onSave}
27
51
  disabled={disabled}
28
52
  className={
29
- "btn btn-sm " +
53
+ "btn btn-sm gap-1.5 " +
30
54
  (saveState === "saved" ? "btn-success" : saveState === "error" ? "btn-error" : "btn-primary") +
31
55
  (disabled ? " opacity-50 cursor-not-allowed" : "")
32
56
  }
33
57
  >
34
- {saving ? "Saving..." : saveState === "saved" ? "Saved" : saveState === "error" ? "Retry" : "Save Changes"}
58
+ {saving && <Loader2 size={13} className="animate-spin" />}
59
+ {saving ? "Saving..." : saveState === "error" ? "Retry" : "Save Changes"}
35
60
  </button>
36
61
  </div>
37
62
  );
@@ -13,6 +13,7 @@ import type {
13
13
  ChatContextUsageMessage,
14
14
  ChatContextBreakdownMessage,
15
15
  ChatPromptSuggestionMessage,
16
+ ChatElicitationRequestMessage,
16
17
  SessionHistoryMessage,
17
18
  ServerMessage,
18
19
  } from "@lattice/shared";
@@ -49,12 +50,12 @@ import {
49
50
  removeQueuedMessage,
50
51
  updateQueuedMessage,
51
52
  clearMessageQueue,
52
- setSessionBusy,
53
53
  addPromptQuestion,
54
54
  addTodoUpdate,
55
55
  setIsPlanMode,
56
56
  setBudgetStatus,
57
57
  setBudgetExceeded,
58
+ updateRateLimit,
58
59
  } from "../stores/session";
59
60
  import type { SessionState, BudgetStatus } from "../stores/session";
60
61
 
@@ -119,7 +120,12 @@ export function useSession(): UseSessionReturn {
119
120
  setFailedInput(null);
120
121
  setPromptSuggestion(null);
121
122
  setIsProcessing(true);
122
- setSessionBusy(false);
123
+ addSessionMessage({
124
+ type: "user",
125
+ uuid: "optimistic-" + Date.now(),
126
+ text: text,
127
+ timestamp: Date.now(),
128
+ } as HistoryMessage);
123
129
  pinTab("chat-" + currentSessionId);
124
130
  sendRef.current(msg as ChatSendMessage);
125
131
  }
@@ -143,6 +149,16 @@ export function useSession(): UseSessionReturn {
143
149
  if (isStaleStream()) return;
144
150
  var m = msg as ChatUserMessage;
145
151
  setCurrentAssistantUuid(null);
152
+ var messages = getSessionStore().state.messages;
153
+ var last = messages.length > 0 ? messages[messages.length - 1] : null;
154
+ if (last && last.type === "user" && last.uuid && last.uuid.startsWith("optimistic-") && last.text === m.text) {
155
+ getSessionStore().setState(function (s) {
156
+ var updated = s.messages.slice();
157
+ updated[updated.length - 1] = { ...updated[updated.length - 1], uuid: m.uuid };
158
+ return { ...s, messages: updated };
159
+ });
160
+ return;
161
+ }
146
162
  addSessionMessage({
147
163
  type: "user",
148
164
  uuid: m.uuid,
@@ -279,6 +295,22 @@ export function useSession(): UseSessionReturn {
279
295
  updatePermissionStatus(m.requestId, m.status);
280
296
  }
281
297
 
298
+ function handleElicitationRequest(msg: ServerMessage) {
299
+ var m = msg as ChatElicitationRequestMessage;
300
+ setCurrentAssistantUuid(null);
301
+ addSessionMessage({
302
+ type: "elicitation",
303
+ toolId: m.requestId,
304
+ elicitationMode: m.mode,
305
+ elicitationServerName: m.serverName,
306
+ elicitationMessage: m.message,
307
+ elicitationUrl: m.url,
308
+ elicitationSchema: m.requestedSchema,
309
+ elicitationStatus: "pending",
310
+ timestamp: Date.now(),
311
+ } as HistoryMessage);
312
+ }
313
+
282
314
  function handleHistoryPage(msg: ServerMessage) {
283
315
  var m = msg as { type: string; sessionId: string; messages: HistoryMessage[]; hasMore: boolean };
284
316
  var state = getSessionStore().state;
@@ -306,34 +338,46 @@ export function useSession(): UseSessionReturn {
306
338
  var projectSlug = m.projectSlug || getSessionStore().state.activeProjectSlug;
307
339
  setSidebarSessionId(m.sessionId);
308
340
  streamSessionId = m.sessionId;
309
- if (m.busy) {
310
- activeStreamGeneration = getStreamGeneration();
311
- }
312
341
  if (m.title) {
313
342
  updateSessionTabTitle(m.sessionId, m.title);
314
343
  }
315
- getSessionStore().setState(function (state) {
316
- return {
317
- ...state,
318
- activeProjectSlug: projectSlug,
319
- activeSessionId: m.sessionId,
320
- activeSessionTitle: m.title ?? null,
321
- messages: mergeToolResults(m.messages),
322
- isProcessing: false,
323
- currentStatus: null,
324
- pendingPermissionCount: 0,
325
- lastResponseCost: null,
326
- lastResponseDuration: null,
327
- lastReadIndex: null,
328
- historyLoading: false,
329
- historyHasMore: m.hasMore || false,
330
- historyTotalMessages: m.totalMessages || m.messages.length,
331
- wasInterrupted: m.interrupted || false,
332
- isBusy: m.busy || false,
333
- busyOwner: m.busyOwner ?? null,
334
- isPlanMode: false,
335
- };
336
- });
344
+ var currentState = getSessionStore().state;
345
+ var alreadyCached = currentState.activeSessionId === m.sessionId
346
+ && !currentState.historyLoading
347
+ && currentState.messages.length > 0;
348
+
349
+ if (alreadyCached) {
350
+ getSessionStore().setState(function (state) {
351
+ return {
352
+ ...state,
353
+ activeSessionTitle: m.title ?? state.activeSessionTitle,
354
+ historyHasMore: m.hasMore || state.historyHasMore,
355
+ historyTotalMessages: m.totalMessages || state.historyTotalMessages,
356
+ wasInterrupted: m.interrupted || false,
357
+ };
358
+ });
359
+ } else {
360
+ getSessionStore().setState(function (state) {
361
+ return {
362
+ ...state,
363
+ activeProjectSlug: projectSlug,
364
+ activeSessionId: m.sessionId,
365
+ activeSessionTitle: m.title ?? null,
366
+ messages: mergeToolResults(m.messages),
367
+ isProcessing: false,
368
+ currentStatus: null,
369
+ pendingPermissionCount: 0,
370
+ lastResponseCost: null,
371
+ lastResponseDuration: null,
372
+ lastReadIndex: null,
373
+ historyLoading: false,
374
+ historyHasMore: m.hasMore || false,
375
+ historyTotalMessages: m.totalMessages || m.messages.length,
376
+ wasInterrupted: m.interrupted || false,
377
+ isPlanMode: false,
378
+ };
379
+ });
380
+ }
337
381
  var storedIndex = getLastReadIndex(m.sessionId);
338
382
  if (storedIndex >= 0 && storedIndex < m.messages.length) {
339
383
  setLastReadIndex(storedIndex);
@@ -360,17 +404,6 @@ export function useSession(): UseSessionReturn {
360
404
  setPromptSuggestion(m.suggestion);
361
405
  }
362
406
 
363
- function handleSessionBusy(msg: ServerMessage) {
364
- var m = msg as { type: string; sessionId: string; busy: boolean; busyOwner?: "cli" | "lattice" };
365
- var sessionState = getSessionStore().state;
366
- if (m.sessionId === sessionState.activeSessionId) {
367
- if (m.busy && sessionState.isProcessing) {
368
- return;
369
- }
370
- setSessionBusy(m.busy, m.busyOwner);
371
- }
372
- }
373
-
374
407
  function handlePromptRequest(msg: ServerMessage) {
375
408
  if (isStaleStream()) return;
376
409
  var m = msg as { type: string; requestId: string; questions: Array<{ question: string; header: string; options: Array<{ label: string; description: string; preview?: string }>; multiSelect: boolean }> };
@@ -397,6 +430,20 @@ export function useSession(): UseSessionReturn {
397
430
  setBudgetStatus({ dailySpend: m.dailySpend, dailyLimit: m.dailyLimit, enforcement: m.enforcement });
398
431
  }
399
432
 
433
+ function handleRateLimit(msg: ServerMessage) {
434
+ var m = msg as { type: string; status: "allowed" | "allowed_warning" | "rejected"; utilization?: number; resetsAt?: number; rateLimitType?: string; overageStatus?: string; overageResetsAt?: number; isUsingOverage?: boolean };
435
+ updateRateLimit({
436
+ status: m.status,
437
+ utilization: m.utilization,
438
+ resetsAt: m.resetsAt,
439
+ rateLimitType: m.rateLimitType || "unknown",
440
+ overageStatus: m.overageStatus,
441
+ overageResetsAt: m.overageResetsAt,
442
+ isUsingOverage: m.isUsingOverage,
443
+ updatedAt: Date.now(),
444
+ });
445
+ }
446
+
400
447
  function handleBudgetExceeded(msg: ServerMessage) {
401
448
  setBudgetExceeded(true);
402
449
  setIsProcessing(false);
@@ -417,13 +464,14 @@ export function useSession(): UseSessionReturn {
417
464
  subscribe("session:history", handleHistory);
418
465
  subscribe("session:history_page_result", handleHistoryPage);
419
466
  subscribe("chat:prompt_suggestion", handlePromptSuggestion);
420
- subscribe("session:busy", handleSessionBusy);
421
467
  subscribe("chat:prompt_request", handlePromptRequest);
422
468
  subscribe("chat:prompt_resolved", handlePromptResolved);
423
469
  subscribe("chat:todo_update", handleTodoUpdate);
424
470
  subscribe("chat:plan_mode", handlePlanMode);
425
471
  subscribe("budget:status", handleBudgetStatus);
426
472
  subscribe("budget:exceeded", handleBudgetExceeded);
473
+ subscribe("chat:elicitation_request", handleElicitationRequest);
474
+ subscribe("chat:rate_limit", handleRateLimit);
427
475
 
428
476
  return function () {
429
477
  subscriptionsActive--;
@@ -441,13 +489,14 @@ export function useSession(): UseSessionReturn {
441
489
  unsubscribe("session:history", handleHistory);
442
490
  unsubscribe("session:history_page_result", handleHistoryPage);
443
491
  unsubscribe("chat:prompt_suggestion", handlePromptSuggestion);
444
- unsubscribe("session:busy", handleSessionBusy);
445
492
  unsubscribe("chat:prompt_request", handlePromptRequest);
446
493
  unsubscribe("chat:prompt_resolved", handlePromptResolved);
447
494
  unsubscribe("chat:todo_update", handleTodoUpdate);
448
495
  unsubscribe("chat:plan_mode", handlePlanMode);
449
496
  unsubscribe("budget:status", handleBudgetStatus);
450
497
  unsubscribe("budget:exceeded", handleBudgetExceeded);
498
+ unsubscribe("chat:elicitation_request", handleElicitationRequest);
499
+ unsubscribe("chat:rate_limit", handleRateLimit);
451
500
  };
452
501
  }, [subscribe, unsubscribe]);
453
502
 
@@ -481,8 +530,6 @@ export function useSession(): UseSessionReturn {
481
530
  promptSuggestion: state.promptSuggestion,
482
531
  failedInput: state.failedInput,
483
532
  messageQueue: state.messageQueue,
484
- isBusy: state.isBusy,
485
- busyOwner: state.busyOwner,
486
533
  isPlanMode: state.isPlanMode,
487
534
  pendingPrefill: state.pendingPrefill,
488
535
  budgetStatus: state.budgetStatus,
@@ -23,7 +23,7 @@ export function useWebSocket(): WebSocketContextValue {
23
23
 
24
24
  export function getWebSocketUrl(): string {
25
25
  if (import.meta.env.DEV) {
26
- return "ws://" + window.location.hostname + ":7654/ws";
26
+ return "ws://" + window.location.hostname + ":17654/ws";
27
27
  }
28
28
  var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
29
29
  return protocol + "//" + window.location.host + "/ws";
@@ -28,6 +28,17 @@ export interface BudgetStatus {
28
28
  enforcement: "warning" | "soft-block" | "hard-block";
29
29
  }
30
30
 
31
+ export interface RateLimitEntry {
32
+ status: "allowed" | "allowed_warning" | "rejected";
33
+ utilization?: number;
34
+ resetsAt?: number;
35
+ rateLimitType: string;
36
+ overageStatus?: string;
37
+ overageResetsAt?: number;
38
+ isUsingOverage?: boolean;
39
+ updatedAt: number;
40
+ }
41
+
31
42
  export interface SessionState {
32
43
  messages: HistoryMessage[];
33
44
  isProcessing: boolean;
@@ -48,12 +59,11 @@ export interface SessionState {
48
59
  promptSuggestion: string | null;
49
60
  failedInput: string | null;
50
61
  messageQueue: string[];
51
- isBusy: boolean;
52
- busyOwner: "cli" | "lattice" | null;
53
62
  isPlanMode: boolean;
54
63
  pendingPrefill: string | null;
55
64
  budgetStatus: BudgetStatus | null;
56
65
  budgetExceeded: boolean;
66
+ rateLimits: Record<string, RateLimitEntry>;
57
67
  }
58
68
 
59
69
  var sessionStore = new Store<SessionState>({
@@ -76,12 +86,11 @@ var sessionStore = new Store<SessionState>({
76
86
  promptSuggestion: null,
77
87
  failedInput: null,
78
88
  messageQueue: [],
79
- isBusy: false,
80
- busyOwner: null,
81
89
  isPlanMode: false,
82
90
  pendingPrefill: null,
83
91
  budgetStatus: null,
84
92
  budgetExceeded: false,
93
+ rateLimits: {},
85
94
  });
86
95
 
87
96
  var streamGeneration = 0;
@@ -268,8 +277,6 @@ export function setActiveSession(projectSlug: string | null, sessionId: string |
268
277
  promptSuggestion: null,
269
278
  failedInput: null,
270
279
  messageQueue: [],
271
- isBusy: false,
272
- busyOwner: null,
273
280
  isPlanMode: false,
274
281
  pendingPrefill: state.pendingPrefill,
275
282
  };
@@ -333,8 +340,6 @@ export function clearSession(): void {
333
340
  promptSuggestion: null,
334
341
  failedInput: null,
335
342
  messageQueue: [],
336
- isBusy: false,
337
- busyOwner: null,
338
343
  isPlanMode: false,
339
344
  pendingPrefill: null,
340
345
  budgetStatus: null,
@@ -367,12 +372,6 @@ export function setFailedInput(text: string | null): void {
367
372
  });
368
373
  }
369
374
 
370
- export function setSessionBusy(busy: boolean, owner?: "cli" | "lattice" | null): void {
371
- sessionStore.setState(function (state) {
372
- return { ...state, isBusy: busy, busyOwner: busy ? (owner ?? null) : null };
373
- });
374
- }
375
-
376
375
  export function setIsPlanMode(active: boolean): void {
377
376
  sessionStore.setState(function (state) {
378
377
  return { ...state, isPlanMode: active };
@@ -385,6 +384,25 @@ export function setBudgetStatus(status: BudgetStatus | null): void {
385
384
  });
386
385
  }
387
386
 
387
+ export function updateRateLimit(entry: RateLimitEntry): void {
388
+ sessionStore.setState(function (state) {
389
+ var updated = { ...state.rateLimits };
390
+ updated[entry.rateLimitType] = entry;
391
+ try { localStorage.setItem("lattice:rateLimits", JSON.stringify(updated)); } catch {}
392
+ return { ...state, rateLimits: updated };
393
+ });
394
+ }
395
+
396
+ export function loadCachedRateLimits(): void {
397
+ try {
398
+ var raw = localStorage.getItem("lattice:rateLimits");
399
+ if (raw) {
400
+ var parsed = JSON.parse(raw) as Record<string, RateLimitEntry>;
401
+ sessionStore.setState(function (state) { return { ...state, rateLimits: parsed }; });
402
+ }
403
+ } catch {}
404
+ }
405
+
388
406
  export function setBudgetExceeded(exceeded: boolean): void {
389
407
  sessionStore.setState(function (state) {
390
408
  return { ...state, budgetExceeded: exceeded };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.46.7",
3
+ "version": "1.47.0",
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>",
@@ -18,8 +18,9 @@ import { detectIdeProjectName } from "./handlers/settings";
18
18
  import "./handlers/session";
19
19
  import "./handlers/chat";
20
20
  import "./handlers/attachment";
21
- import { loadInterruptedSessions, unwatchSessionLock, cleanupClientPermissions, getActiveSessionCountForProject } from "./project/sdk-bridge";
22
- import { clearActiveSession, getActiveSession } from "./handlers/chat";
21
+ import { loadInterruptedSessions, cleanupClientPermissions, cleanupClientElicitations, getActiveStreamCountForProject } from "./project/sdk-bridge";
22
+ import { runWarmup, isWarmupComplete, getWarmupModels, getWarmupAccountInfo } from "./project/warmup";
23
+ import { clearActiveSession } from "./handlers/chat";
23
24
  import { clearActiveProject } from "./handlers/fs";
24
25
  import { clearClientRemoteNode } from "./ws/router";
25
26
  import "./handlers/fs";
@@ -324,13 +325,27 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
324
325
  var connectConfig = loadConfig();
325
326
  var connectIdentity = loadOrCreateIdentity();
326
327
  var localProjects = connectConfig.projects.map(function (p: typeof connectConfig.projects[number]) {
327
- return { slug: p.slug, path: p.path, title: p.title, nodeId: connectIdentity.id, nodeName: connectConfig.name, isRemote: false, ideProjectName: detectIdeProjectName(p.path), activeSessions: getActiveSessionCountForProject(p.path) };
328
+ return { slug: p.slug, path: p.path, title: p.title, nodeId: connectIdentity.id, nodeName: connectConfig.name, isRemote: false, ideProjectName: detectIdeProjectName(p.path), activeSessions: getActiveStreamCountForProject(p.slug) };
328
329
  });
329
330
  var connectRemoteProjects = getAllRemoteProjects(connectIdentity.id);
330
331
  sendTo(ws.data.id, {
331
332
  type: "projects:list",
332
333
  projects: localProjects.concat(connectRemoteProjects as unknown as typeof localProjects),
333
334
  });
335
+ if (isWarmupComplete()) {
336
+ sendTo(ws.data.id, { type: "warmup:models", models: getWarmupModels() } as any);
337
+ var accountInfo = getWarmupAccountInfo();
338
+ if (accountInfo) {
339
+ sendTo(ws.data.id, {
340
+ type: "warmup:account",
341
+ email: accountInfo.email,
342
+ organization: accountInfo.organization,
343
+ subscriptionType: accountInfo.subscriptionType,
344
+ apiKeySource: accountInfo.apiKeySource,
345
+ apiProvider: accountInfo.apiProvider,
346
+ } as any);
347
+ }
348
+ }
334
349
  },
335
350
  message(ws: ServerWebSocket<WsData>, message: string | Buffer) {
336
351
  var now = Date.now();
@@ -354,10 +369,6 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
354
369
  }
355
370
  },
356
371
  close(ws: ServerWebSocket<WsData>) {
357
- var activeSession = getActiveSession(ws.data.id);
358
- if (activeSession) {
359
- unwatchSessionLock(activeSession.sessionId);
360
- }
361
372
  clearActiveSession(ws.data.id);
362
373
  clearActiveProject(ws.data.id);
363
374
  clearClientRemoteNode(ws.data.id);
@@ -365,6 +376,7 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
365
376
  cleanupClientTerminals(ws.data.id);
366
377
  cleanupClientAttachments(ws.data.id);
367
378
  cleanupClientPermissions(ws.data.id);
379
+ cleanupClientElicitations(ws.data.id);
368
380
  clientRateLimits.delete(ws.data.id);
369
381
  log.ws("Client disconnected: %s", ws.data.id);
370
382
  },
@@ -396,6 +408,11 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
396
408
 
397
409
  loadInterruptedSessions();
398
410
 
411
+ var firstProject = config.projects[0];
412
+ if (firstProject) {
413
+ void runWarmup(firstProject.path);
414
+ }
415
+
399
416
  onPeerConnected(function (nodeId: string) {
400
417
  broadcast({ type: "mesh:node_online", nodeId: nodeId });
401
418
  });
@@ -420,7 +437,7 @@ export async function startDaemon(portOverride?: number | null): Promise<void> {
420
437
  var currentIdentity = loadOrCreateIdentity();
421
438
  broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
422
439
  var localProjects = currentConfig.projects.map(function (p: typeof currentConfig.projects[number]) {
423
- return { slug: p.slug, path: p.path, title: p.title, nodeId: currentIdentity.id, nodeName: currentConfig.name, isRemote: false, ideProjectName: detectIdeProjectName(p.path), activeSessions: getActiveSessionCountForProject(p.path) };
440
+ return { slug: p.slug, path: p.path, title: p.title, nodeId: currentIdentity.id, nodeName: currentConfig.name, isRemote: false, ideProjectName: detectIdeProjectName(p.path), activeSessions: getActiveStreamCountForProject(p.slug) };
424
441
  });
425
442
  var remoteProjects = getAllRemoteProjects(currentIdentity.id);
426
443
  broadcast({
@@ -3,7 +3,7 @@ import { registerHandler } from "../ws/router";
3
3
  import { sendTo } from "../ws/broadcast";
4
4
  import { getProjectBySlug } from "../project/registry";
5
5
  import { loadConfig } from "../config";
6
- import { startChatStream, getPendingPermission, deletePendingPermission, addAutoApprovedTool, setSessionPermissionOverride, getActiveStream, buildPermissionRule } from "../project/sdk-bridge";
6
+ import { startChatStream, getPendingPermission, deletePendingPermission, addAutoApprovedTool, setSessionPermissionOverride, getActiveStream, buildPermissionRule, getPendingElicitation, resolveElicitation } from "../project/sdk-bridge";
7
7
  import { getAttachments } from "./attachment";
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { join } from "node:path";
@@ -281,6 +281,17 @@ registerHandler("chat", function (clientId: string, message: ClientMessage) {
281
281
  return;
282
282
  }
283
283
 
284
+ if (message.type === "chat:elicitation_response") {
285
+ var elicitMsg = message as { type: string; requestId: string; action: "accept" | "decline"; content?: Record<string, unknown> };
286
+ var pendingElicit = getPendingElicitation(elicitMsg.requestId);
287
+ if (!pendingElicit) return;
288
+ resolveElicitation(elicitMsg.requestId, {
289
+ action: elicitMsg.action,
290
+ content: elicitMsg.content || {},
291
+ });
292
+ return;
293
+ }
294
+
284
295
  if (message.type === "chat:set_permission_mode") {
285
296
  var modeMsg = message as ChatSetPermissionModeMessage;
286
297
  var activeSession = activeSessionByClient.get(clientId);
@@ -27,7 +27,7 @@ import {
27
27
  import { getContextBreakdown } from "../project/context-breakdown";
28
28
  import { setActiveSession, getActiveSession } from "./chat";
29
29
  import { setActiveProject } from "./fs";
30
- import { wasSessionInterrupted, clearInterruptedFlag, isSessionBusy, watchSessionLock, stopExternalSession, getBusyOwner } from "../project/sdk-bridge";
30
+ import { wasSessionInterrupted, clearInterruptedFlag } from "../project/sdk-bridge";
31
31
  import { log } from "../logger";
32
32
 
33
33
  registerHandler("session", function (clientId: string, message: ClientMessage) {
@@ -105,7 +105,6 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
105
105
  var activateMsg = message as SessionActivateMessage;
106
106
  setActiveSession(clientId, activateMsg.projectSlug, activateMsg.sessionId);
107
107
  setActiveProject(clientId, activateMsg.projectSlug);
108
- watchSessionLock(activateMsg.sessionId);
109
108
  var activateT0 = Date.now();
110
109
  void loadSessionHistory(activateMsg.projectSlug, activateMsg.sessionId).then(function (historyResult) {
111
110
  log.session("session:activate history: %dms", Date.now() - activateT0);
@@ -114,8 +113,6 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
114
113
  if (interrupted) {
115
114
  clearInterruptedFlag(activateMsg.sessionId);
116
115
  }
117
- var busy = isSessionBusy(activateMsg.sessionId);
118
- var busyOwner = busy ? getBusyOwner(activateMsg.sessionId) : undefined;
119
116
  sendTo(clientId, {
120
117
  type: "session:history",
121
118
  projectSlug: activateMsg.projectSlug,
@@ -123,8 +120,6 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
123
120
  messages: historyResult.messages,
124
121
  title: title,
125
122
  interrupted: interrupted || undefined,
126
- busy: busy || undefined,
127
- busyOwner: busyOwner,
128
123
  totalMessages: historyResult.totalMessages,
129
124
  hasMore: historyResult.hasMore,
130
125
  });
@@ -222,13 +217,4 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
222
217
  });
223
218
  }
224
219
 
225
- if (message.type === "session:stop_external") {
226
- var stopMsg = message as { type: string; sessionId: string };
227
- var stopped = stopExternalSession(stopMsg.sessionId);
228
- if (stopped) {
229
- log.session("Sent SIGINT to external CLI process for session %s", stopMsg.sessionId);
230
- } else {
231
- sendTo(clientId, { type: "chat:error", message: "No external process found for this session." });
232
- }
233
- }
234
220
  });
@@ -122,6 +122,9 @@ registerHandler("settings", function (clientId: string, message: ClientMessage)
122
122
  },
123
123
  projects: refreshed.projects,
124
124
  };
125
+ if ("costBudget" in incoming && incoming.costBudget == null) {
126
+ delete (updated as Record<string, unknown>).costBudget;
127
+ }
125
128
  saveConfig(updated);
126
129
  var updatedWithClaudeMd = { ...updated, claudeMd: loadGlobalClaudeMd() };
127
130
  sendTo(clientId, {
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env bun
2
+ delete process.env.CLAUDECODE;
3
+ delete process.env.CLAUDE_CODE_ENTRYPOINT;
4
+
2
5
  import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
3
6
  import { join } from "node:path";
4
7
  import { DAEMON_PID_FILE } from "@lattice/shared";