@cryptiklemur/lattice 0.0.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.
Files changed (162) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/workflows/release.yml +44 -0
  3. package/.impeccable.md +66 -0
  4. package/.releaserc.json +32 -0
  5. package/.serena/project.yml +138 -0
  6. package/CLAUDE.md +35 -0
  7. package/CONTRIBUTING.md +93 -0
  8. package/LICENSE +21 -0
  9. package/README.md +83 -0
  10. package/bun.lock +1459 -0
  11. package/bunfig.toml +2 -0
  12. package/client/index.html +32 -0
  13. package/client/package.json +37 -0
  14. package/client/public/icons/icon-192.svg +11 -0
  15. package/client/public/icons/icon-512.svg +11 -0
  16. package/client/public/manifest.json +24 -0
  17. package/client/public/sw.js +61 -0
  18. package/client/src/App.tsx +28 -0
  19. package/client/src/components/auth/PassphrasePrompt.tsx +70 -0
  20. package/client/src/components/chat/ChatInput.tsx +241 -0
  21. package/client/src/components/chat/ChatView.tsx +727 -0
  22. package/client/src/components/chat/Message.tsx +362 -0
  23. package/client/src/components/chat/ModelSelector.tsx +87 -0
  24. package/client/src/components/chat/PermissionModeSelector.tsx +41 -0
  25. package/client/src/components/chat/StatusBar.tsx +50 -0
  26. package/client/src/components/chat/ToolGroup.tsx +129 -0
  27. package/client/src/components/chat/ToolResultRenderer.tsx +343 -0
  28. package/client/src/components/chat/toolSummary.ts +41 -0
  29. package/client/src/components/dashboard/DashboardView.tsx +219 -0
  30. package/client/src/components/dashboard/ProjectDashboardView.tsx +168 -0
  31. package/client/src/components/mesh/NodeBadge.tsx +24 -0
  32. package/client/src/components/mesh/PairingDialog.tsx +281 -0
  33. package/client/src/components/panels/FileBrowser.tsx +241 -0
  34. package/client/src/components/panels/StickyNotes.tsx +187 -0
  35. package/client/src/components/panels/Terminal.tsx +128 -0
  36. package/client/src/components/project-settings/ProjectClaude.tsx +304 -0
  37. package/client/src/components/project-settings/ProjectEnvironment.tsx +235 -0
  38. package/client/src/components/project-settings/ProjectGeneral.tsx +76 -0
  39. package/client/src/components/project-settings/ProjectMcp.tsx +232 -0
  40. package/client/src/components/project-settings/ProjectPermissions.tsx +209 -0
  41. package/client/src/components/project-settings/ProjectRules.tsx +277 -0
  42. package/client/src/components/project-settings/ProjectSettingsView.tsx +99 -0
  43. package/client/src/components/project-settings/ProjectSkills.tsx +91 -0
  44. package/client/src/components/settings/Appearance.tsx +151 -0
  45. package/client/src/components/settings/ClaudeSettings.tsx +151 -0
  46. package/client/src/components/settings/Environment.tsx +185 -0
  47. package/client/src/components/settings/GlobalMcp.tsx +207 -0
  48. package/client/src/components/settings/GlobalSkills.tsx +125 -0
  49. package/client/src/components/settings/MeshStatus.tsx +145 -0
  50. package/client/src/components/settings/SettingsView.tsx +57 -0
  51. package/client/src/components/settings/SkillMarketplace.tsx +175 -0
  52. package/client/src/components/settings/mcp-shared.tsx +194 -0
  53. package/client/src/components/settings/skill-shared.tsx +177 -0
  54. package/client/src/components/setup/SetupWizard.tsx +750 -0
  55. package/client/src/components/sidebar/NodeSettingsModal.tsx +180 -0
  56. package/client/src/components/sidebar/ProjectDropdown.tsx +43 -0
  57. package/client/src/components/sidebar/ProjectRail.tsx +291 -0
  58. package/client/src/components/sidebar/SearchFilter.tsx +52 -0
  59. package/client/src/components/sidebar/SessionList.tsx +384 -0
  60. package/client/src/components/sidebar/SettingsSidebar.tsx +128 -0
  61. package/client/src/components/sidebar/Sidebar.tsx +209 -0
  62. package/client/src/components/sidebar/UserIsland.tsx +59 -0
  63. package/client/src/components/sidebar/UserMenu.tsx +101 -0
  64. package/client/src/components/ui/CommandPalette.tsx +321 -0
  65. package/client/src/components/ui/ErrorBoundary.tsx +56 -0
  66. package/client/src/components/ui/IconPicker.tsx +209 -0
  67. package/client/src/components/ui/LatticeLogomark.tsx +19 -0
  68. package/client/src/components/ui/PopupMenu.tsx +98 -0
  69. package/client/src/components/ui/SaveFooter.tsx +38 -0
  70. package/client/src/components/ui/Toast.tsx +112 -0
  71. package/client/src/hooks/useMesh.ts +89 -0
  72. package/client/src/hooks/useProjectSettings.ts +56 -0
  73. package/client/src/hooks/useProjects.ts +66 -0
  74. package/client/src/hooks/useSaveState.ts +59 -0
  75. package/client/src/hooks/useSession.ts +317 -0
  76. package/client/src/hooks/useSidebar.ts +74 -0
  77. package/client/src/hooks/useSkills.ts +30 -0
  78. package/client/src/hooks/useTheme.ts +114 -0
  79. package/client/src/hooks/useWebSocket.ts +26 -0
  80. package/client/src/main.tsx +10 -0
  81. package/client/src/providers/WebSocketProvider.tsx +146 -0
  82. package/client/src/router.tsx +391 -0
  83. package/client/src/stores/mesh.ts +78 -0
  84. package/client/src/stores/session.ts +322 -0
  85. package/client/src/stores/sidebar.ts +336 -0
  86. package/client/src/stores/theme.ts +44 -0
  87. package/client/src/styles/global.css +167 -0
  88. package/client/src/styles/theme-vars.css +18 -0
  89. package/client/src/themes/index.ts +79 -0
  90. package/client/src/utils/findDuplicateKeys.ts +12 -0
  91. package/client/tsconfig.json +14 -0
  92. package/client/vite.config.ts +20 -0
  93. package/package.json +46 -0
  94. package/server/package.json +22 -0
  95. package/server/src/auth/passphrase.ts +48 -0
  96. package/server/src/config.ts +55 -0
  97. package/server/src/daemon.ts +338 -0
  98. package/server/src/features/ralph-loop.ts +173 -0
  99. package/server/src/features/scheduler.ts +281 -0
  100. package/server/src/features/sticky-notes.ts +102 -0
  101. package/server/src/handlers/chat.ts +194 -0
  102. package/server/src/handlers/fs.ts +84 -0
  103. package/server/src/handlers/loop.ts +37 -0
  104. package/server/src/handlers/mesh.ts +125 -0
  105. package/server/src/handlers/notes.ts +45 -0
  106. package/server/src/handlers/project-settings.ts +174 -0
  107. package/server/src/handlers/scheduler.ts +47 -0
  108. package/server/src/handlers/session.ts +159 -0
  109. package/server/src/handlers/settings.ts +109 -0
  110. package/server/src/handlers/skills.ts +380 -0
  111. package/server/src/handlers/terminal.ts +70 -0
  112. package/server/src/identity.ts +26 -0
  113. package/server/src/index.ts +190 -0
  114. package/server/src/mesh/connector.ts +209 -0
  115. package/server/src/mesh/discovery.ts +123 -0
  116. package/server/src/mesh/pairing.ts +94 -0
  117. package/server/src/mesh/peers.ts +52 -0
  118. package/server/src/mesh/proxy.ts +103 -0
  119. package/server/src/mesh/session-sync.ts +107 -0
  120. package/server/src/project/context-breakdown.ts +289 -0
  121. package/server/src/project/file-browser.ts +106 -0
  122. package/server/src/project/project-files.ts +267 -0
  123. package/server/src/project/registry.ts +57 -0
  124. package/server/src/project/sdk-bridge.ts +566 -0
  125. package/server/src/project/session.ts +432 -0
  126. package/server/src/project/terminal.ts +69 -0
  127. package/server/src/tls.ts +51 -0
  128. package/server/src/ws/broadcast.ts +31 -0
  129. package/server/src/ws/router.ts +104 -0
  130. package/server/src/ws/server.ts +2 -0
  131. package/server/tsconfig.json +16 -0
  132. package/shared/package.json +11 -0
  133. package/shared/src/constants.ts +7 -0
  134. package/shared/src/index.ts +4 -0
  135. package/shared/src/messages.ts +638 -0
  136. package/shared/src/models.ts +136 -0
  137. package/shared/src/project-settings.ts +45 -0
  138. package/shared/tsconfig.json +11 -0
  139. package/themes/amoled.json +20 -0
  140. package/themes/ayu-light.json +9 -0
  141. package/themes/catppuccin-latte.json +9 -0
  142. package/themes/catppuccin-mocha.json +9 -0
  143. package/themes/clay-light.json +10 -0
  144. package/themes/clay.json +10 -0
  145. package/themes/dracula.json +9 -0
  146. package/themes/everforest-light.json +9 -0
  147. package/themes/everforest.json +9 -0
  148. package/themes/github-light.json +9 -0
  149. package/themes/gruvbox-dark.json +9 -0
  150. package/themes/gruvbox-light.json +9 -0
  151. package/themes/monokai.json +9 -0
  152. package/themes/nord-light.json +9 -0
  153. package/themes/nord.json +9 -0
  154. package/themes/one-dark.json +9 -0
  155. package/themes/one-light.json +9 -0
  156. package/themes/rose-pine-dawn.json +9 -0
  157. package/themes/rose-pine.json +9 -0
  158. package/themes/solarized-dark.json +9 -0
  159. package/themes/solarized-light.json +9 -0
  160. package/themes/tokyo-night-light.json +9 -0
  161. package/themes/tokyo-night.json +9 -0
  162. package/tsconfig.json +26 -0
