@weavelogic/knowledge-graph-agent 0.9.0 → 0.10.1
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/README.md +166 -2
- package/dist/_virtual/index10.js +2 -2
- package/dist/_virtual/index11.js +2 -2
- package/dist/_virtual/index8.js +2 -2
- package/dist/_virtual/index9.js +2 -2
- package/dist/claude/hook-capture.d.ts +209 -0
- package/dist/claude/hook-capture.d.ts.map +1 -0
- package/dist/claude/hook-capture.js +792 -0
- package/dist/claude/hook-capture.js.map +1 -0
- package/dist/claude/index.d.ts +15 -0
- package/dist/claude/index.d.ts.map +1 -0
- package/dist/claude/types.d.ts +1054 -0
- package/dist/claude/types.d.ts.map +1 -0
- package/dist/claude/types.js +61 -0
- package/dist/claude/types.js.map +1 -0
- package/dist/cli/commands/analyze.js +3 -3
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/convert.js +1 -1
- package/dist/cli/commands/convert.js.map +1 -1
- package/dist/cli/commands/hooks.d.ts +11 -0
- package/dist/cli/commands/hooks.d.ts.map +1 -0
- package/dist/cli/commands/hooks.js +282 -0
- package/dist/cli/commands/hooks.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +9 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/docs-analyzer.d.ts +2 -2
- package/dist/generators/docs-analyzer.d.ts.map +1 -1
- package/dist/generators/docs-analyzer.js +1 -1
- package/dist/generators/docs-analyzer.js.map +1 -1
- package/dist/generators/docs-convert.d.ts +1 -1
- package/dist/generators/docs-convert.d.ts.map +1 -1
- package/dist/generators/docs-convert.js +1 -1
- package/dist/generators/docs-convert.js.map +1 -1
- package/dist/node_modules/@typescript-eslint/project-service/dist/index.js +1 -1
- package/dist/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/dist/commonjs/index.js +1 -1
- package/dist/node_modules/fdir/dist/index.js +1 -1
- package/dist/node_modules/tinyglobby/dist/index.js +1 -1
- package/dist/node_modules/ts-api-utils/lib/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { createSessionId, createConversationId, createMessageId, createToolCallId, createSubAgentId, createSwarmId } from "./types.js";
|
|
4
|
+
const DEFAULT_CAPTURE_CONFIG = {
|
|
5
|
+
storageDir: ".kg/claude",
|
|
6
|
+
useKnowledgeGraph: true,
|
|
7
|
+
createMarkdown: true,
|
|
8
|
+
separateToolOutputs: true,
|
|
9
|
+
maxContentLength: 5e4,
|
|
10
|
+
captureSubAgents: true,
|
|
11
|
+
captureSwarms: true,
|
|
12
|
+
captureWorkflows: true,
|
|
13
|
+
defaultTags: ["claude", "interaction"]
|
|
14
|
+
};
|
|
15
|
+
class HookCaptureSystem {
|
|
16
|
+
config;
|
|
17
|
+
state;
|
|
18
|
+
projectRoot;
|
|
19
|
+
constructor(projectRoot, config = {}) {
|
|
20
|
+
this.projectRoot = projectRoot;
|
|
21
|
+
this.config = { ...DEFAULT_CAPTURE_CONFIG, ...config };
|
|
22
|
+
this.state = {
|
|
23
|
+
currentSessionId: null,
|
|
24
|
+
currentConversationId: null,
|
|
25
|
+
pendingToolCalls: /* @__PURE__ */ new Map(),
|
|
26
|
+
subAgentStack: [],
|
|
27
|
+
activeSwarmId: null,
|
|
28
|
+
storage: null
|
|
29
|
+
};
|
|
30
|
+
this.ensureStorageDir();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Ensure storage directory exists
|
|
34
|
+
*/
|
|
35
|
+
ensureStorageDir() {
|
|
36
|
+
const fullPath = join(this.projectRoot, this.config.storageDir);
|
|
37
|
+
if (!existsSync(fullPath)) {
|
|
38
|
+
mkdirSync(fullPath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create empty token usage
|
|
43
|
+
*/
|
|
44
|
+
createEmptyTokenUsage() {
|
|
45
|
+
return {
|
|
46
|
+
inputTokens: 0,
|
|
47
|
+
outputTokens: 0,
|
|
48
|
+
totalTokens: 0
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create empty aggregated token usage
|
|
53
|
+
*/
|
|
54
|
+
createEmptyAggregatedTokenUsage() {
|
|
55
|
+
return {
|
|
56
|
+
inputTokens: 0,
|
|
57
|
+
outputTokens: 0,
|
|
58
|
+
totalTokens: 0,
|
|
59
|
+
operationCount: 0
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create base metadata
|
|
64
|
+
*/
|
|
65
|
+
createBaseMetadata(tags = []) {
|
|
66
|
+
const now = /* @__PURE__ */ new Date();
|
|
67
|
+
return {
|
|
68
|
+
createdAt: now,
|
|
69
|
+
updatedAt: now,
|
|
70
|
+
tags: [...this.config.defaultTags, ...tags]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get current git branch
|
|
75
|
+
*/
|
|
76
|
+
getGitBranch() {
|
|
77
|
+
try {
|
|
78
|
+
const headPath = join(this.projectRoot, ".git", "HEAD");
|
|
79
|
+
if (existsSync(headPath)) {
|
|
80
|
+
const content = readFileSync(headPath, "utf-8").trim();
|
|
81
|
+
if (content.startsWith("ref: refs/heads/")) {
|
|
82
|
+
return content.replace("ref: refs/heads/", "");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Start a new session
|
|
91
|
+
*/
|
|
92
|
+
startSession(name, purpose) {
|
|
93
|
+
const sessionId = createSessionId();
|
|
94
|
+
const now = /* @__PURE__ */ new Date();
|
|
95
|
+
const environment = {
|
|
96
|
+
workingDirectory: this.projectRoot,
|
|
97
|
+
gitBranch: this.getGitBranch(),
|
|
98
|
+
platform: process.platform
|
|
99
|
+
};
|
|
100
|
+
const session = {
|
|
101
|
+
id: sessionId,
|
|
102
|
+
name: name || `Session ${now.toISOString().slice(0, 16)}`,
|
|
103
|
+
purpose: purpose || "General interaction",
|
|
104
|
+
startedAt: now,
|
|
105
|
+
status: "running",
|
|
106
|
+
conversationIds: [],
|
|
107
|
+
swarmIds: [],
|
|
108
|
+
workflowIds: [],
|
|
109
|
+
environment,
|
|
110
|
+
tokenUsage: this.createEmptyAggregatedTokenUsage(),
|
|
111
|
+
metadata: this.createBaseMetadata(["session"])
|
|
112
|
+
};
|
|
113
|
+
this.state.storage = {
|
|
114
|
+
session,
|
|
115
|
+
conversations: /* @__PURE__ */ new Map(),
|
|
116
|
+
messages: /* @__PURE__ */ new Map(),
|
|
117
|
+
toolCalls: /* @__PURE__ */ new Map(),
|
|
118
|
+
subAgents: /* @__PURE__ */ new Map(),
|
|
119
|
+
swarms: /* @__PURE__ */ new Map()
|
|
120
|
+
};
|
|
121
|
+
this.state.currentSessionId = sessionId;
|
|
122
|
+
this.saveSession(session);
|
|
123
|
+
return session;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* End the current session
|
|
127
|
+
*/
|
|
128
|
+
endSession() {
|
|
129
|
+
if (!this.state.storage) return null;
|
|
130
|
+
const session = this.state.storage.session;
|
|
131
|
+
session.endedAt = /* @__PURE__ */ new Date();
|
|
132
|
+
session.status = "completed";
|
|
133
|
+
session.metadata.updatedAt = /* @__PURE__ */ new Date();
|
|
134
|
+
this.aggregateSessionTokens();
|
|
135
|
+
this.saveSession(session);
|
|
136
|
+
const result = { ...session };
|
|
137
|
+
this.state.currentSessionId = null;
|
|
138
|
+
this.state.currentConversationId = null;
|
|
139
|
+
this.state.storage = null;
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Start a new conversation
|
|
144
|
+
*/
|
|
145
|
+
startConversation(model, systemPrompt) {
|
|
146
|
+
if (!this.state.storage) {
|
|
147
|
+
this.startSession();
|
|
148
|
+
}
|
|
149
|
+
const conversationId = createConversationId();
|
|
150
|
+
const now = /* @__PURE__ */ new Date();
|
|
151
|
+
const conversation = {
|
|
152
|
+
id: conversationId,
|
|
153
|
+
sessionId: this.state.storage.session.id,
|
|
154
|
+
model: model || "claude-opus-4-5-20251101",
|
|
155
|
+
systemPrompt,
|
|
156
|
+
messageIds: [],
|
|
157
|
+
subAgentIds: [],
|
|
158
|
+
status: "running",
|
|
159
|
+
startedAt: now,
|
|
160
|
+
tokenUsage: this.createEmptyTokenUsage(),
|
|
161
|
+
metadata: this.createBaseMetadata(["conversation"])
|
|
162
|
+
};
|
|
163
|
+
this.state.storage.conversations.set(conversationId, conversation);
|
|
164
|
+
this.state.storage.session.conversationIds.push(conversationId);
|
|
165
|
+
this.state.currentConversationId = conversationId;
|
|
166
|
+
return conversation;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Handle hook events
|
|
170
|
+
*/
|
|
171
|
+
handleHookEvent(event) {
|
|
172
|
+
switch (event.event) {
|
|
173
|
+
case "UserPromptSubmit":
|
|
174
|
+
this.handleUserPrompt(event);
|
|
175
|
+
break;
|
|
176
|
+
case "PreToolUse":
|
|
177
|
+
this.handlePreToolUse(event);
|
|
178
|
+
break;
|
|
179
|
+
case "PostToolUse":
|
|
180
|
+
this.handlePostToolUse(event);
|
|
181
|
+
break;
|
|
182
|
+
case "Stop":
|
|
183
|
+
this.handleSessionStop(event);
|
|
184
|
+
break;
|
|
185
|
+
case "PreCompact":
|
|
186
|
+
this.handlePreCompact(event);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Handle user prompt submission
|
|
192
|
+
*/
|
|
193
|
+
handleUserPrompt(event) {
|
|
194
|
+
if (!this.state.currentConversationId || !this.state.storage) {
|
|
195
|
+
this.startConversation();
|
|
196
|
+
}
|
|
197
|
+
const messageId = createMessageId();
|
|
198
|
+
const now = /* @__PURE__ */ new Date();
|
|
199
|
+
const conversationId = this.state.currentConversationId;
|
|
200
|
+
const textBlock = {
|
|
201
|
+
type: "text",
|
|
202
|
+
text: this.truncateContent(event.userPrompt || "")
|
|
203
|
+
};
|
|
204
|
+
const message = {
|
|
205
|
+
id: messageId,
|
|
206
|
+
conversationId,
|
|
207
|
+
role: "user",
|
|
208
|
+
content: textBlock.text,
|
|
209
|
+
contentBlocks: [textBlock],
|
|
210
|
+
toolCallIds: [],
|
|
211
|
+
timestamp: now,
|
|
212
|
+
tokenUsage: this.createEmptyTokenUsage(),
|
|
213
|
+
metadata: this.createBaseMetadata(["prompt", "user"])
|
|
214
|
+
};
|
|
215
|
+
this.state.storage.messages.set(messageId, message);
|
|
216
|
+
const conversation = this.state.storage.conversations.get(conversationId);
|
|
217
|
+
if (conversation) {
|
|
218
|
+
conversation.messageIds.push(messageId);
|
|
219
|
+
}
|
|
220
|
+
this.saveMessage(message);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Handle pre-tool use event
|
|
224
|
+
*/
|
|
225
|
+
handlePreToolUse(event) {
|
|
226
|
+
if (!this.state.currentConversationId || !this.state.storage) {
|
|
227
|
+
this.startConversation();
|
|
228
|
+
}
|
|
229
|
+
const toolCallId = createToolCallId();
|
|
230
|
+
const now = /* @__PURE__ */ new Date();
|
|
231
|
+
const conversationId = this.state.currentConversationId;
|
|
232
|
+
let lastMessageId;
|
|
233
|
+
const conversation = this.state.storage.conversations.get(conversationId);
|
|
234
|
+
if (conversation && conversation.messageIds.length > 0) {
|
|
235
|
+
lastMessageId = conversation.messageIds[conversation.messageIds.length - 1];
|
|
236
|
+
const lastMessage = this.state.storage.messages.get(lastMessageId);
|
|
237
|
+
if (!lastMessage || lastMessage.role === "user") {
|
|
238
|
+
const assistantMsgId = createMessageId();
|
|
239
|
+
const assistantMessage = {
|
|
240
|
+
id: assistantMsgId,
|
|
241
|
+
conversationId,
|
|
242
|
+
role: "assistant",
|
|
243
|
+
content: "",
|
|
244
|
+
contentBlocks: [],
|
|
245
|
+
toolCallIds: [],
|
|
246
|
+
timestamp: now,
|
|
247
|
+
tokenUsage: this.createEmptyTokenUsage(),
|
|
248
|
+
metadata: this.createBaseMetadata(["response", "assistant"])
|
|
249
|
+
};
|
|
250
|
+
this.state.storage.messages.set(assistantMsgId, assistantMessage);
|
|
251
|
+
conversation.messageIds.push(assistantMsgId);
|
|
252
|
+
lastMessageId = assistantMsgId;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
const assistantMsgId = createMessageId();
|
|
256
|
+
const assistantMessage = {
|
|
257
|
+
id: assistantMsgId,
|
|
258
|
+
conversationId,
|
|
259
|
+
role: "assistant",
|
|
260
|
+
content: "",
|
|
261
|
+
contentBlocks: [],
|
|
262
|
+
toolCallIds: [],
|
|
263
|
+
timestamp: now,
|
|
264
|
+
tokenUsage: this.createEmptyTokenUsage(),
|
|
265
|
+
metadata: this.createBaseMetadata(["response", "assistant"])
|
|
266
|
+
};
|
|
267
|
+
this.state.storage.messages.set(assistantMsgId, assistantMessage);
|
|
268
|
+
if (conversation) {
|
|
269
|
+
conversation.messageIds.push(assistantMsgId);
|
|
270
|
+
}
|
|
271
|
+
lastMessageId = assistantMsgId;
|
|
272
|
+
}
|
|
273
|
+
const toolCall = {
|
|
274
|
+
id: toolCallId,
|
|
275
|
+
messageId: lastMessageId,
|
|
276
|
+
toolName: event.toolName || "unknown",
|
|
277
|
+
toolCategory: this.categorizeToolCall(event.toolName || ""),
|
|
278
|
+
input: event.toolInput || {},
|
|
279
|
+
status: "running",
|
|
280
|
+
startedAt: now,
|
|
281
|
+
metadata: this.createBaseMetadata(["tool-call", event.toolName || "unknown"])
|
|
282
|
+
};
|
|
283
|
+
this.state.storage.toolCalls.set(toolCallId, toolCall);
|
|
284
|
+
const message = this.state.storage.messages.get(lastMessageId);
|
|
285
|
+
if (message) {
|
|
286
|
+
message.toolCallIds = message.toolCallIds || [];
|
|
287
|
+
message.toolCallIds.push(toolCallId);
|
|
288
|
+
}
|
|
289
|
+
this.state.pendingToolCalls.set(event.toolName || "unknown", toolCallId);
|
|
290
|
+
if (event.toolName === "Task" && this.config.captureSubAgents) {
|
|
291
|
+
this.handleSubAgentSpawn(event, toolCall);
|
|
292
|
+
}
|
|
293
|
+
if (event.toolName?.includes("swarm") && this.config.captureSwarms) {
|
|
294
|
+
this.handleSwarmOperation(event, toolCall);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Handle post-tool use event
|
|
299
|
+
*/
|
|
300
|
+
handlePostToolUse(event) {
|
|
301
|
+
if (!this.state.storage) return;
|
|
302
|
+
const now = /* @__PURE__ */ new Date();
|
|
303
|
+
const toolCallId = this.state.pendingToolCalls.get(event.toolName || "unknown");
|
|
304
|
+
if (!toolCallId) return;
|
|
305
|
+
this.state.pendingToolCalls.delete(event.toolName || "unknown");
|
|
306
|
+
const toolCall = this.state.storage.toolCalls.get(toolCallId);
|
|
307
|
+
if (!toolCall) return;
|
|
308
|
+
toolCall.output = this.truncateContent(event.toolOutput || "");
|
|
309
|
+
toolCall.completedAt = now;
|
|
310
|
+
toolCall.status = event.error ? "failed" : "completed";
|
|
311
|
+
toolCall.executionTimeMs = event.duration;
|
|
312
|
+
if (event.error) {
|
|
313
|
+
toolCall.error = {
|
|
314
|
+
type: "execution_error",
|
|
315
|
+
message: event.error
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
toolCall.metadata.updatedAt = now;
|
|
319
|
+
if (toolCall.toolCategory === "file") {
|
|
320
|
+
const input = toolCall.input;
|
|
321
|
+
const filePath = input.file_path || input.path;
|
|
322
|
+
if (filePath) {
|
|
323
|
+
toolCall.affectedFiles = [String(filePath)];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (event.toolName === "Task" && this.state.subAgentStack.length > 0) {
|
|
327
|
+
this.handleSubAgentComplete(event);
|
|
328
|
+
}
|
|
329
|
+
this.saveToolCall(toolCall);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Handle session stop event
|
|
333
|
+
*/
|
|
334
|
+
handleSessionStop(_event) {
|
|
335
|
+
this.endSession();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Handle pre-compact event
|
|
339
|
+
*/
|
|
340
|
+
handlePreCompact(_event) {
|
|
341
|
+
if (this.state.storage) {
|
|
342
|
+
this.state.storage.session.metadata.custom = {
|
|
343
|
+
...this.state.storage.session.metadata.custom,
|
|
344
|
+
lastCompaction: (/* @__PURE__ */ new Date()).toISOString()
|
|
345
|
+
};
|
|
346
|
+
this.saveSession(this.state.storage.session);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Handle sub-agent spawn
|
|
351
|
+
*/
|
|
352
|
+
handleSubAgentSpawn(event, toolCall) {
|
|
353
|
+
if (!this.state.storage || !this.state.currentConversationId) return;
|
|
354
|
+
const subAgentId = createSubAgentId();
|
|
355
|
+
const now = /* @__PURE__ */ new Date();
|
|
356
|
+
const input = event.toolInput;
|
|
357
|
+
const parentConversationId = this.state.currentConversationId;
|
|
358
|
+
const parentMessageId = toolCall.messageId;
|
|
359
|
+
const subAgent = {
|
|
360
|
+
id: subAgentId,
|
|
361
|
+
parentConversationId,
|
|
362
|
+
parentMessageId,
|
|
363
|
+
toolCallId: toolCall.id,
|
|
364
|
+
agentType: input.subagent_type || "custom",
|
|
365
|
+
name: String(input.description || "Sub-agent"),
|
|
366
|
+
task: this.truncateContent(String(input.prompt || input.description || "Unknown task")),
|
|
367
|
+
model: input.model || "claude-sonnet-4-20250514",
|
|
368
|
+
status: "running",
|
|
369
|
+
startedAt: now,
|
|
370
|
+
tokenUsage: this.createEmptyTokenUsage(),
|
|
371
|
+
metadata: this.createBaseMetadata(["sub-agent", String(input.subagent_type || "custom")])
|
|
372
|
+
};
|
|
373
|
+
this.state.storage.subAgents.set(subAgentId, subAgent);
|
|
374
|
+
this.state.subAgentStack.push(subAgentId);
|
|
375
|
+
const conversation = this.state.storage.conversations.get(parentConversationId);
|
|
376
|
+
if (conversation) {
|
|
377
|
+
conversation.subAgentIds = conversation.subAgentIds || [];
|
|
378
|
+
conversation.subAgentIds.push(subAgentId);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Handle sub-agent completion
|
|
383
|
+
*/
|
|
384
|
+
handleSubAgentComplete(event) {
|
|
385
|
+
if (!this.state.storage) return;
|
|
386
|
+
const subAgentId = this.state.subAgentStack.pop();
|
|
387
|
+
if (!subAgentId) return;
|
|
388
|
+
const subAgent = this.state.storage.subAgents.get(subAgentId);
|
|
389
|
+
if (!subAgent) return;
|
|
390
|
+
const now = /* @__PURE__ */ new Date();
|
|
391
|
+
subAgent.completedAt = now;
|
|
392
|
+
subAgent.status = event.error ? "failed" : "completed";
|
|
393
|
+
subAgent.result = {
|
|
394
|
+
success: !event.error,
|
|
395
|
+
summary: this.truncateContent(event.toolOutput || "").slice(0, 500),
|
|
396
|
+
error: event.error
|
|
397
|
+
};
|
|
398
|
+
subAgent.metadata.updatedAt = now;
|
|
399
|
+
this.saveSubAgent(subAgent);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Handle swarm operation
|
|
403
|
+
*/
|
|
404
|
+
handleSwarmOperation(event, _toolCall) {
|
|
405
|
+
if (!this.state.storage) return;
|
|
406
|
+
const input = event.toolInput;
|
|
407
|
+
if (event.toolName?.includes("swarm_init")) {
|
|
408
|
+
const swarmId = createSwarmId();
|
|
409
|
+
const now = /* @__PURE__ */ new Date();
|
|
410
|
+
const swarm = {
|
|
411
|
+
id: swarmId,
|
|
412
|
+
sessionId: this.state.storage.session.id,
|
|
413
|
+
name: String(input.name || "Swarm"),
|
|
414
|
+
topology: input.topology || "mesh",
|
|
415
|
+
strategy: input.strategy || "adaptive",
|
|
416
|
+
maxAgents: input.maxAgents || 8,
|
|
417
|
+
agentIds: [],
|
|
418
|
+
task: String(input.task || "Swarm task"),
|
|
419
|
+
status: "running",
|
|
420
|
+
startedAt: now,
|
|
421
|
+
tokenUsage: this.createEmptyAggregatedTokenUsage(),
|
|
422
|
+
metadata: this.createBaseMetadata(["swarm"])
|
|
423
|
+
};
|
|
424
|
+
this.state.storage.swarms.set(swarmId, swarm);
|
|
425
|
+
this.state.storage.session.swarmIds = this.state.storage.session.swarmIds || [];
|
|
426
|
+
this.state.storage.session.swarmIds.push(swarmId);
|
|
427
|
+
this.state.activeSwarmId = swarmId;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Categorize tool calls
|
|
432
|
+
*/
|
|
433
|
+
categorizeToolCall(toolName) {
|
|
434
|
+
const categories = {
|
|
435
|
+
Read: "file",
|
|
436
|
+
Write: "file",
|
|
437
|
+
Edit: "file",
|
|
438
|
+
MultiEdit: "file",
|
|
439
|
+
Glob: "file",
|
|
440
|
+
Grep: "file",
|
|
441
|
+
Bash: "bash",
|
|
442
|
+
Task: "task",
|
|
443
|
+
WebFetch: "search",
|
|
444
|
+
WebSearch: "search",
|
|
445
|
+
TodoWrite: "todo",
|
|
446
|
+
NotebookEdit: "notebook",
|
|
447
|
+
Skill: "skill"
|
|
448
|
+
};
|
|
449
|
+
if (toolName.startsWith("mcp__")) {
|
|
450
|
+
return "mcp";
|
|
451
|
+
}
|
|
452
|
+
return categories[toolName] || "other";
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Aggregate session tokens
|
|
456
|
+
*/
|
|
457
|
+
aggregateSessionTokens() {
|
|
458
|
+
if (!this.state.storage) return;
|
|
459
|
+
let totalInput = 0;
|
|
460
|
+
let totalOutput = 0;
|
|
461
|
+
let operationCount = 0;
|
|
462
|
+
for (const conversation of this.state.storage.conversations.values()) {
|
|
463
|
+
totalInput += conversation.tokenUsage.inputTokens;
|
|
464
|
+
totalOutput += conversation.tokenUsage.outputTokens;
|
|
465
|
+
operationCount++;
|
|
466
|
+
}
|
|
467
|
+
this.state.storage.session.tokenUsage = {
|
|
468
|
+
inputTokens: totalInput,
|
|
469
|
+
outputTokens: totalOutput,
|
|
470
|
+
totalTokens: totalInput + totalOutput,
|
|
471
|
+
operationCount
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Truncate content to max length
|
|
476
|
+
*/
|
|
477
|
+
truncateContent(content) {
|
|
478
|
+
if (content.length <= this.config.maxContentLength) {
|
|
479
|
+
return content;
|
|
480
|
+
}
|
|
481
|
+
return content.slice(0, this.config.maxContentLength) + "\n\n[... truncated ...]";
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get storage path for an entity
|
|
485
|
+
*/
|
|
486
|
+
getStoragePath(type, id) {
|
|
487
|
+
const dir = join(this.projectRoot, this.config.storageDir, type);
|
|
488
|
+
if (!existsSync(dir)) {
|
|
489
|
+
mkdirSync(dir, { recursive: true });
|
|
490
|
+
}
|
|
491
|
+
return join(dir, `${id}.json`);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get markdown path for an entity
|
|
495
|
+
*/
|
|
496
|
+
getMarkdownPath(type, id) {
|
|
497
|
+
const dir = join(this.projectRoot, this.config.storageDir, "docs", type);
|
|
498
|
+
if (!existsSync(dir)) {
|
|
499
|
+
mkdirSync(dir, { recursive: true });
|
|
500
|
+
}
|
|
501
|
+
return join(dir, `${id}.md`);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Save session to storage
|
|
505
|
+
*/
|
|
506
|
+
saveSession(session) {
|
|
507
|
+
const path = this.getStoragePath("sessions", session.id);
|
|
508
|
+
writeFileSync(path, JSON.stringify(session, null, 2));
|
|
509
|
+
if (this.config.createMarkdown) {
|
|
510
|
+
this.saveSessionMarkdown(session);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Save session as markdown
|
|
515
|
+
*/
|
|
516
|
+
saveSessionMarkdown(session) {
|
|
517
|
+
const path = this.getMarkdownPath("sessions", session.id);
|
|
518
|
+
const content = `---
|
|
519
|
+
title: "${session.name}"
|
|
520
|
+
type: claude-session
|
|
521
|
+
status: ${session.status}
|
|
522
|
+
created: ${session.startedAt.toISOString()}
|
|
523
|
+
updated: ${session.metadata.updatedAt.toISOString()}
|
|
524
|
+
tags: [${session.metadata.tags.join(", ")}]
|
|
525
|
+
session_id: "${session.id}"
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
# ${session.name}
|
|
529
|
+
|
|
530
|
+
**Purpose:** ${session.purpose || "General interaction"}
|
|
531
|
+
|
|
532
|
+
**Started:** ${session.startedAt.toISOString()}
|
|
533
|
+
${session.endedAt ? `**Ended:** ${session.endedAt.toISOString()}` : "**Status:** In Progress"}
|
|
534
|
+
|
|
535
|
+
## Environment
|
|
536
|
+
|
|
537
|
+
- **Working Directory:** ${session.environment?.workingDirectory || "Unknown"}
|
|
538
|
+
${session.environment?.gitBranch ? `- **Git Branch:** ${session.environment.gitBranch}` : ""}
|
|
539
|
+
|
|
540
|
+
## Conversations
|
|
541
|
+
|
|
542
|
+
${session.conversationIds.map((id) => `- [[${id}]]`).join("\n") || "No conversations yet"}
|
|
543
|
+
|
|
544
|
+
## Token Usage
|
|
545
|
+
|
|
546
|
+
| Metric | Count |
|
|
547
|
+
|--------|-------|
|
|
548
|
+
| Input Tokens | ${session.tokenUsage.inputTokens} |
|
|
549
|
+
| Output Tokens | ${session.tokenUsage.outputTokens} |
|
|
550
|
+
| Total Tokens | ${session.tokenUsage.totalTokens} |
|
|
551
|
+
| Operations | ${session.tokenUsage.operationCount} |
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
> Captured by kg-agent hook system
|
|
555
|
+
`;
|
|
556
|
+
writeFileSync(path, content);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Save message to storage
|
|
560
|
+
*/
|
|
561
|
+
saveMessage(message) {
|
|
562
|
+
if (this.config.createMarkdown) {
|
|
563
|
+
this.appendToConversationLog(message);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Append message to conversation log
|
|
568
|
+
*/
|
|
569
|
+
appendToConversationLog(message) {
|
|
570
|
+
const path = this.getMarkdownPath("conversations", message.conversationId);
|
|
571
|
+
const dir = dirname(path);
|
|
572
|
+
if (!existsSync(dir)) {
|
|
573
|
+
mkdirSync(dir, { recursive: true });
|
|
574
|
+
}
|
|
575
|
+
if (!existsSync(path)) {
|
|
576
|
+
const header = `---
|
|
577
|
+
title: "Conversation ${message.conversationId}"
|
|
578
|
+
type: claude-conversation
|
|
579
|
+
created: ${message.timestamp.toISOString()}
|
|
580
|
+
tags: [${this.config.defaultTags.join(", ")}]
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
# Conversation Log
|
|
584
|
+
|
|
585
|
+
`;
|
|
586
|
+
writeFileSync(path, header);
|
|
587
|
+
}
|
|
588
|
+
const roleIcon = message.role === "user" ? "👤" : "🤖";
|
|
589
|
+
const entry = `
|
|
590
|
+
## ${roleIcon} ${message.role.charAt(0).toUpperCase() + message.role.slice(1)} - ${message.timestamp.toISOString()}
|
|
591
|
+
|
|
592
|
+
${message.content}
|
|
593
|
+
|
|
594
|
+
${message.toolCallIds && message.toolCallIds.length > 0 ? `**Tool Calls:** ${message.toolCallIds.join(", ")}` : ""}
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
`;
|
|
598
|
+
appendFileSync(path, entry);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Save tool call to storage
|
|
602
|
+
*/
|
|
603
|
+
saveToolCall(toolCall) {
|
|
604
|
+
if (this.config.separateToolOutputs && toolCall.output) {
|
|
605
|
+
const path = this.getStoragePath("tool-outputs", toolCall.id);
|
|
606
|
+
writeFileSync(
|
|
607
|
+
path,
|
|
608
|
+
JSON.stringify(
|
|
609
|
+
{
|
|
610
|
+
id: toolCall.id,
|
|
611
|
+
name: toolCall.toolName,
|
|
612
|
+
category: toolCall.toolCategory,
|
|
613
|
+
input: toolCall.input,
|
|
614
|
+
output: toolCall.output,
|
|
615
|
+
status: toolCall.status,
|
|
616
|
+
executionTimeMs: toolCall.executionTimeMs,
|
|
617
|
+
affectedFiles: toolCall.affectedFiles,
|
|
618
|
+
timestamp: toolCall.completedAt?.toISOString()
|
|
619
|
+
},
|
|
620
|
+
null,
|
|
621
|
+
2
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Save sub-agent to storage
|
|
628
|
+
*/
|
|
629
|
+
saveSubAgent(subAgent) {
|
|
630
|
+
const path = this.getStoragePath("agents", subAgent.id);
|
|
631
|
+
writeFileSync(path, JSON.stringify(subAgent, null, 2));
|
|
632
|
+
if (this.config.createMarkdown) {
|
|
633
|
+
this.saveSubAgentMarkdown(subAgent);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Save sub-agent as markdown
|
|
638
|
+
*/
|
|
639
|
+
saveSubAgentMarkdown(subAgent) {
|
|
640
|
+
const path = this.getMarkdownPath("agents", subAgent.id);
|
|
641
|
+
const content = `---
|
|
642
|
+
title: "Sub-Agent: ${subAgent.name.slice(0, 50)}"
|
|
643
|
+
type: claude-agent
|
|
644
|
+
status: ${subAgent.status}
|
|
645
|
+
agent_type: ${subAgent.agentType}
|
|
646
|
+
created: ${subAgent.startedAt.toISOString()}
|
|
647
|
+
tags: [${subAgent.metadata.tags.join(", ")}]
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
# Sub-Agent: ${subAgent.agentType}
|
|
651
|
+
|
|
652
|
+
**Name:** ${subAgent.name}
|
|
653
|
+
**Task:** ${subAgent.task.slice(0, 200)}${subAgent.task.length > 200 ? "..." : ""}
|
|
654
|
+
|
|
655
|
+
**Status:** ${subAgent.status}
|
|
656
|
+
**Model:** ${subAgent.model}
|
|
657
|
+
**Spawned:** ${subAgent.startedAt.toISOString()}
|
|
658
|
+
${subAgent.completedAt ? `**Completed:** ${subAgent.completedAt.toISOString()}` : ""}
|
|
659
|
+
|
|
660
|
+
## Task Description
|
|
661
|
+
|
|
662
|
+
\`\`\`
|
|
663
|
+
${subAgent.task}
|
|
664
|
+
\`\`\`
|
|
665
|
+
|
|
666
|
+
${subAgent.result ? `
|
|
667
|
+
## Result
|
|
668
|
+
|
|
669
|
+
**Success:** ${subAgent.result.success}
|
|
670
|
+
|
|
671
|
+
${subAgent.result.summary ? `### Summary
|
|
672
|
+
${subAgent.result.summary}` : ""}
|
|
673
|
+
|
|
674
|
+
${subAgent.result.error ? `### Error
|
|
675
|
+
\`\`\`
|
|
676
|
+
${subAgent.result.error}
|
|
677
|
+
\`\`\`` : ""}
|
|
678
|
+
` : ""}
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
> Captured by kg-agent hook system
|
|
682
|
+
`;
|
|
683
|
+
writeFileSync(path, content);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get current session
|
|
687
|
+
*/
|
|
688
|
+
getCurrentSession() {
|
|
689
|
+
return this.state.storage?.session || null;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get current conversation
|
|
693
|
+
*/
|
|
694
|
+
getCurrentConversation() {
|
|
695
|
+
if (!this.state.storage || !this.state.currentConversationId) return null;
|
|
696
|
+
return this.state.storage.conversations.get(this.state.currentConversationId) || null;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Load session from storage
|
|
700
|
+
*/
|
|
701
|
+
loadSession(sessionId) {
|
|
702
|
+
const path = this.getStoragePath("sessions", sessionId);
|
|
703
|
+
if (!existsSync(path)) return null;
|
|
704
|
+
try {
|
|
705
|
+
const data = readFileSync(path, "utf-8");
|
|
706
|
+
return JSON.parse(data);
|
|
707
|
+
} catch {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* List all stored sessions
|
|
713
|
+
*/
|
|
714
|
+
listSessions() {
|
|
715
|
+
const sessionsDir = join(this.projectRoot, this.config.storageDir, "sessions");
|
|
716
|
+
if (!existsSync(sessionsDir)) return [];
|
|
717
|
+
try {
|
|
718
|
+
const { readdirSync } = require("fs");
|
|
719
|
+
const files = readdirSync(sessionsDir);
|
|
720
|
+
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
721
|
+
} catch {
|
|
722
|
+
return [];
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
async function processHookEvent(projectRoot, eventType, config) {
|
|
727
|
+
const capture = new HookCaptureSystem(projectRoot, config);
|
|
728
|
+
const chunks = [];
|
|
729
|
+
for await (const chunk of process.stdin) {
|
|
730
|
+
chunks.push(chunk);
|
|
731
|
+
}
|
|
732
|
+
const inputData = Buffer.concat(chunks).toString("utf-8");
|
|
733
|
+
let eventData;
|
|
734
|
+
try {
|
|
735
|
+
const parsed = JSON.parse(inputData);
|
|
736
|
+
eventData = {
|
|
737
|
+
event: eventType,
|
|
738
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
739
|
+
toolName: parsed.tool_name || process.env.TOOL_NAME,
|
|
740
|
+
toolInput: parsed.tool_input,
|
|
741
|
+
toolOutput: parsed.tool_output,
|
|
742
|
+
userPrompt: parsed.user_prompt || inputData,
|
|
743
|
+
metadata: parsed
|
|
744
|
+
};
|
|
745
|
+
} catch {
|
|
746
|
+
eventData = {
|
|
747
|
+
event: eventType,
|
|
748
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
749
|
+
userPrompt: inputData
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
capture.handleHookEvent(eventData);
|
|
753
|
+
}
|
|
754
|
+
function generateHookConfig(projectRoot) {
|
|
755
|
+
const kgBinPath = "npx @weavelogic/knowledge-graph-agent";
|
|
756
|
+
return {
|
|
757
|
+
hooks: {
|
|
758
|
+
UserPromptSubmit: [
|
|
759
|
+
{
|
|
760
|
+
type: "command",
|
|
761
|
+
command: `${kgBinPath} hooks capture --event UserPromptSubmit --path "${projectRoot}"`
|
|
762
|
+
}
|
|
763
|
+
],
|
|
764
|
+
PreToolUse: [
|
|
765
|
+
{
|
|
766
|
+
type: "command",
|
|
767
|
+
command: `${kgBinPath} hooks capture --event PreToolUse --path "${projectRoot}"`
|
|
768
|
+
}
|
|
769
|
+
],
|
|
770
|
+
PostToolUse: [
|
|
771
|
+
{
|
|
772
|
+
type: "command",
|
|
773
|
+
command: `${kgBinPath} hooks capture --event PostToolUse --path "${projectRoot}"`
|
|
774
|
+
}
|
|
775
|
+
],
|
|
776
|
+
Stop: [
|
|
777
|
+
{
|
|
778
|
+
type: "command",
|
|
779
|
+
command: `${kgBinPath} hooks capture --event Stop --path "${projectRoot}"`
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
export {
|
|
786
|
+
DEFAULT_CAPTURE_CONFIG,
|
|
787
|
+
HookCaptureSystem,
|
|
788
|
+
HookCaptureSystem as default,
|
|
789
|
+
generateHookConfig,
|
|
790
|
+
processHookEvent
|
|
791
|
+
};
|
|
792
|
+
//# sourceMappingURL=hook-capture.js.map
|