@cryptiklemur/lattice 5.8.3 → 5.10.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 (93) hide show
  1. package/dist/client/assets/{angular-html-BpO2Tp5w.js → angular-html-BDIcxkJq.js} +1 -1
  2. package/dist/client/assets/{angular-ts-DgHuzSgH.js → angular-ts-Bt22ouNH.js} +1 -1
  3. package/dist/client/assets/{apl-DN-3g-kA.js → apl-p8qkxzEK.js} +1 -1
  4. package/dist/client/assets/{astro-DC7TkZXH.js → astro-CIaMc49M.js} +1 -1
  5. package/dist/client/assets/{blade-DJeBezn7.js → blade-BR56EAMD.js} +1 -1
  6. package/dist/client/assets/{c-CWPHtG_V.js → c-Dli0HzAh.js} +1 -1
  7. package/dist/client/assets/{cobol-TtN3pbhH.js → cobol-Cad15ECy.js} +1 -1
  8. package/dist/client/assets/{coffee-DR36C-Bj.js → coffee-DpyATEbF.js} +1 -1
  9. package/dist/client/assets/{cpp-C9XaUQ0i.js → cpp-KN8_NFsf.js} +1 -1
  10. package/dist/client/assets/{crystal-T-sTBJEW.js → crystal-CuyGv0kh.js} +1 -1
  11. package/dist/client/assets/{css-BX5yO7SA.js → css-Cm3q4bxn.js} +1 -1
  12. package/dist/client/assets/{dist-VJaucYqW.js → dist-BjxsMc4u.js} +2 -2
  13. package/dist/client/assets/{edge-B6UK8iH4.js → edge-B6S7CSbx.js} +1 -1
  14. package/dist/client/assets/{elixir-CgftVsub.js → elixir-CNUy9H8T.js} +1 -1
  15. package/dist/client/assets/{elm-Bm5ehGFJ.js → elm-CNfcWmb9.js} +1 -1
  16. package/dist/client/assets/{erb-BGdFwtqx.js → erb-DWebzDaI.js} +1 -1
  17. package/dist/client/assets/{git-rebase-Dgch70i-.js → git-rebase-B_Pt2ZBK.js} +1 -1
  18. package/dist/client/assets/{glimmer-js-CO2m7K3o.js → glimmer-js-CVwoOd72.js} +1 -1
  19. package/dist/client/assets/{glimmer-ts-Rh9OBnOT.js → glimmer-ts-CjtFSxjz.js} +1 -1
  20. package/dist/client/assets/{glsl-DKLX4cjW.js → glsl-CP4rggAA.js} +1 -1
  21. package/dist/client/assets/{graphql-CirzzkU1.js → graphql-Dbm6sAtp.js} +1 -1
  22. package/dist/client/assets/{hack-CQC9znUp.js → hack-Bj9y3SGf.js} +1 -1
  23. package/dist/client/assets/{haml-ze151Tzg.js → haml-DRGrdf3f.js} +1 -1
  24. package/dist/client/assets/{handlebars-CxjP8Lo0.js → handlebars-CFKjcBMg.js} +1 -1
  25. package/dist/client/assets/{html-CqXIaUHF.js → html-Vcd4eHHg.js} +1 -1
  26. package/dist/client/assets/{html-derivative-BzQtEeTI.js → html-derivative-BF0YbD4L.js} +1 -1
  27. package/dist/client/assets/{http-DuZ92gpQ.js → http-CGVTa2NT.js} +1 -1
  28. package/dist/client/assets/{hurl-CAbh6Y6a.js → hurl-B0GrsGqd.js} +1 -1
  29. package/dist/client/assets/{index-E8YNABWy.js → index-CX1tudsF.js} +132 -132
  30. package/dist/client/assets/index-DlfI20Gn.css +2 -0
  31. package/dist/client/assets/{java-DzcsTbJs.js → java-BJHQqHsm.js} +1 -1
  32. package/dist/client/assets/{javascript-DClRq2ts.js → javascript-CmuMsKrc.js} +1 -1
  33. package/dist/client/assets/{jinja-B5sT9_-9.js → jinja-JxCLeq1j.js} +1 -1
  34. package/dist/client/assets/{jison-CU3zhnCb.js → jison-BdgAUhei.js} +1 -1
  35. package/dist/client/assets/{json-CQp0L0ej.js → json-DtPissHL.js} +1 -1
  36. package/dist/client/assets/{jsx-Bf-5FvbF.js → jsx-DUAxxDkP.js} +1 -1
  37. package/dist/client/assets/{julia-CydtGy78.js → julia-DxDlbL6e.js} +1 -1
  38. package/dist/client/assets/{just-DdklfRff.js → just-CVmAAx2R.js} +1 -1
  39. package/dist/client/assets/{latex-Br5dIruj.js → latex-uwxggTWA.js} +1 -1
  40. package/dist/client/assets/{liquid-BzrfNGvH.js → liquid-xsETAJJy.js} +1 -1
  41. package/dist/client/assets/{lua-Cj4dlLGr.js → lua-B2Hh8PgD.js} +1 -1
  42. package/dist/client/assets/{marko-BSCcyzMZ.js → marko-yDeGxD87.js} +1 -1
  43. package/dist/client/assets/{mdc-BPOjCacH.js → mdc-QMp4ieYR.js} +1 -1
  44. package/dist/client/assets/{nginx-DbzWTwI6.js → nginx-7gmRmcqz.js} +1 -1
  45. package/dist/client/assets/{nim-CRdChtbV.js → nim-CA8SNY_7.js} +1 -1
  46. package/dist/client/assets/{perl-C3QVEeKS.js → perl-lx5nW4VC.js} +1 -1
  47. package/dist/client/assets/{php-C1EdFiJm.js → php-DgHiW953.js} +1 -1
  48. package/dist/client/assets/{pug-CO6P9E1X.js → pug-CbbB1vwb.js} +1 -1
  49. package/dist/client/assets/{qml-iW4zlehx.js → qml-COrzwCIh.js} +1 -1
  50. package/dist/client/assets/{r-C6XBkrUL.js → r-Dv7pZJDH.js} +1 -1
  51. package/dist/client/assets/{razor-CeQnxDgb.js → razor-D2m8EDP5.js} +1 -1
  52. package/dist/client/assets/{regexp-Dy8aAKap.js → regexp-BXLT-jPc.js} +1 -1
  53. package/dist/client/assets/{rst-OwTtwL0i.js → rst-_S6rrUYh.js} +1 -1
  54. package/dist/client/assets/{ruby-D7JWM2N5.js → ruby-C3XO7tYY.js} +1 -1
  55. package/dist/client/assets/{sas-DiK66SDU.js → sas-DP2k4iuN.js} +1 -1
  56. package/dist/client/assets/{scss-qrlTvxMb.js → scss-lhLFMXGn.js} +1 -1
  57. package/dist/client/assets/{shellscript-D7AOrbZb.js → shellscript-BYlBPHen.js} +1 -1
  58. package/dist/client/assets/{shellsession-DjQiM7TM.js → shellsession-CbVyQKWZ.js} +1 -1
  59. package/dist/client/assets/{soy-BL7E9JSD.js → soy-Be8a0lHq.js} +1 -1
  60. package/dist/client/assets/{sql-CteFkLc2.js → sql-2KxvU9YS.js} +1 -1
  61. package/dist/client/assets/{stata-B3MgNvuI.js → stata-BxlWftTS.js} +1 -1
  62. package/dist/client/assets/{surrealql-COKgmBsN.js → surrealql-CJ-q86nR.js} +1 -1
  63. package/dist/client/assets/{svelte-Gjt4fCGF.js → svelte-Q1ml0OiY.js} +1 -1
  64. package/dist/client/assets/{templ-Bj518YFy.js → templ-BbfPZhtu.js} +1 -1
  65. package/dist/client/assets/{tex-DRQn3t1e.js → tex-Dcth4Gi6.js} +1 -1
  66. package/dist/client/assets/{ts-tags-Ca2ut20u.js → ts-tags-BKhSOXI3.js} +1 -1
  67. package/dist/client/assets/{tsx-q8zyOWFk.js → tsx-CS6iQ0XH.js} +1 -1
  68. package/dist/client/assets/{twig-TjCAErzr.js → twig-BHp31ZxS.js} +1 -1
  69. package/dist/client/assets/{typescript-p7KFNEGW.js → typescript-16YJBTaO.js} +1 -1
  70. package/dist/client/assets/{vue-BPqqN4qD.js → vue-CMKwTi4r.js} +1 -1
  71. package/dist/client/assets/{vue-html-DWzvGKv5.js → vue-html-Dr8VUA2G.js} +1 -1
  72. package/dist/client/assets/{vue-vine-ooxqXIjP.js → vue-vine-DZUqDerl.js} +1 -1
  73. package/dist/client/assets/{xml-B8jNsjt9.js → xml-CBbBKKDC.js} +1 -1
  74. package/dist/client/assets/{xsl-B6pyn4Tp.js → xsl-DWEX6PKX.js} +1 -1
  75. package/dist/client/assets/{yaml-BN6nvuC4.js → yaml-DvKvvh3X.js} +1 -1
  76. package/dist/client/index.html +2 -2
  77. package/dist/client/sw.js +1 -1
  78. package/dist/server/daemon.js +42 -2
  79. package/dist/server/features/context-analyzer.js +239 -0
  80. package/dist/server/features/session-history.js +127 -0
  81. package/dist/server/features/specs.js +87 -1
  82. package/dist/server/features/superpowers.js +173 -0
  83. package/dist/server/handlers/chat.js +4 -0
  84. package/dist/server/handlers/context-hooks.js +171 -0
  85. package/dist/server/handlers/hooks.js +233 -0
  86. package/dist/server/handlers/session.js +1 -1
  87. package/dist/server/handlers/specs.js +57 -0
  88. package/dist/server/handlers/superpowers.js +13 -0
  89. package/dist/server/logger.js +1 -0
  90. package/dist/server/project/sdk-bridge.js +67 -2
  91. package/dist/server/project/session.js +2 -1
  92. package/package.json +1 -1
  93. package/dist/client/assets/index-etW8QK5W.css +0 -2
