@threadcrumbs/common 0.1.2 → 0.1.4
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 +45 -12
- package/lib/claude-transcript.mjs +108 -5
- package/lib/context-breakdown.mjs +149 -0
- package/lib/jsonl-follower.mjs +11 -0
- package/lib/opencode-store.mjs +130 -37
- package/lib/telemetry-fields.mjs +109 -2
- package/lib/tool-kinds.mjs +256 -0
- package/package.json +1 -1
- package/telemetry/HARNESS-NOTES.md +246 -0
- package/telemetry/README.md +137 -11
- package/telemetry/antigravity.mjs +29 -4
- package/telemetry/claude-code.mjs +59 -23
- package/telemetry/codex.mjs +162 -28
- package/telemetry/cursor.mjs +45 -13
- package/telemetry/hermes.mjs +54 -18
package/telemetry/hermes.mjs
CHANGED
|
@@ -8,20 +8,25 @@ import {
|
|
|
8
8
|
DETAIL_LIMITS,
|
|
9
9
|
RECENT_MS,
|
|
10
10
|
activityDetail,
|
|
11
|
+
askOf,
|
|
11
12
|
clip,
|
|
12
13
|
clipBlock,
|
|
13
14
|
emptyActivity,
|
|
14
15
|
endCall,
|
|
15
16
|
keepNewest,
|
|
17
|
+
keepText,
|
|
16
18
|
number,
|
|
19
|
+
openTool,
|
|
17
20
|
planItems,
|
|
18
21
|
planSummary,
|
|
19
22
|
settleCalls,
|
|
20
23
|
startCall,
|
|
21
24
|
text,
|
|
25
|
+
textList,
|
|
22
26
|
titleFromPrompt,
|
|
23
27
|
toolFile,
|
|
24
28
|
toolInput,
|
|
29
|
+
toolNow,
|
|
25
30
|
toolTarget,
|
|
26
31
|
} from '../lib/telemetry-fields.mjs'
|
|
27
32
|
|
|
@@ -183,8 +188,11 @@ function sessionsFrom(db, profile, lengths, home, at) {
|
|
|
183
188
|
const open = turnOpen(tip, root)
|
|
184
189
|
if (!open && at - lastAt(tip) > RECENT_MS) continue
|
|
185
190
|
const ids = chain.map((r) => r.id)
|
|
186
|
-
const
|
|
187
|
-
|
|
191
|
+
const working = rows
|
|
192
|
+
.filter((r) => isErrand(r) && ids.includes(r.parent_session_id) && turnOpen(r, chainOf(r).at(-1)))
|
|
193
|
+
.sort((a, b) => seconds(a.started_at) - seconds(b.started_at))
|
|
194
|
+
.map((r) => ({ id: r.id, name: text(r.title) || 'Subagent' }))
|
|
195
|
+
out.push(toSession({ db, profile, tip, root, chain, ids, open, errands: working.length, workingSubagents: working, lengths, home, messages, visible, at, lastAt: lastAt(tip) }))
|
|
188
196
|
}
|
|
189
197
|
return out
|
|
190
198
|
}
|
|
@@ -213,11 +221,15 @@ function detailFrom(db, rootId, at) {
|
|
|
213
221
|
|
|
214
222
|
const activity = emptyActivity()
|
|
215
223
|
const prompts = []
|
|
224
|
+
const thoughts = []
|
|
225
|
+
const replies = []
|
|
216
226
|
const pick = (column, fallback = 'NULL') => (messages.has(column) ? column : `${fallback} AS ${column}`)
|
|
227
|
+
const said = (column) => (messages.has(column) ? `CASE WHEN role = 'assistant' THEN ${column} END AS ${column}` : `NULL AS ${column}`)
|
|
228
|
+
const reasons = messages.has('reasoning') || messages.has('reasoning_content')
|
|
217
229
|
const order = messages.has('timestamp') ? 'timestamp, id' : 'id'
|
|
218
230
|
const records = db
|
|
219
231
|
.prepare(
|
|
220
|
-
`SELECT id, role, CASE WHEN role
|
|
232
|
+
`SELECT id, role, CASE WHEN role IN ('user', 'assistant') THEN content END AS content, ${said('reasoning')}, ${said('reasoning_content')}, ${pick('tool_calls')}, ${pick('tool_call_id')}, ${pick('timestamp', '0')}
|
|
221
233
|
FROM messages WHERE session_id IN (${placeholders}) ${visible} ORDER BY ${order}`,
|
|
222
234
|
)
|
|
223
235
|
.all(...ids)
|
|
@@ -227,10 +239,12 @@ function detailFrom(db, rootId, at) {
|
|
|
227
239
|
settleCalls(activity)
|
|
228
240
|
const prompt = text(record.content).trim()
|
|
229
241
|
if (prompt) keepNewest(prompts, { at: time, text: clipBlock(prompt, DETAIL_LIMITS.promptText) }, DETAIL_LIMITS.prompts)
|
|
230
|
-
} else if (record.role === 'assistant'
|
|
231
|
-
|
|
242
|
+
} else if (record.role === 'assistant') {
|
|
243
|
+
keepText(thoughts, 'thoughts', { at: time, text: text(record.reasoning) || text(record.reasoning_content) })
|
|
244
|
+
keepText(replies, 'replies', { at: time, text: text(record.content) })
|
|
245
|
+
for (const call of (record.tool_calls && parseJson(record.tool_calls, [])) || []) {
|
|
232
246
|
if (!call?.id) continue
|
|
233
|
-
const args =
|
|
247
|
+
const args = callArgs(call)
|
|
234
248
|
startCall(activity, { id: text(call.id), name: text(call.function?.name), target: toolTarget(args, cwd), input: toolInput(args), ...toolFile(args, cwd), startedAt: time })
|
|
235
249
|
}
|
|
236
250
|
} else if (record.role === 'tool' && record.tool_call_id) {
|
|
@@ -252,7 +266,7 @@ function detailFrom(db, rootId, at) {
|
|
|
252
266
|
status: working ? 'working' : FAILED_END.test(text(r.end_reason)) ? 'error' : r.ended_at != null ? 'done' : 'idle',
|
|
253
267
|
model: text(r.model),
|
|
254
268
|
tokens: tokens.some((t) => t != null) ? { input: tokens[0] || 0, output: tokens[1] || 0, cacheRead: tokens[2] || 0, cacheWrite: tokens[3] || 0 } : null,
|
|
255
|
-
tool: working && label ? { name: label[1].trim(), startedAt: seconds(r.last_activity_at) } : null,
|
|
269
|
+
tool: working && label ? toolNow({ name: label[1].trim(), startedAt: seconds(r.last_activity_at) }) : null,
|
|
256
270
|
startedAt: seconds(r.started_at),
|
|
257
271
|
lastActivityAt: lastAt(r),
|
|
258
272
|
}
|
|
@@ -289,11 +303,14 @@ function detailFrom(db, rootId, at) {
|
|
|
289
303
|
subagents,
|
|
290
304
|
files: null,
|
|
291
305
|
prompts,
|
|
306
|
+
thoughts: reasons ? textList(thoughts) : null,
|
|
307
|
+
replies: textList(replies),
|
|
308
|
+
contextBreakdown: null,
|
|
292
309
|
errors,
|
|
293
310
|
}
|
|
294
311
|
}
|
|
295
312
|
|
|
296
|
-
function openCalls(db, sessionId, messages, visible) {
|
|
313
|
+
function openCalls(db, sessionId, messages, visible, cwd) {
|
|
297
314
|
if (!['tool_calls', 'tool_call_id', 'timestamp'].every((c) => messages.has(c))) return []
|
|
298
315
|
const last = db
|
|
299
316
|
.prepare(`SELECT id, tool_calls, timestamp FROM messages WHERE session_id = ? AND role = 'assistant' ${visible} ORDER BY id DESC LIMIT 1`)
|
|
@@ -310,7 +327,17 @@ function openCalls(db, sessionId, messages, visible) {
|
|
|
310
327
|
)
|
|
311
328
|
return (Array.isArray(calls) ? calls : [])
|
|
312
329
|
.filter((c) => c?.id && !answered.has(c.id))
|
|
313
|
-
.map((c) => (
|
|
330
|
+
.map((c) => openTool(text(c.function?.name), seconds(last.timestamp), callArgs(c), cwd))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const callArgs = (call) => (typeof call.function?.arguments === 'string' ? parseJson(call.function.arguments, null) : call.function?.arguments)
|
|
334
|
+
|
|
335
|
+
function turnStart(db, ids, messages, visible) {
|
|
336
|
+
if (!messages.has('timestamp')) return null
|
|
337
|
+
const row = db
|
|
338
|
+
.prepare(`SELECT MAX(timestamp) AS at FROM messages WHERE session_id IN (${ids.map(() => '?').join(', ')}) AND role = 'user' ${visible}`)
|
|
339
|
+
.get(...ids)
|
|
340
|
+
return row?.at == null ? null : seconds(row.at)
|
|
314
341
|
}
|
|
315
342
|
|
|
316
343
|
function latestPlan(db, ids, messages) {
|
|
@@ -339,25 +366,28 @@ function sum(chain, column) {
|
|
|
339
366
|
return known ? total : null
|
|
340
367
|
}
|
|
341
368
|
|
|
342
|
-
function toSession({ db, profile, tip, root, chain, ids, open, errands, lengths, home, messages, visible, at, lastAt }) {
|
|
369
|
+
function toSession({ db, profile, tip, root, chain, ids, open, errands, workingSubagents, lengths, home, messages, visible, at, lastAt }) {
|
|
343
370
|
const description = text(tip.last_activity_description)
|
|
344
371
|
const placeholders = ids.map(() => '?').join(', ')
|
|
345
|
-
const calls = open ? openCalls(db, tip.id, messages, visible) : []
|
|
372
|
+
const calls = open ? openCalls(db, tip.id, messages, visible, text(tip.cwd)) : []
|
|
346
373
|
const working = calls.filter((c) => !DELEGATE_TOOLS.has(c.name))
|
|
374
|
+
const asking = ASKING.exec(description)
|
|
347
375
|
let status
|
|
376
|
+
let statusReason
|
|
348
377
|
if (open) {
|
|
349
|
-
if (
|
|
350
|
-
else
|
|
351
|
-
|
|
352
|
-
else if (
|
|
353
|
-
else
|
|
378
|
+
if (asking) [status, statusReason] = ['asking', /approval/i.test(asking[1]) ? 'approval' : 'question']
|
|
379
|
+
else if (!working.length && (calls.length || errands)) [status, statusReason] = ['delegating', 'subagents']
|
|
380
|
+
else [status, statusReason] = ['working', 'turn-open']
|
|
381
|
+
} else if (errands) [status, statusReason] = ['delegating', 'subagents']
|
|
382
|
+
else if (tip.last_read_at != null) [status, statusReason] = seconds(tip.last_activity_at || tip.started_at) > seconds(tip.last_read_at) ? ['done', 'unread'] : ['idle', 'read']
|
|
383
|
+
else [status, statusReason] = tip.ended_at == null && at - lastAt < ACTIVE_WINDOW_MS ? ['done', 'recent'] : ['idle', 'quiet']
|
|
354
384
|
|
|
355
385
|
let tool = null
|
|
356
386
|
if (status === 'working' || status === 'delegating') {
|
|
357
387
|
const latest = (working.length ? working : calls).at(-1)
|
|
358
388
|
const label = TOOL_LABEL.exec(description)
|
|
359
|
-
if (latest) tool = latest
|
|
360
|
-
else if (label) tool = { name: label[1].trim(), startedAt: seconds(tip.last_activity_at) }
|
|
389
|
+
if (latest) tool = toolNow(latest)
|
|
390
|
+
else if (label) tool = toolNow({ name: label[1].trim(), startedAt: seconds(tip.last_activity_at) })
|
|
361
391
|
}
|
|
362
392
|
|
|
363
393
|
const where = text(tip.git_repo_root) || text(tip.cwd)
|
|
@@ -393,11 +423,17 @@ function toSession({ db, profile, tip, root, chain, ids, open, errands, lengths,
|
|
|
393
423
|
? { input: tokens[0] || 0, output: tokens[1] || 0, cacheRead: tokens[2] || 0, cacheWrite: tokens[3] || 0 }
|
|
394
424
|
: null,
|
|
395
425
|
context: used ? { used, limit: contextLimit(lengths, text(tip.model), tip.billing_base_url) } : null,
|
|
426
|
+
contextHistory: null,
|
|
396
427
|
costUsd: costs.some((c) => c != null) ? costs.reduce((total, c) => total + number(c), 0) : null,
|
|
397
428
|
lines: null,
|
|
398
429
|
plan: planSummary(latestPlan(db, ids, messages)),
|
|
399
430
|
subagents: errands,
|
|
431
|
+
workingSubagents,
|
|
400
432
|
prompts: number(prompts?.n),
|
|
433
|
+
turnStartedAt: open ? turnStart(db, ids, messages, visible) : null,
|
|
434
|
+
statusReason,
|
|
435
|
+
ask: askOf(status),
|
|
436
|
+
usageLimits: null,
|
|
401
437
|
}
|
|
402
438
|
}
|
|
403
439
|
|