@@ -0,0 +1,57 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+ import { loadConfig, saveConfig } from "../config";
4
+ import type { ProjectSummary } from "@lattice/shared";
5
+
6
+ export function listProjects(nodeId: string): ProjectSummary[] {
7
+ var config = loadConfig();
8
+ return config.projects.map(function(p) {
9
+ return {
10
+ slug: p.slug,
11
+ path: p.path,
12
+ title: p.title,
13
+ nodeId: nodeId,
14
+ };
15
+ });
16
+ }
17
+
18
+ export function addProject(path: string, title?: string): ProjectSummary | null {
19
+ if (!existsSync(path)) return null;
20
+
21
+ var config = loadConfig();
22
+
23
+ if (config.projects.some(function(p) { return p.path === path; })) return null;
24
+
25
+ var slug = generateSlug(basename(path), config.projects.map(function(p) { return p.slug; }));
26
+ var project = { path: path, slug: slug, title: title || basename(path), env: {} };
27
+ config.projects.push(project);
28
+ saveConfig(config);
29
+
30
+ return { slug: project.slug, path: project.path, title: project.title, nodeId: "" };
31
+ }
32
+
33
+ export function removeProject(slug: string): boolean {
34
+ var config = loadConfig();
35
+ var idx = config.projects.findIndex(function(p) { return p.slug === slug; });
36
+ if (idx === -1) return false;
37
+ config.projects.splice(idx, 1);
38
+ saveConfig(config);
39
+ return true;
40
+ }
41
+
42
+ export function getProjectBySlug(slug: string): { path: string; slug: string; title: string; env: Record<string, string> } | undefined {
43
+ var config = loadConfig();
44
+ return config.projects.find(function(p) { return p.slug === slug; });
45
+ }
46
+
47
+ export function generateSlug(name: string, existing: string[]): string {
48
+ var slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
49
+ if (!slug) slug = "project";
50
+ var candidate = slug;
51
+ var counter = 1;
52
+ while (existing.includes(candidate)) {
53
+ candidate = slug + "-" + counter;
54
+ counter++;
55
+ }
56
+ return candidate;
57
+ }
@@ -0,0 +1,566 @@
1
+ import type { Query } from "@anthropic-ai/claude-agent-sdk";
2
+ import { query } from "@anthropic-ai/claude-agent-sdk";
3
+ import type { SDKMessage, SDKPartialAssistantMessage, SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";
4
+ import type { CanUseTool, PermissionMode, PermissionResult, PermissionUpdate } from "@anthropic-ai/claude-agent-sdk";
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { homedir } from "node:os";
8
+ import { sendTo } from "../ws/broadcast";
9
+ import { syncSessionToPeers } from "../mesh/session-sync";
10
+ import { resolveSkillContent } from "../handlers/skills";
11
+ import { guessContextWindow } from "./session";
12
+ import { getLatticeHome } from "../config";
13
+
14
+ interface PendingPermission {
15
+ resolve: (result: PermissionResult) => void;
16
+ toolName: string;
17
+ toolUseID: string;
18
+ input: Record<string, unknown>;
19
+ suggestions: PermissionUpdate[] | undefined;
20
+ clientId: string;
21
+ sessionId: string;
22
+ }
23
+
24
+ var pendingPermissions = new Map<string, PendingPermission>();
25
+ var autoApprovedTools = new Map<string, Set<string>>();
26
+ var sessionPermissionOverrides = new Map<string, PermissionMode>();
27
+
28
+ export interface ChatStreamOptions {
29
+ projectSlug: string;
30
+ sessionId: string;
31
+ text: string;
32
+ clientId: string;
33
+ cwd: string;
34
+ env?: Record<string, string>;
35
+ model?: string;
36
+ effort?: "low" | "medium" | "high" | "max";
37
+ isNewSession?: boolean;
38
+ }
39
+
40
+ export interface ModelEntry {
41
+ value: string;
42
+ displayName: string;
43
+ }
44
+
45
+ var KNOWN_MODELS: ModelEntry[] = [
46
+ { value: "default", displayName: "Default" },
47
+ { value: "opus", displayName: "Opus" },
48
+ { value: "sonnet", displayName: "Sonnet" },
49
+ { value: "haiku", displayName: "Haiku" },
50
+ ];
51
+
52
+ export function getAvailableModels(): ModelEntry[] {
53
+ return KNOWN_MODELS.slice();
54
+ }
55
+
56
+ var activeStreams = new Map<string, Query>();
57
+ var streamMetadata = new Map<string, { projectSlug: string; clientId: string; startedAt: number }>();
58
+ var interruptedSessions = new Set<string>();
59
+
60
+ function getStreamStatePath(): string {
61
+ return join(getLatticeHome(), "active-streams.json");
62
+ }
63
+
64
+ function persistStreamState(): void {
65
+ var entries: Record<string, { projectSlug: string; clientId: string; startedAt: number }> = {};
66
+ streamMetadata.forEach(function (meta, sessionId) {
67
+ entries[sessionId] = meta;
68
+ });
69
+ var dir = getLatticeHome();
70
+ if (!existsSync(dir)) {
71
+ mkdirSync(dir, { recursive: true });
72
+ }
73
+ writeFileSync(getStreamStatePath(), JSON.stringify(entries), "utf-8");
74
+ }
75
+
76
+ export function loadInterruptedSessions(): void {
77
+ var path = getStreamStatePath();
78
+ if (!existsSync(path)) return;
79
+ try {
80
+ var data = JSON.parse(readFileSync(path, "utf-8"));
81
+ for (var sessionId of Object.keys(data)) {
82
+ interruptedSessions.add(sessionId);
83
+ }
84
+ } catch {}
85
+ writeFileSync(path, "{}", "utf-8");
86
+ }
87
+
88
+ export function wasSessionInterrupted(sessionId: string): boolean {
89
+ return interruptedSessions.has(sessionId);
90
+ }
91
+
92
+ export function clearInterruptedFlag(sessionId: string): void {
93
+ interruptedSessions.delete(sessionId);
94
+ }
95
+
96
+ export function getPendingPermission(requestId: string): PendingPermission | undefined {
97
+ return pendingPermissions.get(requestId);
98
+ }
99
+
100
+ export function deletePendingPermission(requestId: string): void {
101
+ pendingPermissions.delete(requestId);
102
+ }
103
+
104
+ export function addAutoApprovedTool(sessionId: string, toolName: string): void {
105
+ var tools = autoApprovedTools.get(sessionId);
106
+ if (!tools) {
107
+ tools = new Set<string>();
108
+ autoApprovedTools.set(sessionId, tools);
109
+ }
110
+ tools.add(toolName);
111
+ }
112
+
113
+ export function setSessionPermissionOverride(sessionId: string, mode: PermissionMode): void {
114
+ sessionPermissionOverrides.set(sessionId, mode);
115
+ }
116
+
117
+ export function getActiveStream(sessionId: string): Query | undefined {
118
+ return activeStreams.get(sessionId);
119
+ }
120
+
121
+ export function matchesAllowRules(rules: string[], toolName: string, currentRule: string): boolean {
122
+ for (var i = 0; i < rules.length; i++) {
123
+ var rule = rules[i];
124
+ if (rule === toolName || rule === currentRule) return true;
125
+ var ruleMatch = rule.match(/^(\w+)\((.+)\)$/);
126
+ if (!ruleMatch) continue;
127
+ var ruleToolName = ruleMatch[1];
128
+ var rulePattern = ruleMatch[2];
129
+ if (ruleToolName !== toolName) continue;
130
+ var currentMatch = currentRule.match(/^\w+\((.+)\)$/);
131
+ if (!currentMatch) continue;
132
+ var currentContent = currentMatch[1];
133
+ if (rulePattern === "*") return true;
134
+ if (rulePattern.endsWith(":*")) {
135
+ var rulePrefix = rulePattern.slice(0, -1);
136
+ if (currentContent.startsWith(rulePrefix) || currentContent === rulePattern.slice(0, -2)) return true;
137
+ }
138
+ if (rulePattern.endsWith("**")) {
139
+ var dirPrefix = rulePattern.slice(0, -2);
140
+ if (currentContent.startsWith(dirPrefix)) return true;
141
+ }
142
+ }
143
+ return false;
144
+ }
145
+
146
+ export function buildPermissionRule(toolName: string, input: Record<string, unknown>): string {
147
+ if (toolName === "Bash") {
148
+ var command = input.command || input.cmd || "";
149
+ if (typeof command === "string" && command) {
150
+ var firstWord = command.split(/\s+/)[0];
151
+ if (firstWord === "curl" || firstWord === "wget") {
152
+ var urlMatch = command.match(/https?:\/\/[^\s"']+/);
153
+ if (urlMatch) {
154
+ try {
155
+ var parsed = new URL(urlMatch[0]);
156
+ return toolName + "(" + firstWord + ":" + parsed.hostname + ")";
157
+ } catch {}
158
+ }
159
+ }
160
+ return toolName + "(" + firstWord + ":*)";
161
+ }
162
+ }
163
+
164
+ if (toolName === "Read" || toolName === "Edit" || toolName === "Write") {
165
+ var filePath = input.file_path || input.path || "";
166
+ if (typeof filePath === "string" && filePath) {
167
+ var dirParts = filePath.split("/");
168
+ dirParts.pop();
169
+ var dir = dirParts.join("/");
170
+ if (dir) {
171
+ var prefix = dir.startsWith("/") ? "" : "/";
172
+ return toolName + "(" + prefix + dir + "/**)";
173
+ }
174
+ }
175
+ }
176
+
177
+ if (toolName === "WebFetch") {
178
+ var url = input.url || "";
179
+ if (typeof url === "string" && url) {
180
+ try {
181
+ var parsed = new URL(url);
182
+ return toolName + "(domain:" + parsed.hostname + ")";
183
+ } catch {}
184
+ }
185
+ }
186
+
187
+ return toolName;
188
+ }
189
+
190
+ export function startChatStream(options: ChatStreamOptions): void {
191
+ var { projectSlug, sessionId, text, clientId, cwd, env, model, effort, isNewSession } = options;
192
+ var startTime = Date.now();
193
+
194
+ if (activeStreams.has(sessionId)) {
195
+ sendTo(clientId, { type: "chat:error", message: "Session already has an active stream." });
196
+ return;
197
+ }
198
+
199
+ var projectSettingsPath = join(cwd, ".claude", "settings.json");
200
+ var savedAdditionalDirs: string[] = [];
201
+ var latticeDefaults: Record<string, unknown> = {};
202
+ if (existsSync(projectSettingsPath)) {
203
+ try {
204
+ var projSettings = JSON.parse(readFileSync(projectSettingsPath, "utf-8"));
205
+ if (projSettings.permissions && Array.isArray(projSettings.permissions.additionalDirectories)) {
206
+ savedAdditionalDirs = projSettings.permissions.additionalDirectories;
207
+ }
208
+ if (projSettings.lattice && typeof projSettings.lattice === "object") {
209
+ latticeDefaults = projSettings.lattice as Record<string, unknown>;
210
+ }
211
+ } catch {}
212
+ }
213
+
214
+ var effectiveMode: PermissionMode = sessionPermissionOverrides.get(sessionId)
215
+ || (latticeDefaults.defaultPermissionMode as PermissionMode | undefined)
216
+ || "acceptEdits";
217
+ sessionPermissionOverrides.delete(sessionId);
218
+
219
+ var mcpServers: Record<string, unknown> = {};
220
+ var claudeJsonPath = join(homedir(), ".claude.json");
221
+ if (existsSync(claudeJsonPath)) {
222
+ try {
223
+ var claudeJson = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
224
+ if (claudeJson.mcpServers && typeof claudeJson.mcpServers === "object") {
225
+ mcpServers = claudeJson.mcpServers;
226
+ }
227
+ } catch {}
228
+ }
229
+
230
+ var projectMcpPath = join(cwd, ".mcp.json");
231
+ if (existsSync(projectMcpPath)) {
232
+ try {
233
+ var projectMcpJson = JSON.parse(readFileSync(projectMcpPath, "utf-8"));
234
+ if (projectMcpJson.mcpServers && typeof projectMcpJson.mcpServers === "object") {
235
+ mcpServers = { ...mcpServers, ...projectMcpJson.mcpServers };
236
+ }
237
+ } catch {}
238
+ }
239
+
240
+ var queryOptions: Parameters<typeof query>[0]["options"] = {
241
+ cwd,
242
+ permissionMode: effectiveMode,
243
+ additionalDirectories: savedAdditionalDirs.length > 0 ? savedAdditionalDirs : undefined,
244
+ mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers as Record<string, any> : undefined,
245
+ };
246
+
247
+ queryOptions.canUseTool = function (toolName, input, options) {
248
+ var approved = autoApprovedTools.get(sessionId);
249
+ if (approved && approved.has(toolName)) {
250
+ return Promise.resolve({ behavior: "allow", updatedInput: input, toolUseID: options.toolUseID } as PermissionResult);
251
+ }
252
+
253
+ if (toolName === "Read") {
254
+ var readPath = (input.file_path || input.path || "") as string;
255
+ if (readPath.startsWith("/tmp/") || readPath === "/tmp") {
256
+ return Promise.resolve({ behavior: "allow", updatedInput: input, toolUseID: options.toolUseID } as PermissionResult);
257
+ }
258
+ }
259
+
260
+ var allowRules: string[] = [];
261
+ if (existsSync(projectSettingsPath)) {
262
+ try {
263
+ var projSettingsForRules = JSON.parse(readFileSync(projectSettingsPath, "utf-8"));
264
+ if (projSettingsForRules.permissions && Array.isArray(projSettingsForRules.permissions.allow)) {
265
+ allowRules = projSettingsForRules.permissions.allow;
266
+ }
267
+ } catch {}
268
+ }
269
+
270
+ if (allowRules.length > 0) {
271
+ var currentRule = buildPermissionRule(toolName, input);
272
+ if (matchesAllowRules(allowRules, toolName, currentRule)) {
273
+ return Promise.resolve({ behavior: "allow", updatedInput: input, toolUseID: options.toolUseID } as PermissionResult);
274
+ }
275
+ }
276
+
277
+ var requestId = options.toolUseID;
278
+ var rule = buildPermissionRule(toolName, input);
279
+ var title = options.title || rule;
280
+
281
+ sendTo(clientId, {
282
+ type: "chat:permission_request",
283
+ requestId: requestId,
284
+ tool: toolName,
285
+ args: JSON.stringify(input),
286
+ title: title,
287
+ decisionReason: options.decisionReason,
288
+ permissionRule: rule,
289
+ });
290
+
291
+ return new Promise<PermissionResult>(function (resolve) {
292
+ pendingPermissions.set(requestId, {
293
+ resolve: resolve,
294
+ toolName: toolName,
295
+ toolUseID: options.toolUseID,
296
+ input: input,
297
+ suggestions: options.suggestions,
298
+ clientId: clientId,
299
+ sessionId: sessionId,
300
+ });
301
+
302
+ if (options.signal) {
303
+ options.signal.addEventListener("abort", function () {
304
+ if (pendingPermissions.has(requestId)) {
305
+ pendingPermissions.delete(requestId);
306
+ resolve({ behavior: "deny", message: "Stream aborted.", toolUseID: options.toolUseID });
307
+ sendTo(clientId, { type: "chat:permission_resolved", requestId: requestId, status: "denied" });
308
+ }
309
+ }, { once: true });
310
+ }
311
+ });
312
+ } as CanUseTool;
313
+
314
+ var shouldResume = false;
315
+ if (isNewSession) {
316
+ shouldResume = false;
317
+ } else {
318
+ var hash = cwd.replace(/\//g, "-");
319
+ var sessionFile = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
320
+ shouldResume = existsSync(sessionFile);
321
+ }
322
+
323
+ if (shouldResume) {
324
+ queryOptions.resume = sessionId;
325
+ } else {
326
+ queryOptions.sessionId = sessionId;
327
+ }
328
+
329
+ if (model && model !== "default") {
330
+ queryOptions.model = model;
331
+ }
332
+
333
+ if (!model || model === "default") {
334
+ if (latticeDefaults.defaultModel && typeof latticeDefaults.defaultModel === "string") {
335
+ queryOptions.model = latticeDefaults.defaultModel as string;
336
+ }
337
+ }
338
+
339
+ if (effort) {
340
+ queryOptions.effort = effort;
341
+ }
342
+
343
+ if (!effort) {
344
+ if (latticeDefaults.defaultEffort && typeof latticeDefaults.defaultEffort === "string") {
345
+ queryOptions.effort = latticeDefaults.defaultEffort as any;
346
+ }
347
+ }
348
+
349
+ if (latticeDefaults.thinking) {
350
+ (queryOptions as any).thinking = latticeDefaults.thinking;
351
+ }
352
+
353
+ if (env) {
354
+ queryOptions.env = env;
355
+ }
356
+
357
+ var prompt = text;
358
+ if (text.startsWith("/")) {
359
+ var parts = text.split(/\s+/);
360
+ var skillName = parts[0].slice(1);
361
+ var skillArgs = parts.slice(1).join(" ");
362
+ var skillContent = resolveSkillContent(skillName);
363
+ if (skillContent) {
364
+ prompt = "<skill-name>" + skillName + "</skill-name>\n" +
365
+ "<skill-content>\n" + skillContent + "\n</skill-content>\n" +
366
+ (skillArgs ? "<skill-args>" + skillArgs + "</skill-args>\n" : "") +
367
+ "Execute this skill. Follow its instructions exactly.";
368
+ }
369
+ }
370
+
371
+ sendTo(clientId, {
372
+ type: "chat:user_message",
373
+ text,
374
+ uuid: crypto.randomUUID(),
375
+ });
376
+
377
+ var activeToolBlocks: Record<number, { id: string; name: string; inputJson: string }> = {};
378
+ var doneSent = false;
379
+
380
+ var stream = query({ prompt: prompt, options: queryOptions });
381
+ activeStreams.set(sessionId, stream);
382
+ streamMetadata.set(sessionId, { projectSlug, clientId, startedAt: Date.now() });
383
+ persistStreamState();
384
+
385
+ void (async function () {
386
+ try {
387
+ for await (var msg of stream) {
388
+ processMessage(msg);
389
+ }
390
+ } catch (err: unknown) {
391
+ var errMsg = err instanceof Error ? err.message : String(err);
392
+ console.error("[lattice] SDK stream error: " + errMsg);
393
+ sendTo(clientId, { type: "chat:error", message: errMsg });
394
+ } finally {
395
+ activeStreams.delete(sessionId);
396
+ streamMetadata.delete(sessionId);
397
+ persistStreamState();
398
+
399
+ var toCleanup: string[] = [];
400
+ pendingPermissions.forEach(function (entry, reqId) {
401
+ if (entry.sessionId === sessionId) {
402
+ toCleanup.push(reqId);
403
+ entry.resolve({ behavior: "deny", message: "Session ended.", toolUseID: entry.toolUseID });
404
+ sendTo(entry.clientId, { type: "chat:permission_resolved", requestId: reqId, status: "denied" });
405
+ }
406
+ });
407
+ toCleanup.forEach(function (reqId) { pendingPermissions.delete(reqId); });
408
+
409
+ autoApprovedTools.delete(sessionId);
410
+ sessionPermissionOverrides.delete(sessionId);
411
+
412
+ if (!doneSent) {
413
+ doneSent = true;
414
+ sendTo(clientId, { type: "chat:done", cost: 0, duration: Date.now() - startTime });
415
+ }
416
+ }
417
+ })();
418
+
419
+ function processMessage(msg: SDKMessage): void {
420
+
421
+ if (msg.type === "system") {
422
+ var sysMsg = msg as { type: "system"; subtype?: string; mcp_servers?: { name: string; status: string }[]; tools?: string[] };
423
+ if (sysMsg.subtype === "init") {
424
+ console.log("[lattice] SDK init - MCP servers:", JSON.stringify(sysMsg.mcp_servers || []));
425
+ console.log("[lattice] SDK init - tools count:", (sysMsg.tools || []).length);
426
+ var mcpTools = (sysMsg.tools || []).filter(function (t) { return t.startsWith("mcp__"); });
427
+ if (mcpTools.length > 0) {
428
+ console.log("[lattice] SDK init - MCP tools:", mcpTools.join(", "));
429
+ }
430
+ }
431
+ return;
432
+ }
433
+
434
+ if (msg.type === "assistant") {
435
+ var assistantMsg = msg as { type: "assistant"; message: { content: unknown; model?: string; usage?: { input_tokens?: number; output_tokens?: number; cache_read_input_tokens?: number; cache_creation_input_tokens?: number } } };
436
+ var msgUsage = assistantMsg.message.usage;
437
+ if (msgUsage && msgUsage.input_tokens != null) {
438
+ sendTo(clientId, {
439
+ type: "chat:context_usage",
440
+ inputTokens: msgUsage.input_tokens || 0,
441
+ outputTokens: msgUsage.output_tokens || 0,
442
+ cacheReadTokens: msgUsage.cache_read_input_tokens || 0,
443
+ cacheCreationTokens: msgUsage.cache_creation_input_tokens || 0,
444
+ contextWindow: guessContextWindow(assistantMsg.message.model || ""),
445
+ });
446
+ }
447
+ var aContent = assistantMsg.message.content;
448
+ if (Array.isArray(aContent)) {
449
+ for (var ai = 0; ai < aContent.length; ai++) {
450
+ var aBlock = aContent[ai] as { type?: string; text?: string; id?: string; name?: string; input?: unknown };
451
+ if (aBlock.type === "text" && aBlock.text) {
452
+ sendTo(clientId, { type: "chat:delta", text: aBlock.text });
453
+ } else if (aBlock.type === "tool_use" && aBlock.id && aBlock.name) {
454
+ sendTo(clientId, {
455
+ type: "chat:tool_start",
456
+ toolId: aBlock.id,
457
+ name: aBlock.name,
458
+ args: JSON.stringify(aBlock.input ?? {}),
459
+ });
460
+ }
461
+ }
462
+ } else if (typeof aContent === "string" && aContent) {
463
+ sendTo(clientId, { type: "chat:delta", text: aContent });
464
+ }
465
+ return;
466
+ }
467
+
468
+ if (msg.type === "stream_event") {
469
+ var partial = msg as SDKPartialAssistantMessage;
470
+ var evt = partial.event;
471
+
472
+ if (evt.type === "content_block_start") {
473
+ var block = (evt as { content_block: { type: string; id?: string; name?: string }; index: number }).content_block;
474
+ var idx = (evt as { index: number }).index;
475
+ if (block.type === "tool_use" && block.id && block.name) {
476
+ activeToolBlocks[idx] = { id: block.id, name: block.name, inputJson: "" };
477
+ sendTo(clientId, {
478
+ type: "chat:tool_start",
479
+ toolId: block.id,
480
+ name: block.name,
481
+ args: "",
482
+ });
483
+ }
484
+ return;
485
+ }
486
+
487
+ if (evt.type === "content_block_delta") {
488
+ var deltaEvt = evt as { index: number; delta: { type: string; text?: string; partial_json?: string } };
489
+ var blockIdx = deltaEvt.index;
490
+
491
+ if (deltaEvt.delta.type === "text_delta" && typeof deltaEvt.delta.text === "string") {
492
+ sendTo(clientId, { type: "chat:delta", text: deltaEvt.delta.text });
493
+ } else if (deltaEvt.delta.type === "input_json_delta" && activeToolBlocks[blockIdx]) {
494
+ activeToolBlocks[blockIdx].inputJson += deltaEvt.delta.partial_json || "";
495
+ var updatedTool = activeToolBlocks[blockIdx];
496
+ sendTo(clientId, {
497
+ type: "chat:tool_start",
498
+ toolId: updatedTool.id,
499
+ name: updatedTool.name,
500
+ args: updatedTool.inputJson,
501
+ });
502
+ }
503
+ return;
504
+ }
505
+
506
+ if (evt.type === "content_block_stop") {
507
+ var stopIdx = (evt as { index: number }).index;
508
+ delete activeToolBlocks[stopIdx];
509
+ return;
510
+ }
511
+
512
+ return;
513
+ }
514
+
515
+ if (msg.type === "user") {
516
+ var userMsg = msg as { type: "user"; message: { content: unknown }; tool_use_result?: unknown };
517
+ var content = userMsg.message.content;
518
+ if (Array.isArray(content)) {
519
+ for (var i = 0; i < content.length; i++) {
520
+ var item = content[i] as { type?: string; tool_use_id?: string; content?: unknown };
521
+ if (item.type === "tool_result" && item.tool_use_id) {
522
+ var resultContent = typeof item.content === "string"
523
+ ? item.content
524
+ : JSON.stringify(item.content ?? "");
525
+ sendTo(clientId, {
526
+ type: "chat:tool_result",
527
+ toolId: item.tool_use_id,
528
+ content: resultContent,
529
+ });
530
+ }
531
+ }
532
+ }
533
+ return;
534
+ }
535
+
536
+ if (msg.type === "result") {
537
+ var resultMsg = msg as SDKResultMessage;
538
+ var dur = Date.now() - startTime;
539
+ var cost = resultMsg.total_cost_usd || 0;
540
+
541
+ if (resultMsg.usage && resultMsg.modelUsage) {
542
+ var contextWindow = 0;
543
+ var modelKeys = Object.keys(resultMsg.modelUsage);
544
+ for (var mk = 0; mk < modelKeys.length; mk++) {
545
+ var mu = resultMsg.modelUsage[modelKeys[mk]];
546
+ if (mu.contextWindow > contextWindow) {
547
+ contextWindow = mu.contextWindow;
548
+ }
549
+ }
550
+ sendTo(clientId, {
551
+ type: "chat:context_usage",
552
+ inputTokens: resultMsg.usage.input_tokens || 0,
553
+ outputTokens: resultMsg.usage.output_tokens || 0,
554
+ cacheReadTokens: resultMsg.usage.cache_read_input_tokens || 0,
555
+ cacheCreationTokens: resultMsg.usage.cache_creation_input_tokens || 0,
556
+ contextWindow: contextWindow,
557
+ });
558
+ }
559
+
560
+ doneSent = true;
561
+ sendTo(clientId, { type: "chat:done", cost: cost, duration: dur });
562
+ syncSessionToPeers(cwd, projectSlug, sessionId);
563
+ return;
564
+ }
565
+ }
566
+ }