@cryptiklemur/lattice 5.9.0 → 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.
- package/dist/client/assets/{angular-html-8JKlj5ad.js → angular-html-BDIcxkJq.js} +1 -1
- package/dist/client/assets/{angular-ts-DM4EAst9.js → angular-ts-Bt22ouNH.js} +1 -1
- package/dist/client/assets/{apl-udROknSk.js → apl-p8qkxzEK.js} +1 -1
- package/dist/client/assets/{astro-CJikhv_r.js → astro-CIaMc49M.js} +1 -1
- package/dist/client/assets/{blade-CClbyOec.js → blade-BR56EAMD.js} +1 -1
- package/dist/client/assets/{c-COT7AWn_.js → c-Dli0HzAh.js} +1 -1
- package/dist/client/assets/{cobol-CPRu8Rmc.js → cobol-Cad15ECy.js} +1 -1
- package/dist/client/assets/{coffee-BAPMJD1x.js → coffee-DpyATEbF.js} +1 -1
- package/dist/client/assets/{cpp-D17pC1A2.js → cpp-KN8_NFsf.js} +1 -1
- package/dist/client/assets/{crystal-Wm3u_0fs.js → crystal-CuyGv0kh.js} +1 -1
- package/dist/client/assets/{css-DdsIUVB9.js → css-Cm3q4bxn.js} +1 -1
- package/dist/client/assets/{dist-Czy4uBIU.js → dist-BjxsMc4u.js} +2 -2
- package/dist/client/assets/{edge-Ba2zKZ07.js → edge-B6S7CSbx.js} +1 -1
- package/dist/client/assets/{elixir-CjkAxj6n.js → elixir-CNUy9H8T.js} +1 -1
- package/dist/client/assets/{elm-eK84qhmU.js → elm-CNfcWmb9.js} +1 -1
- package/dist/client/assets/{erb-DaOSeBfZ.js → erb-DWebzDaI.js} +1 -1
- package/dist/client/assets/{git-rebase-Bs_LvrD9.js → git-rebase-B_Pt2ZBK.js} +1 -1
- package/dist/client/assets/{glimmer-js-BTQEYtLP.js → glimmer-js-CVwoOd72.js} +1 -1
- package/dist/client/assets/{glimmer-ts-8knr0l2i.js → glimmer-ts-CjtFSxjz.js} +1 -1
- package/dist/client/assets/{glsl-BYHK68lX.js → glsl-CP4rggAA.js} +1 -1
- package/dist/client/assets/{graphql-DDj1OtPF.js → graphql-Dbm6sAtp.js} +1 -1
- package/dist/client/assets/{hack-CcKIyJY8.js → hack-Bj9y3SGf.js} +1 -1
- package/dist/client/assets/{haml-Ckvp9ZDE.js → haml-DRGrdf3f.js} +1 -1
- package/dist/client/assets/{handlebars-BmkOrZAU.js → handlebars-CFKjcBMg.js} +1 -1
- package/dist/client/assets/{html-BNeILzYX.js → html-Vcd4eHHg.js} +1 -1
- package/dist/client/assets/{html-derivative-DXYTWJdT.js → html-derivative-BF0YbD4L.js} +1 -1
- package/dist/client/assets/{http-Bm6thhnK.js → http-CGVTa2NT.js} +1 -1
- package/dist/client/assets/{hurl-C1VtDM6O.js → hurl-B0GrsGqd.js} +1 -1
- package/dist/client/assets/{index-BtD8qQe3.js → index-CX1tudsF.js} +132 -132
- package/dist/client/assets/index-DlfI20Gn.css +2 -0
- package/dist/client/assets/{java-B6K9sIL3.js → java-BJHQqHsm.js} +1 -1
- package/dist/client/assets/{javascript-CVnObVni.js → javascript-CmuMsKrc.js} +1 -1
- package/dist/client/assets/{jinja-DIl2wopt.js → jinja-JxCLeq1j.js} +1 -1
- package/dist/client/assets/{jison-Bzd-FxY3.js → jison-BdgAUhei.js} +1 -1
- package/dist/client/assets/{json-DGk6UGY4.js → json-DtPissHL.js} +1 -1
- package/dist/client/assets/{jsx-gCV-fD9Z.js → jsx-DUAxxDkP.js} +1 -1
- package/dist/client/assets/{julia-BrGr__9m.js → julia-DxDlbL6e.js} +1 -1
- package/dist/client/assets/{just-Di9h03x3.js → just-CVmAAx2R.js} +1 -1
- package/dist/client/assets/{latex-DsZVz_IO.js → latex-uwxggTWA.js} +1 -1
- package/dist/client/assets/{liquid-zepQwhaf.js → liquid-xsETAJJy.js} +1 -1
- package/dist/client/assets/{lua-C0SGvxRb.js → lua-B2Hh8PgD.js} +1 -1
- package/dist/client/assets/{marko-DmPgKAzL.js → marko-yDeGxD87.js} +1 -1
- package/dist/client/assets/{mdc-1KJwlAmt.js → mdc-QMp4ieYR.js} +1 -1
- package/dist/client/assets/{nginx-_-_z2Bjm.js → nginx-7gmRmcqz.js} +1 -1
- package/dist/client/assets/{nim-C9QfMb2o.js → nim-CA8SNY_7.js} +1 -1
- package/dist/client/assets/{perl-DnP404vu.js → perl-lx5nW4VC.js} +1 -1
- package/dist/client/assets/{php-DyGyvtbP.js → php-DgHiW953.js} +1 -1
- package/dist/client/assets/{pug-C0v501gI.js → pug-CbbB1vwb.js} +1 -1
- package/dist/client/assets/{qml-Dn6-iA6b.js → qml-COrzwCIh.js} +1 -1
- package/dist/client/assets/{r-96JXyyfm.js → r-Dv7pZJDH.js} +1 -1
- package/dist/client/assets/{razor-CyBZxn9W.js → razor-D2m8EDP5.js} +1 -1
- package/dist/client/assets/{regexp-WC_YlMIa.js → regexp-BXLT-jPc.js} +1 -1
- package/dist/client/assets/{rst-BByyhlEZ.js → rst-_S6rrUYh.js} +1 -1
- package/dist/client/assets/{ruby-BvQy3HKZ.js → ruby-C3XO7tYY.js} +1 -1
- package/dist/client/assets/{sas-BtW-j5ov.js → sas-DP2k4iuN.js} +1 -1
- package/dist/client/assets/{scss-DhqQMrFB.js → scss-lhLFMXGn.js} +1 -1
- package/dist/client/assets/{shellscript-D16b1-i2.js → shellscript-BYlBPHen.js} +1 -1
- package/dist/client/assets/{shellsession-Bw15k9FH.js → shellsession-CbVyQKWZ.js} +1 -1
- package/dist/client/assets/{soy-Cs-U7QL1.js → soy-Be8a0lHq.js} +1 -1
- package/dist/client/assets/{sql-BWtytU08.js → sql-2KxvU9YS.js} +1 -1
- package/dist/client/assets/{stata-CnuYfC8x.js → stata-BxlWftTS.js} +1 -1
- package/dist/client/assets/{surrealql-DKSkaRaH.js → surrealql-CJ-q86nR.js} +1 -1
- package/dist/client/assets/{svelte-Cyv2obHJ.js → svelte-Q1ml0OiY.js} +1 -1
- package/dist/client/assets/{templ-C0EIvZlo.js → templ-BbfPZhtu.js} +1 -1
- package/dist/client/assets/{tex-CuZaUPAe.js → tex-Dcth4Gi6.js} +1 -1
- package/dist/client/assets/{ts-tags-DwkkHusB.js → ts-tags-BKhSOXI3.js} +1 -1
- package/dist/client/assets/{tsx-Dtf-SzhM.js → tsx-CS6iQ0XH.js} +1 -1
- package/dist/client/assets/{twig-CeA0HG91.js → twig-BHp31ZxS.js} +1 -1
- package/dist/client/assets/{typescript-KIqRWyll.js → typescript-16YJBTaO.js} +1 -1
- package/dist/client/assets/{vue-DhTR6Wxc.js → vue-CMKwTi4r.js} +1 -1
- package/dist/client/assets/{vue-html-3oU4yV-b.js → vue-html-Dr8VUA2G.js} +1 -1
- package/dist/client/assets/{vue-vine-DTbyNCqF.js → vue-vine-DZUqDerl.js} +1 -1
- package/dist/client/assets/{xml-e4PNboGe.js → xml-CBbBKKDC.js} +1 -1
- package/dist/client/assets/{xsl-B0MOV4pk.js → xsl-DWEX6PKX.js} +1 -1
- package/dist/client/assets/{yaml-Cymug6w8.js → yaml-DvKvvh3X.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/daemon.js +42 -2
- package/dist/server/features/context-analyzer.js +239 -0
- package/dist/server/features/session-history.js +127 -0
- package/dist/server/features/specs.js +87 -1
- package/dist/server/features/superpowers.js +173 -0
- package/dist/server/handlers/chat.js +4 -0
- package/dist/server/handlers/context-hooks.js +171 -0
- package/dist/server/handlers/hooks.js +233 -0
- package/dist/server/handlers/session.js +1 -1
- package/dist/server/handlers/specs.js +57 -0
- package/dist/server/handlers/superpowers.js +13 -0
- package/dist/server/logger.js +1 -0
- package/dist/server/project/sdk-bridge.js +67 -2
- package/dist/server/project/session.js +2 -1
- package/package.json +1 -1
- package/dist/client/assets/index-B5VoMd15.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
|
+
});
|
package/dist/server/logger.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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>",
|