@@ -0,0 +1,233 @@
1
+ import { resolve } from "node:path";
2
+ import { ContextAnalyzer } from "../features/context-analyzer.js";
3
+ import { broadcast } from "../ws/broadcast.js";
4
+ import { loadConfig } from "../config.js";
5
+ import { log } from "../logger.js";
6
+ import { upsertFromSnapshot, addToolEventToHistory, markSessionEnded } from "../features/session-history.js";
7
+ var sessionProjectMap = new Map();
8
+ function matchCwdToProject(cwd) {
9
+ var config = loadConfig();
10
+ var resolved = resolve(cwd);
11
+ for (var i = 0; i < config.projects.length; i++) {
12
+ var p = config.projects[i];
13
+ var projectPath = resolve(p.path);
14
+ if (resolved === projectPath || resolved.startsWith(projectPath + "/")) {
15
+ return { projectName: p.title, projectSlug: p.slug };
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ const sessionAnalyzers = new Map();
21
+ function getOrCreateAnalyzer(sessionId) {
22
+ let analyzer = sessionAnalyzers.get(sessionId);
23
+ if (!analyzer) {
24
+ analyzer = new ContextAnalyzer(function (msg) {
25
+ broadcast({ ...msg, hookSessionId: sessionId });
26
+ });
27
+ sessionAnalyzers.set(sessionId, analyzer);
28
+ }
29
+ return analyzer;
30
+ }
31
+ function cleanupSession(sessionId) {
32
+ sessionAnalyzers.delete(sessionId);
33
+ }
34
+ export function handleHookStatusline(req, res) {
35
+ const body = req.body;
36
+ if (!body.session_id) {
37
+ res.status(400).json({ status: "error", message: "missing session_id" });
38
+ return;
39
+ }
40
+ const cw = body.context_window || {};
41
+ const cost = body.cost || {};
42
+ const model = body.model || {};
43
+ const inputTokens = cw.total_input_tokens || 0;
44
+ const outputTokens = cw.total_output_tokens || 0;
45
+ const cacheReadTokens = cw.cache_read_input_tokens || 0;
46
+ const cacheCreationTokens = cw.cache_creation_input_tokens || 0;
47
+ const contextWindow = cw.context_window_size || 0;
48
+ const usedPercent = cw.used_percentage || 0;
49
+ const costUsd = cost.total_cost_usd || 0;
50
+ const durationMs = cost.total_duration_ms || 0;
51
+ const modelId = model.id || "";
52
+ const modelName = model.display_name || "";
53
+ const analyzer = getOrCreateAnalyzer(body.session_id);
54
+ analyzer.updateUsage({
55
+ inputTokens,
56
+ outputTokens,
57
+ cacheReadTokens,
58
+ cacheCreationTokens,
59
+ }, contextWindow);
60
+ var statusProject = sessionProjectMap.get(body.session_id) || null;
61
+ broadcast({
62
+ type: "context:statusline",
63
+ hookSessionId: body.session_id,
64
+ inputTokens,
65
+ outputTokens,
66
+ cacheReadTokens,
67
+ cacheCreationTokens,
68
+ contextWindow,
69
+ usedPercent,
70
+ costUsd,
71
+ durationMs,
72
+ modelId,
73
+ modelName,
74
+ timestamp: Date.now(),
75
+ projectName: statusProject?.projectName || null,
76
+ projectSlug: statusProject?.projectSlug || null,
77
+ });
78
+ upsertFromSnapshot(body.session_id, {
79
+ inputTokens,
80
+ outputTokens,
81
+ cacheReadTokens,
82
+ cacheCreationTokens,
83
+ contextWindow,
84
+ usedPercent,
85
+ costUsd,
86
+ durationMs,
87
+ modelId,
88
+ modelName,
89
+ projectName: statusProject?.projectName || null,
90
+ projectSlug: statusProject?.projectSlug || null,
91
+ });
92
+ res.status(202).json({ status: "accepted" });
93
+ }
94
+ export function handleHookEvent(req, res) {
95
+ const body = req.body;
96
+ if (!body.session_id || !body.event_type) {
97
+ res.status(400).json({ status: "error", message: "missing session_id or event_type" });
98
+ return;
99
+ }
100
+ const analyzer = getOrCreateAnalyzer(body.session_id);
101
+ switch (body.event_type) {
102
+ case "PostToolUse": {
103
+ if (body.tool_name) {
104
+ const toolId = body.session_id + "-" + body.timestamp_ms;
105
+ analyzer.onToolStart(toolId, body.tool_name);
106
+ // For hook-based flow, the tool already ran. Mark result immediately.
107
+ // The next statusline snapshot will trigger the delta computation.
108
+ analyzer.onToolResult(toolId);
109
+ }
110
+ break;
111
+ }
112
+ case "Stop": {
113
+ cleanupSession(body.session_id);
114
+ markSessionEnded(body.session_id);
115
+ broadcast({
116
+ type: "context:session_ended",
117
+ hookSessionId: body.session_id,
118
+ timestamp: body.timestamp_ms || Date.now(),
119
+ });
120
+ break;
121
+ }
122
+ case "SessionStart": {
123
+ // Reset analyzer for fresh session
124
+ cleanupSession(body.session_id);
125
+ getOrCreateAnalyzer(body.session_id);
126
+ broadcast({
127
+ type: "context:session_started",
128
+ hookSessionId: body.session_id,
129
+ timestamp: body.timestamp_ms || Date.now(),
130
+ });
131
+ break;
132
+ }
133
+ case "PreCompact": {
134
+ broadcast({
135
+ type: "context:compact",
136
+ hookSessionId: body.session_id,
137
+ phase: "pre",
138
+ timestamp: body.timestamp_ms || Date.now(),
139
+ });
140
+ break;
141
+ }
142
+ case "PostCompact": {
143
+ broadcast({
144
+ type: "context:compact",
145
+ hookSessionId: body.session_id,
146
+ phase: "post",
147
+ timestamp: body.timestamp_ms || Date.now(),
148
+ });
149
+ break;
150
+ }
151
+ default:
152
+ break;
153
+ }
154
+ log.server("Hook event: %s for session %s", body.event_type, body.session_id.slice(0, 8));
155
+ res.status(202).json({ status: "accepted" });
156
+ }
157
+ function estimateTokens(value) {
158
+ if (value == null)
159
+ return 0;
160
+ var text = typeof value === "string" ? value : JSON.stringify(value);
161
+ return Math.ceil(text.length / 4);
162
+ }
163
+ function summarizeInput(toolName, input) {
164
+ if (input == null)
165
+ return "";
166
+ if (typeof input === "string")
167
+ return input.slice(0, 120);
168
+ if (typeof input === "object") {
169
+ var obj = input;
170
+ if (obj.command)
171
+ return String(obj.command).slice(0, 120);
172
+ if (obj.file_path)
173
+ return String(obj.file_path);
174
+ if (obj.pattern)
175
+ return String(obj.pattern).slice(0, 120);
176
+ if (obj.query)
177
+ return String(obj.query).slice(0, 120);
178
+ if (obj.prompt)
179
+ return String(obj.prompt).slice(0, 120);
180
+ var keys = Object.keys(obj);
181
+ if (keys.length > 0)
182
+ return keys.slice(0, 3).join(", ");
183
+ }
184
+ return "";
185
+ }
186
+ export function handleHookToolUse(req, res) {
187
+ var body = req.body;
188
+ if (!body.session_id) {
189
+ res.status(400).json({ status: "error", message: "missing session_id" });
190
+ return;
191
+ }
192
+ var toolName = body.tool_name || "unknown";
193
+ var inputSummary = summarizeInput(toolName, body.tool_input);
194
+ var estInput = estimateTokens(body.tool_input);
195
+ var estOutput = estimateTokens(body.tool_response);
196
+ var now = Date.now();
197
+ if (body.cwd && !sessionProjectMap.has(body.session_id)) {
198
+ var match = matchCwdToProject(body.cwd);
199
+ if (match) {
200
+ sessionProjectMap.set(body.session_id, match);
201
+ }
202
+ }
203
+ var sessionProject = sessionProjectMap.get(body.session_id) || null;
204
+ var analyzer = getOrCreateAnalyzer(body.session_id);
205
+ var toolId = body.session_id + "-" + now;
206
+ analyzer.onToolStart(toolId, toolName);
207
+ analyzer.onToolResult(toolId);
208
+ broadcast({
209
+ type: "context:tool_event",
210
+ hookSessionId: body.session_id,
211
+ toolName,
212
+ inputSummary,
213
+ estimatedInputTokens: estInput,
214
+ estimatedOutputTokens: estOutput,
215
+ estimatedTotalTokens: estInput + estOutput,
216
+ timestamp: now,
217
+ projectName: sessionProject?.projectName || null,
218
+ projectSlug: sessionProject?.projectSlug || null,
219
+ });
220
+ addToolEventToHistory(body.session_id, {
221
+ toolName,
222
+ inputSummary,
223
+ estimatedInputTokens: estInput,
224
+ estimatedOutputTokens: estOutput,
225
+ estimatedTotalTokens: estInput + estOutput,
226
+ timestamp: now,
227
+ });
228
+ log.server("Hook tool_use: %s %s for session %s", toolName, inputSummary.slice(0, 40), body.session_id.slice(0, 8));
229
+ res.status(202).json({ status: "accepted" });
230
+ }
231
+ export function getHookSessionAnalyzers() {
232
+ return sessionAnalyzers;
233
+ }
@@ -70,7 +70,7 @@ registerHandler("session", async function (clientId, message) {
70
70
  }
71
71
  if (message.type === "session:create") {
72
72
  var createMsg = message;
73
- var session = createSession(createMsg.projectSlug);
73
+ var session = createSession(createMsg.projectSlug, createMsg.sessionType);
74
74
  updateSessionInIndex(createMsg.projectSlug, session);
75
75
  sendTo(clientId, { type: "session:created", session });
76
76
  broadcastToProject(createMsg.projectSlug, {
@@ -1,6 +1,8 @@
1
1
  import { registerHandler } from "../ws/router.js";
2
2
  import { sendTo, broadcastToProject } from "../ws/broadcast.js";
3
3
  import { listSpecs, getSpec, createSpec, updateSpec, deleteSpec, linkSession, unlinkSession, addActivity, } from "../features/specs.js";
4
+ import { createSession, updateSessionInIndex } from "../project/session.js";
5
+ import { buildBrainstormPrompt, buildWritePlanPrompt, buildExecutePrompt } from "../features/superpowers.js";
4
6
  registerHandler("specs", function (clientId, message) {
5
7
  if (message.type === "specs:list") {
6
8
  var listMsg = message;
@@ -31,6 +33,61 @@ registerHandler("specs", function (clientId, message) {
31
33
  broadcastToProject(created.projectSlug, { type: "specs:created", spec: created });
32
34
  return;
33
35
  }
36
+ if (message.type === "specs:create-with-brainstorm") {
37
+ var brainstormMsg = message;
38
+ var slug = brainstormMsg.projectSlug;
39
+ var newSpec = createSpec({ projectSlug: slug, title: "New Spec" });
40
+ var brainstormSession = createSession(slug, "brainstorm");
41
+ updateSessionInIndex(slug, brainstormSession);
42
+ linkSession(newSpec.id, brainstormSession.id, "Brainstorm session", "brainstorm");
43
+ var brainstormPrompt = buildBrainstormPrompt(newSpec, slug);
44
+ sendTo(clientId, {
45
+ type: "specs:brainstorm-started",
46
+ spec: newSpec,
47
+ sessionId: brainstormSession.id,
48
+ systemPrompt: { type: "preset", preset: "claude_code", append: brainstormPrompt },
49
+ });
50
+ broadcastToProject(slug, { type: "specs:created", spec: newSpec });
51
+ return;
52
+ }
53
+ if (message.type === "specs:start-plan") {
54
+ var planMsg = message;
55
+ var planSpec = getSpec(planMsg.specId);
56
+ if (!planSpec) {
57
+ sendTo(clientId, { type: "chat:error", message: "Spec not found" });
58
+ return;
59
+ }
60
+ var planSession = createSession(planMsg.projectSlug, "write-plan");
61
+ updateSessionInIndex(planMsg.projectSlug, planSession);
62
+ linkSession(planSpec.id, planSession.id, "Write plan session", "write-plan");
63
+ var planPrompt = buildWritePlanPrompt(planSpec, planMsg.projectSlug);
64
+ sendTo(clientId, {
65
+ type: "specs:plan-started",
66
+ spec: planSpec,
67
+ sessionId: planSession.id,
68
+ systemPrompt: { type: "preset", preset: "claude_code", append: planPrompt },
69
+ });
70
+ return;
71
+ }
72
+ if (message.type === "specs:start-execute") {
73
+ var execMsg = message;
74
+ var execSpec = getSpec(execMsg.specId);
75
+ if (!execSpec) {
76
+ sendTo(clientId, { type: "chat:error", message: "Spec not found" });
77
+ return;
78
+ }
79
+ var execSession = createSession(execMsg.projectSlug, "execute");
80
+ updateSessionInIndex(execMsg.projectSlug, execSession);
81
+ linkSession(execSpec.id, execSession.id, "Execute session", "execute");
82
+ var execPrompt = buildExecutePrompt(execSpec, execMsg.projectSlug);
83
+ sendTo(clientId, {
84
+ type: "specs:execute-started",
85
+ spec: execSpec,
86
+ sessionId: execSession.id,
87
+ systemPrompt: { type: "preset", preset: "claude_code", append: execPrompt },
88
+ });
89
+ return;
90
+ }
34
91
  if (message.type === "specs:update") {
35
92
  var updateMsg = message;
36
93
  var updated = updateSpec(updateMsg.id, {
@@ -0,0 +1,13 @@
1
+ import { registerHandler } from "../ws/router.js";
2
+ import { sendTo } from "../ws/broadcast.js";
3
+ import { isSuperpowersInstalled, getSuperpowersVersion, getAvailableSkills } from "../features/superpowers.js";
4
+ registerHandler("superpowers", function (clientId, message) {
5
+ if (message.type === "superpowers:status_request") {
6
+ sendTo(clientId, {
7
+ type: "superpowers:status",
8
+ installed: isSuperpowersInstalled(),
9
+ version: getSuperpowersVersion(),
10
+ skillsAvailable: getAvailableSkills(),
11
+ });
12
+ }
13
+ });
@@ -17,4 +17,5 @@ export var log = {
17
17
  update: createDebug("lattice:update"),
18
18
  terminal: createDebug("lattice:terminal"),
19
19
  settings: createDebug("lattice:settings"),
20
+ superpowers: createDebug("lattice:superpowers"),
20
21
  };
@@ -2,7 +2,7 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { homedir } from "node:os";
5
- import { sendTo, broadcast } from "../ws/broadcast.js";
5
+ import { sendTo, broadcast, broadcastToProject } from "../ws/broadcast.js";
6
6
  import { syncSessionToPeers } from "../mesh/session-sync.js";
7
7
  import { resolveSkillContent } from "../handlers/skills.js";
8
8
  import { getPluginMcpServers } from "../handlers/plugins.js";
@@ -13,6 +13,8 @@ import { getDailySpend, invalidateDailySpendCache } from "../analytics/engine.js
13
13
  import { getWarmupModels, cacheRateLimitEntry } from "./warmup.js";
14
14
  import { execSync } from "node:child_process";
15
15
  import { sendPush } from "../push.js";
16
+ import { parseSpecPopulate, parsePlanContent, parseSpecActivity, populateSpec, updateSpec as updateSpecData, addActivity } from "../features/specs.js";
17
+ import { ContextAnalyzer } from "../features/context-analyzer.js";
16
18
  var HIDDEN_TOOLS = new Set([
17
19
  "TaskUpdate", "TaskCreate", "TaskGet", "TaskList", "TaskOutput", "TaskStop",
18
20
  "TodoWrite", "TodoRead",
@@ -640,6 +642,9 @@ export function startChatStream(options) {
640
642
  if (env) {
641
643
  queryOptions.env = env;
642
644
  }
645
+ if (options.systemPrompt) {
646
+ queryOptions.systemPrompt = options.systemPrompt;
647
+ }
643
648
  var prompt = resolvePromptText(text);
644
649
  sendTo(clientId, {
645
650
  type: "chat:user_message",
@@ -666,6 +671,13 @@ export function startChatStream(options) {
666
671
  turnDoneSent: false,
667
672
  messageUUIDs: [],
668
673
  ended: false,
674
+ accumulatedText: "",
675
+ specId: options.specId,
676
+ analyzer: new ContextAnalyzer(function (msg) {
677
+ var ss = sessionStreams.get(sessionId);
678
+ if (ss)
679
+ sendTo(ss.clientId, msg);
680
+ }),
669
681
  };
670
682
  sessionStreams.set(sessionId, sessionStream);
671
683
  persistStreamState();
@@ -728,6 +740,38 @@ export function startChatStream(options) {
728
740
  }
729
741
  })();
730
742
  }
743
+ function processStructuredOutput(ss) {
744
+ var text = ss.accumulatedText;
745
+ var specId = ss.specId;
746
+ var specFields = parseSpecPopulate(text);
747
+ if (specFields) {
748
+ var updated = populateSpec(specId, specFields, ss.sessionId);
749
+ if (updated) {
750
+ broadcastToProject(ss.projectSlug, { type: "specs:updated", spec: updated });
751
+ }
752
+ }
753
+ var planContent = parsePlanContent(text);
754
+ if (planContent) {
755
+ var updatedPlan = updateSpecData(specId, {
756
+ sections: { implementationPlan: planContent },
757
+ });
758
+ if (updatedPlan) {
759
+ broadcastToProject(ss.projectSlug, { type: "specs:updated", spec: updatedPlan });
760
+ }
761
+ }
762
+ var searchText = text;
763
+ var activityData = parseSpecActivity(searchText);
764
+ while (activityData) {
765
+ var updatedActivity = addActivity(specId, activityData.type, activityData.detail, ss.sessionId);
766
+ if (updatedActivity) {
767
+ broadcastToProject(ss.projectSlug, { type: "specs:activity_added", spec: updatedActivity });
768
+ }
769
+ var endIdx = searchText.indexOf("</spec-activity>");
770
+ searchText = searchText.slice(endIdx + "</spec-activity>".length);
771
+ activityData = parseSpecActivity(searchText);
772
+ }
773
+ ss.accumulatedText = "";
774
+ }
731
775
  function processMessage(ss, msg) {
732
776
  var sessionId = ss.sessionId;
733
777
  if (msg.type === "system") {
@@ -743,14 +787,21 @@ function processMessage(ss, msg) {
743
787
  var assistantMsg = msg;
744
788
  var msgUsage = assistantMsg.message.usage;
745
789
  if (msgUsage && msgUsage.input_tokens != null) {
790
+ var ctxWindow = guessContextWindow(assistantMsg.message.model || "");
746
791
  sendTo(ss.clientId, {
747
792
  type: "chat:context_usage",
748
793
  inputTokens: msgUsage.input_tokens || 0,
749
794
  outputTokens: msgUsage.output_tokens || 0,
750
795
  cacheReadTokens: msgUsage.cache_read_input_tokens || 0,
751
796
  cacheCreationTokens: msgUsage.cache_creation_input_tokens || 0,
752
- contextWindow: guessContextWindow(assistantMsg.message.model || ""),
797
+ contextWindow: ctxWindow,
753
798
  });
799
+ ss.analyzer.updateUsage({
800
+ inputTokens: msgUsage.input_tokens || 0,
801
+ outputTokens: msgUsage.output_tokens || 0,
802
+ cacheReadTokens: msgUsage.cache_read_input_tokens || 0,
803
+ cacheCreationTokens: msgUsage.cache_creation_input_tokens || 0,
804
+ }, ctxWindow);
754
805
  }
755
806
  return;
756
807
  }
@@ -762,6 +813,7 @@ function processMessage(ss, msg) {
762
813
  var idx = evt.index;
763
814
  if (block.type === "tool_use" && block.id && block.name) {
764
815
  ss.activeToolBlocks[idx] = { id: block.id, name: block.name, inputJson: "" };
816
+ ss.analyzer.onToolStart(block.id, block.name);
765
817
  if (HIDDEN_TOOLS.has(block.name)) {
766
818
  ss.hiddenToolIds.add(block.id);
767
819
  }
@@ -781,6 +833,9 @@ function processMessage(ss, msg) {
781
833
  var blockIdx = deltaEvt.index;
782
834
  if (deltaEvt.delta.type === "text_delta" && typeof deltaEvt.delta.text === "string") {
783
835
  sendTo(ss.clientId, { type: "chat:delta", text: deltaEvt.delta.text });
836
+ if (ss.specId) {
837
+ ss.accumulatedText += deltaEvt.delta.text;
838
+ }
784
839
  }
785
840
  else if (deltaEvt.delta.type === "input_json_delta" && ss.activeToolBlocks[blockIdx]) {
786
841
  ss.activeToolBlocks[blockIdx].inputJson += deltaEvt.delta.partial_json || "";
@@ -836,6 +891,7 @@ function processMessage(ss, msg) {
836
891
  for (var i = 0; i < content.length; i++) {
837
892
  var item = content[i];
838
893
  if (item.type === "tool_result" && item.tool_use_id) {
894
+ ss.analyzer.onToolResult(item.tool_use_id);
839
895
  if (ss.hiddenToolIds.has(item.tool_use_id))
840
896
  continue;
841
897
  var resultContent = typeof item.content === "string"
@@ -908,6 +964,15 @@ function processMessage(ss, msg) {
908
964
  cacheCreationTokens: resultMsg.usage.cache_creation_input_tokens || 0,
909
965
  contextWindow: contextWindow,
910
966
  });
967
+ ss.analyzer.updateUsage({
968
+ inputTokens: resultMsg.usage.input_tokens || 0,
969
+ outputTokens: resultMsg.usage.output_tokens || 0,
970
+ cacheReadTokens: resultMsg.usage.cache_read_input_tokens || 0,
971
+ cacheCreationTokens: resultMsg.usage.cache_creation_input_tokens || 0,
972
+ }, contextWindow);
973
+ }
974
+ if (ss.specId && ss.accumulatedText) {
975
+ processStructuredOutput(ss);
911
976
  }
912
977
  ss.turnDoneSent = true;
913
978
  sendTo(ss.clientId, { type: "chat:done", cost: cost, duration: dur });
@@ -727,7 +727,7 @@ export async function getSessionHistoryPage(sessionId, beforeIndex, limit, proje
727
727
  var page = cached.messages.slice(startIdx, endIdx);
728
728
  return { messages: page, hasMore: startIdx > 0, totalMessages: total };
729
729
  }
730
- export function createSession(projectSlug) {
730
+ export function createSession(projectSlug, sessionType) {
731
731
  var sessionId = randomUUID();
732
732
  var now = Date.now();
733
733
  return {
@@ -736,6 +736,7 @@ export function createSession(projectSlug) {
736
736
  title: "Session " + new Date(now).toLocaleString(),
737
737
  createdAt: now,
738
738
  updatedAt: now,
739
+ sessionType: (sessionType || "chat"),
739
740
  };
740
741
  }
741
742
  export async function renameSession(projectSlug, sessionId, title) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "5.8.3",
3
+ "version": "5.10.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>",