@faithfulalabi/agent-lens 0.1.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/LICENSE +21 -0
- package/README.md +124 -0
- package/bin/agent-lens.js +40 -0
- package/bin/package.json +4 -0
- package/dist/src/archive/cron-log.js +41 -0
- package/dist/src/archive/discover.js +89 -0
- package/dist/src/archive/index.js +7 -0
- package/dist/src/archive/lock.js +119 -0
- package/dist/src/archive/log.js +15 -0
- package/dist/src/archive/mirror.js +366 -0
- package/dist/src/archive/paths.js +159 -0
- package/dist/src/archive/read.js +133 -0
- package/dist/src/archive/report.js +272 -0
- package/dist/src/archive/seal.js +76 -0
- package/dist/src/archive/sidecar.js +39 -0
- package/dist/src/cli/args.js +47 -0
- package/dist/src/cli/commands/archive.js +65 -0
- package/dist/src/cli/commands/doctor.js +191 -0
- package/dist/src/cli/commands/prune.js +159 -0
- package/dist/src/cli/commands/rebuild.js +98 -0
- package/dist/src/cli/commands/schedule.js +325 -0
- package/dist/src/cli/commands/start.js +83 -0
- package/dist/src/cli/commands/warm.js +96 -0
- package/dist/src/cli/index.js +102 -0
- package/dist/src/content/resolve.js +163 -0
- package/dist/src/corpus/env.js +32 -0
- package/dist/src/corpus/paths.js +70 -0
- package/dist/src/corpus/scan.js +85 -0
- package/dist/src/corpus/watch.js +189 -0
- package/dist/src/db/freshness.js +82 -0
- package/dist/src/db/open.js +74 -0
- package/dist/src/db/read.js +266 -0
- package/dist/src/db/schema.js +312 -0
- package/dist/src/db/sidecars.js +216 -0
- package/dist/src/db/spill-index.js +68 -0
- package/dist/src/db/write.js +279 -0
- package/dist/src/project/pipeline.js +307 -0
- package/dist/src/project/subagents.js +41 -0
- package/dist/src/project/tools.js +94 -0
- package/dist/src/server/api.js +249 -0
- package/dist/src/server/app.js +28 -0
- package/dist/src/server/config.js +24 -0
- package/dist/src/server/drift-report.js +35 -0
- package/dist/src/server/index.js +1 -0
- package/dist/src/server/live.js +109 -0
- package/dist/src/server/middleware/host-guard.js +42 -0
- package/dist/src/server/middleware/token-auth.js +19 -0
- package/dist/src/server/start.js +150 -0
- package/dist/src/server/static-ui.js +97 -0
- package/dist/src/server/stream.js +50 -0
- package/dist/src/server/warm.js +48 -0
- package/dist/src/shared/api.js +1 -0
- package/dist/src/shared/entities.js +1 -0
- package/dist/src/shared/index.js +2 -0
- package/dist/src/shared/pricing.js +68 -0
- package/dist/src/shared/token.js +39 -0
- package/dist/src/transcript/accessors.js +28 -0
- package/dist/src/transcript/agents.js +44 -0
- package/dist/src/transcript/blocks.js +75 -0
- package/dist/src/transcript/drift.js +42 -0
- package/dist/src/transcript/human.js +65 -0
- package/dist/src/transcript/line.js +251 -0
- package/dist/src/transcript/raw-types.js +1 -0
- package/dist/src/transcript/spill.js +122 -0
- package/dist/src/transcript/usage.js +63 -0
- package/dist/src/transcript/version.js +1 -0
- package/package.json +70 -0
- package/ui/dist/assets/index-CKKoKUCq.js +254 -0
- package/ui/dist/assets/index-Chza4fL6.css +1 -0
- package/ui/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/ui/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/ui/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/ui/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/ui/dist/index.html +21 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { runPipeline } from '../project/pipeline.js';
|
|
2
|
+
import { linkSubagents, } from '../project/subagents.js';
|
|
3
|
+
import { resolvePersistedOutput } from '../transcript/spill.js';
|
|
4
|
+
import { PROJECTOR_VERSION } from '../transcript/version.js';
|
|
5
|
+
import { estimateCost } from '../shared/pricing.js';
|
|
6
|
+
const UPSERT_INDEX_SQL = `INSERT INTO sessions
|
|
7
|
+
(id, source_path, archive_path, file_mtime_ms, file_size,
|
|
8
|
+
project_path, started_at, last_activity_at)
|
|
9
|
+
VALUES
|
|
10
|
+
(:id, :source_path, :archive_path, :file_mtime_ms, :file_size,
|
|
11
|
+
:project_path, :started_at, :last_activity_at)
|
|
12
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
13
|
+
source_path = excluded.source_path,
|
|
14
|
+
archive_path = excluded.archive_path,
|
|
15
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
16
|
+
file_size = excluded.file_size,
|
|
17
|
+
project_path = excluded.project_path,
|
|
18
|
+
started_at = excluded.started_at,
|
|
19
|
+
last_activity_at = excluded.last_activity_at`;
|
|
20
|
+
export function upsertSessionIndex(db, row) {
|
|
21
|
+
db.prepare(UPSERT_INDEX_SQL).run({ ...row });
|
|
22
|
+
}
|
|
23
|
+
const UPSERT_SIDECAR_SQL = `INSERT INTO sessions
|
|
24
|
+
(id, source_path, archive_path, file_mtime_ms, file_size,
|
|
25
|
+
project_path, started_at, last_activity_at,
|
|
26
|
+
parent_session_id, spawned_by_event_id, agent_type, agent_description, spawn_depth)
|
|
27
|
+
VALUES
|
|
28
|
+
(:id, :source_path, :archive_path, :file_mtime_ms, :file_size,
|
|
29
|
+
:project_path, :started_at, :last_activity_at,
|
|
30
|
+
:parent_session_id, :spawned_by_event_id, :agent_type, :agent_description, :spawn_depth)
|
|
31
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
32
|
+
source_path = excluded.source_path,
|
|
33
|
+
archive_path = excluded.archive_path,
|
|
34
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
35
|
+
file_size = excluded.file_size,
|
|
36
|
+
project_path = excluded.project_path,
|
|
37
|
+
started_at = excluded.started_at,
|
|
38
|
+
last_activity_at = excluded.last_activity_at,
|
|
39
|
+
parent_session_id = excluded.parent_session_id,
|
|
40
|
+
spawned_by_event_id = excluded.spawned_by_event_id,
|
|
41
|
+
agent_type = excluded.agent_type,
|
|
42
|
+
agent_description = excluded.agent_description,
|
|
43
|
+
spawn_depth = excluded.spawn_depth`;
|
|
44
|
+
export function upsertSidecarIndex(db, row) {
|
|
45
|
+
db.prepare(UPSERT_SIDECAR_SQL).run({
|
|
46
|
+
...row,
|
|
47
|
+
agent_type: row.agent_type ?? null,
|
|
48
|
+
agent_description: row.agent_description ?? null,
|
|
49
|
+
spawn_depth: row.spawn_depth ?? null,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function deleteSessionProjection(db, id) {
|
|
53
|
+
db.prepare(`INSERT INTO events_fts(events_fts, rowid, text, input)
|
|
54
|
+
SELECT 'delete', rowid, text, input FROM events WHERE session_id = ?`).run(id);
|
|
55
|
+
db.prepare('DELETE FROM events WHERE session_id = ?').run(id);
|
|
56
|
+
db.prepare('DELETE FROM turns WHERE session_id = ?').run(id);
|
|
57
|
+
}
|
|
58
|
+
const INSERT_TURN_SQL = `INSERT INTO turns
|
|
59
|
+
(id, session_id, seq, kind, parent_event_id, title, started_at, ended_at,
|
|
60
|
+
duration_ms, duration_source, tokens_in, tokens_out, tokens_cache_read,
|
|
61
|
+
tokens_cache_write, tool_call_count, error_count, first_seq, last_seq)
|
|
62
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
|
|
63
|
+
const INSERT_EVENT_SQL = `INSERT INTO events
|
|
64
|
+
(id, session_id, turn_id, seq, kind, ts, request_id, block_index, name, status,
|
|
65
|
+
duration_ms, duration_source, input, input_bytes, input_storage, text, text_bytes,
|
|
66
|
+
output_storage, spill_path, spill_bytes, src_offset, src_len, result_offset,
|
|
67
|
+
result_len, result_block, model, tokens_in, tokens_out, tokens_cache_read,
|
|
68
|
+
tokens_cache_write, est_cost, child_session_id, agent_type, agent_status, raw_type,
|
|
69
|
+
raw_subtype, attrs)
|
|
70
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
|
|
71
|
+
const POPULATE_FTS_SQL = `INSERT INTO events_fts(rowid, text, input)
|
|
72
|
+
SELECT rowid, text, input FROM events WHERE session_id = ?`;
|
|
73
|
+
const WRITE_HEADER_SQL = `UPDATE sessions SET
|
|
74
|
+
project_path = COALESCE(:project_path, project_path),
|
|
75
|
+
git_branch = :git_branch,
|
|
76
|
+
model = :model,
|
|
77
|
+
harness_version = :harness_version,
|
|
78
|
+
title = :title,
|
|
79
|
+
preview = :preview,
|
|
80
|
+
started_at = :started_at,
|
|
81
|
+
last_activity_at = :last_activity_at
|
|
82
|
+
WHERE id = :id`;
|
|
83
|
+
const STAMP_SQL = `UPDATE sessions SET
|
|
84
|
+
drift_json = :drift_json,
|
|
85
|
+
projected_mtime_ms = :mtime_ms,
|
|
86
|
+
projected_size = :size,
|
|
87
|
+
projector_version = :version,
|
|
88
|
+
projected_at = :projected_at,
|
|
89
|
+
projection_state = :state,
|
|
90
|
+
projection_error = NULL
|
|
91
|
+
WHERE id = :id`;
|
|
92
|
+
const SAVEPOINT = 'agent_lens_projection';
|
|
93
|
+
export function projectSession(db, id, env, fold) {
|
|
94
|
+
const row = db.prepare('SELECT archive_path, source_path FROM sessions WHERE id = ?').get(id);
|
|
95
|
+
if (row === undefined)
|
|
96
|
+
throw new Error(`no sessions row to project: ${id}`);
|
|
97
|
+
db.exec(`SAVEPOINT ${SAVEPOINT}`);
|
|
98
|
+
try {
|
|
99
|
+
const state = writeProjection(db, id, row.archive_path, row.source_path, env, fold);
|
|
100
|
+
db.exec(`RELEASE ${SAVEPOINT}`);
|
|
101
|
+
return state;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
db.exec(`ROLLBACK TO ${SAVEPOINT}`);
|
|
105
|
+
db.exec(`RELEASE ${SAVEPOINT}`);
|
|
106
|
+
recordFailure(db, id, error);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function writeProjection(db, id, archivePath, sourcePath, env, fold) {
|
|
111
|
+
deleteSessionProjection(db, id);
|
|
112
|
+
const read = env.readLines(archivePath);
|
|
113
|
+
const projection = runPipeline(read.lines, { session_id: id, drift: read.drift });
|
|
114
|
+
const toolUseIds = new Set(projection.events
|
|
115
|
+
.filter((event) => event.kind === 'tool_call' && event.name === 'Agent')
|
|
116
|
+
.map((event) => event.id));
|
|
117
|
+
const sidecarRows = linkSubagents(projection.events, env.sidecars(archivePath, sourcePath, toolUseIds), projection.launches, read.drift);
|
|
118
|
+
const spills = resolveSpills(read.lines, env.spillEnv(archivePath));
|
|
119
|
+
const drift = withUnresolvedSpills(read.drift.serialize(), spills.unresolved);
|
|
120
|
+
const header = projection.header;
|
|
121
|
+
if (header === undefined) {
|
|
122
|
+
stamp(db, id, fold, 'empty', drift);
|
|
123
|
+
return 'empty';
|
|
124
|
+
}
|
|
125
|
+
db.prepare(WRITE_HEADER_SQL).run({
|
|
126
|
+
project_path: header.project_path ?? null,
|
|
127
|
+
git_branch: header.git_branch ?? null,
|
|
128
|
+
model: header.model ?? null,
|
|
129
|
+
harness_version: header.harness_version ?? null,
|
|
130
|
+
title: header.title ?? null,
|
|
131
|
+
preview: header.preview ?? null,
|
|
132
|
+
started_at: header.started_at,
|
|
133
|
+
last_activity_at: header.last_activity_at,
|
|
134
|
+
id,
|
|
135
|
+
});
|
|
136
|
+
for (const sidecar of sidecarRows)
|
|
137
|
+
upsertSidecarIndex(db, sidecar);
|
|
138
|
+
insertTurns(db, projection.turns);
|
|
139
|
+
insertEvents(db, projection.events, spills.byOffset);
|
|
140
|
+
db.prepare(ROLLUP_TURN_COST_SQL).run({ id });
|
|
141
|
+
db.prepare(POPULATE_FTS_SQL).run(id);
|
|
142
|
+
recomputeSessionRollups(db, id);
|
|
143
|
+
stamp(db, id, fold, 'ready', drift);
|
|
144
|
+
return 'ready';
|
|
145
|
+
}
|
|
146
|
+
function stamp(db, id, fold, state, drift) {
|
|
147
|
+
db.prepare(STAMP_SQL).run({
|
|
148
|
+
drift_json: drift,
|
|
149
|
+
mtime_ms: fold.mtime_ms,
|
|
150
|
+
size: fold.size,
|
|
151
|
+
version: PROJECTOR_VERSION,
|
|
152
|
+
projected_at: new Date().toISOString(),
|
|
153
|
+
state,
|
|
154
|
+
id,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function recordFailure(db, id, error) {
|
|
158
|
+
try {
|
|
159
|
+
db.prepare(`UPDATE sessions SET projection_state = 'failed', projection_error = ? WHERE id = ?`).run(String(error), id);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function insertTurns(db, turns) {
|
|
165
|
+
const insert = db.prepare(INSERT_TURN_SQL);
|
|
166
|
+
for (const turn of turns) {
|
|
167
|
+
insert.run(turn.id, turn.session_id, turn.seq, turn.kind, turn.parent_event_id ?? null, turn.title, turn.started_at, turn.ended_at, turn.duration_ms ?? null, turn.duration_source, turn.tokens_in, turn.tokens_out, turn.tokens_cache_read, turn.tokens_cache_write, turn.tool_call_count, turn.error_count, turn.first_seq, turn.last_seq);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function insertEvents(db, events, spills) {
|
|
171
|
+
const insert = db.prepare(INSERT_EVENT_SQL);
|
|
172
|
+
for (const event of events) {
|
|
173
|
+
const output = spillColumns(event, spills);
|
|
174
|
+
const est_cost = estimateCost(event.model, {
|
|
175
|
+
tokens_in: event.tokens_in,
|
|
176
|
+
tokens_out: event.tokens_out,
|
|
177
|
+
cache_read: event.tokens_cache_read,
|
|
178
|
+
cache_write: event.tokens_cache_write,
|
|
179
|
+
});
|
|
180
|
+
insert.run(event.id, event.session_id, event.turn_id, event.seq, event.kind, event.ts, event.request_id ?? null, event.block_index, event.name ?? null, event.status ?? null, event.duration_ms ?? null, event.duration_source ?? null, event.input ?? null, event.input_bytes ?? null, event.input_storage ?? null, event.text ?? null, event.text_bytes ?? null, output.output_storage, output.spill_path, output.spill_bytes, event.src_offset, event.src_len, event.result_offset ?? null, event.result_len ?? null, event.result_block ?? null, event.model ?? null, event.tokens_in ?? null, event.tokens_out ?? null, event.tokens_cache_read ?? null, event.tokens_cache_write ?? null, est_cost, event.child_session_id ?? null, event.agent_type ?? null, event.agent_status ?? null, event.raw_type, event.raw_subtype ?? null, event.attrs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function spillColumns(event, spills) {
|
|
184
|
+
if (event.output_storage !== 'spill' || event.result_offset === undefined) {
|
|
185
|
+
return { output_storage: event.output_storage ?? null, spill_path: null, spill_bytes: null };
|
|
186
|
+
}
|
|
187
|
+
const state = spills.get(event.result_offset);
|
|
188
|
+
if (state?.kind === 'resolved') {
|
|
189
|
+
return {
|
|
190
|
+
output_storage: 'spill',
|
|
191
|
+
spill_path: state.path,
|
|
192
|
+
spill_bytes: state.declaredSize ?? null,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { output_storage: 'missing', spill_path: null, spill_bytes: null };
|
|
196
|
+
}
|
|
197
|
+
function resolveSpills(lines, env) {
|
|
198
|
+
const byOffset = new Map();
|
|
199
|
+
let unresolved = 0;
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
const state = resolvePersistedOutput(line.raw, env);
|
|
202
|
+
if (state.kind === 'none')
|
|
203
|
+
continue;
|
|
204
|
+
byOffset.set(line.byte_offset, state);
|
|
205
|
+
if (state.kind === 'missing')
|
|
206
|
+
unresolved += 1;
|
|
207
|
+
}
|
|
208
|
+
return { byOffset, unresolved };
|
|
209
|
+
}
|
|
210
|
+
function withUnresolvedSpills(drift, unresolved) {
|
|
211
|
+
if (unresolved === 0)
|
|
212
|
+
return drift;
|
|
213
|
+
const counted = JSON.parse(drift);
|
|
214
|
+
counted['unresolved_spills'] = unresolved;
|
|
215
|
+
return JSON.stringify(counted);
|
|
216
|
+
}
|
|
217
|
+
const ROLLUP_TURN_COST_SQL = `UPDATE turns SET est_cost = (
|
|
218
|
+
CASE WHEN EXISTS (
|
|
219
|
+
SELECT 1 FROM events e WHERE e.turn_id = turns.id
|
|
220
|
+
AND e.est_cost IS NULL
|
|
221
|
+
AND (COALESCE(e.tokens_in, 0) + COALESCE(e.tokens_out, 0)
|
|
222
|
+
+ COALESCE(e.tokens_cache_read, 0) + COALESCE(e.tokens_cache_write, 0)) > 0
|
|
223
|
+
) THEN NULL
|
|
224
|
+
ELSE (SELECT sum(e2.est_cost) FROM events e2 WHERE e2.turn_id = turns.id)
|
|
225
|
+
END
|
|
226
|
+
) WHERE session_id = :id`;
|
|
227
|
+
const ROLLUP_SQL = `UPDATE sessions SET
|
|
228
|
+
turn_count = COALESCE((SELECT count(*) FROM turns WHERE session_id = :id AND kind = 'human'), 0),
|
|
229
|
+
tool_call_count = COALESCE((SELECT sum(tool_call_count) FROM turns WHERE session_id = :id), 0),
|
|
230
|
+
error_count = COALESCE((SELECT sum(error_count) FROM turns WHERE session_id = :id), 0),
|
|
231
|
+
tokens_in = COALESCE((SELECT sum(tokens_in) FROM turns WHERE session_id = :id), 0),
|
|
232
|
+
tokens_out = COALESCE((SELECT sum(tokens_out) FROM turns WHERE session_id = :id), 0),
|
|
233
|
+
tokens_cache_read = COALESCE((SELECT sum(tokens_cache_read) FROM turns WHERE session_id = :id), 0),
|
|
234
|
+
tokens_cache_write = COALESCE((SELECT sum(tokens_cache_write) FROM turns WHERE session_id = :id), 0),
|
|
235
|
+
est_cost = CASE WHEN EXISTS (
|
|
236
|
+
SELECT 1 FROM turns t WHERE t.session_id = :id
|
|
237
|
+
AND t.est_cost IS NULL
|
|
238
|
+
AND (t.tokens_in + t.tokens_out
|
|
239
|
+
+ t.tokens_cache_read + t.tokens_cache_write) > 0
|
|
240
|
+
) THEN NULL
|
|
241
|
+
ELSE (SELECT sum(t2.est_cost) FROM turns t2 WHERE t2.session_id = :id)
|
|
242
|
+
END
|
|
243
|
+
WHERE id = :id`;
|
|
244
|
+
export function recomputeSessionRollups(db, id) {
|
|
245
|
+
db.prepare(ROLLUP_SQL).run({ id });
|
|
246
|
+
}
|
|
247
|
+
const SUBAGENT_ROLLUP_SQL = `UPDATE sessions SET
|
|
248
|
+
agent_count = COALESCE((SELECT sum(1 + c.agent_count) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
249
|
+
sub_tool_call_count = COALESCE((SELECT sum(c.tool_call_count + c.sub_tool_call_count) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
250
|
+
sub_error_count = COALESCE((SELECT sum(c.error_count + c.sub_error_count) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
251
|
+
sub_tokens_in = COALESCE((SELECT sum(c.tokens_in + c.sub_tokens_in) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
252
|
+
sub_tokens_out = COALESCE((SELECT sum(c.tokens_out + c.sub_tokens_out) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
253
|
+
sub_tokens_cache_read = COALESCE((SELECT sum(c.tokens_cache_read + c.sub_tokens_cache_read) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
254
|
+
sub_tokens_cache_write = COALESCE((SELECT sum(c.tokens_cache_write + c.sub_tokens_cache_write) FROM sessions c WHERE c.parent_session_id = :id), 0),
|
|
255
|
+
sub_est_cost = CASE WHEN EXISTS (
|
|
256
|
+
SELECT 1 FROM sessions c WHERE c.parent_session_id = :id AND (
|
|
257
|
+
(c.est_cost IS NULL AND (c.tokens_in + c.tokens_out
|
|
258
|
+
+ c.tokens_cache_read + c.tokens_cache_write) > 0)
|
|
259
|
+
OR (c.sub_est_cost IS NULL AND (c.sub_tokens_in + c.sub_tokens_out
|
|
260
|
+
+ c.sub_tokens_cache_read + c.sub_tokens_cache_write) > 0))
|
|
261
|
+
) THEN NULL
|
|
262
|
+
ELSE (SELECT sum(COALESCE(c.est_cost, 0) + COALESCE(c.sub_est_cost, 0))
|
|
263
|
+
FROM sessions c
|
|
264
|
+
WHERE c.parent_session_id = :id
|
|
265
|
+
AND (c.est_cost IS NOT NULL OR c.sub_est_cost IS NOT NULL))
|
|
266
|
+
END
|
|
267
|
+
WHERE id = :id`;
|
|
268
|
+
export function recomputeSubagentRollups(db, id) {
|
|
269
|
+
db.prepare(SUBAGENT_ROLLUP_SQL).run({ id });
|
|
270
|
+
}
|
|
271
|
+
export function markRollupComplete(db, id) {
|
|
272
|
+
db.prepare(`UPDATE sessions SET rollup_state = 'complete' WHERE id = ?`).run(id);
|
|
273
|
+
}
|
|
274
|
+
export function setParentSession(db, id, parent_session_id) {
|
|
275
|
+
db.prepare('UPDATE sessions SET parent_session_id = ? WHERE id = ?').run(parent_session_id, id);
|
|
276
|
+
}
|
|
277
|
+
export function writeMeta(db, key, value) {
|
|
278
|
+
db.prepare('INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)').run(key, value);
|
|
279
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { asyncAgentLaunch, taskNotification } from '../transcript/agents.js';
|
|
2
|
+
import { contentBlocks } from '../transcript/blocks.js';
|
|
3
|
+
import { isHumanPrompt, MACHINERY_TAGS } from '../transcript/human.js';
|
|
4
|
+
import { foldControlLines, foldSessionEnvelope, promptGroupId, toolDenialKind, turnDurationMs, } from '../transcript/line.js';
|
|
5
|
+
import { foldRequestGroup, groupByRequestId, modelOfRequestGroup, requestIdOf, } from '../transcript/usage.js';
|
|
6
|
+
import { joinToolCalls, NO_INPUT, toolInput, } from './tools.js';
|
|
7
|
+
const MAX_TITLE_CHARS = 200;
|
|
8
|
+
const NO_BLOCKS = Object.freeze([]);
|
|
9
|
+
const MS_PER_DAY = 86_400_000;
|
|
10
|
+
const MS_PER_HOUR = 3_600_000;
|
|
11
|
+
const MS_PER_MINUTE = 60_000;
|
|
12
|
+
const MS_PER_SECOND = 1_000;
|
|
13
|
+
const DAYS_PER_ERA = 146_097;
|
|
14
|
+
const DAYS_TO_EPOCH = 719_468;
|
|
15
|
+
export function epochMs(ts) {
|
|
16
|
+
const year = Number(ts.slice(0, 4));
|
|
17
|
+
const month = Number(ts.slice(5, 7));
|
|
18
|
+
const day = Number(ts.slice(8, 10));
|
|
19
|
+
const shifted = year - (month <= 2 ? 1 : 0);
|
|
20
|
+
const era = ((shifted >= 0 ? shifted : shifted - 399) / 400) | 0;
|
|
21
|
+
const yearOfEra = shifted - era * 400;
|
|
22
|
+
const dayOfYear = (((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) | 0) + day - 1;
|
|
23
|
+
const dayOfEra = yearOfEra * 365 + ((yearOfEra / 4) | 0) - ((yearOfEra / 100) | 0) + dayOfYear;
|
|
24
|
+
const days = era * DAYS_PER_ERA + dayOfEra - DAYS_TO_EPOCH;
|
|
25
|
+
return (days * MS_PER_DAY +
|
|
26
|
+
Number(ts.slice(11, 13)) * MS_PER_HOUR +
|
|
27
|
+
Number(ts.slice(14, 16)) * MS_PER_MINUTE +
|
|
28
|
+
Number(ts.slice(17, 19)) * MS_PER_SECOND +
|
|
29
|
+
Number(ts.slice(20, 23)));
|
|
30
|
+
}
|
|
31
|
+
function prose(blocks) {
|
|
32
|
+
return blocks
|
|
33
|
+
.flatMap((block) => (block.kind === 'text' ? [block.text] : []))
|
|
34
|
+
.join('\n')
|
|
35
|
+
.trim();
|
|
36
|
+
}
|
|
37
|
+
function resultText(children) {
|
|
38
|
+
return children.flatMap((child) => (child.kind === 'text' ? [child.text] : [])).join('\n');
|
|
39
|
+
}
|
|
40
|
+
function blockText(block) {
|
|
41
|
+
switch (block?.kind) {
|
|
42
|
+
case 'text':
|
|
43
|
+
case 'thinking':
|
|
44
|
+
case 'thinking_elided':
|
|
45
|
+
return block.text;
|
|
46
|
+
case 'image':
|
|
47
|
+
return block.placeholder;
|
|
48
|
+
default:
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function eventKind(line, block, human) {
|
|
53
|
+
switch (block?.kind) {
|
|
54
|
+
case 'text':
|
|
55
|
+
return human ? 'prompt' : 'text';
|
|
56
|
+
case 'thinking':
|
|
57
|
+
case 'thinking_elided':
|
|
58
|
+
return 'thinking';
|
|
59
|
+
case 'tool_use':
|
|
60
|
+
return 'tool_call';
|
|
61
|
+
case 'image':
|
|
62
|
+
return 'text';
|
|
63
|
+
}
|
|
64
|
+
if (line.kind === 'system') {
|
|
65
|
+
if (line.subtype === 'compact_boundary')
|
|
66
|
+
return 'compaction';
|
|
67
|
+
if (line.subtype === 'api_error')
|
|
68
|
+
return 'error';
|
|
69
|
+
}
|
|
70
|
+
return 'unknown';
|
|
71
|
+
}
|
|
72
|
+
function rawTypeOf(line) {
|
|
73
|
+
return line.kind === 'unknown' ? line.raw_type : line.kind;
|
|
74
|
+
}
|
|
75
|
+
function rawSubtypeOf(line) {
|
|
76
|
+
if (line.kind === 'unknown')
|
|
77
|
+
return line.raw_subtype;
|
|
78
|
+
return line.kind === 'system' ? line.subtype : undefined;
|
|
79
|
+
}
|
|
80
|
+
function turnKind(lead, human, text, compacted) {
|
|
81
|
+
if (human)
|
|
82
|
+
return 'human';
|
|
83
|
+
const tag = MACHINERY_TAGS.find((candidate) => text.startsWith(candidate));
|
|
84
|
+
if (tag === '<task-notification>')
|
|
85
|
+
return 'task_notification';
|
|
86
|
+
if (tag === '<command-name>' || tag === '<command-message>')
|
|
87
|
+
return 'slash_command';
|
|
88
|
+
if (compacted)
|
|
89
|
+
return 'compaction';
|
|
90
|
+
return lead.kind === 'system' ? 'system' : 'unknown';
|
|
91
|
+
}
|
|
92
|
+
export function runPipeline(lines, ctx) {
|
|
93
|
+
const segmentOf = [];
|
|
94
|
+
const blocksAt = [];
|
|
95
|
+
const humanAt = [];
|
|
96
|
+
const assistantAt = [];
|
|
97
|
+
const leadOf = new Map();
|
|
98
|
+
const compactedSegments = new Set();
|
|
99
|
+
const reportedDuration = new Map();
|
|
100
|
+
const errorsOf = new Map();
|
|
101
|
+
const titleOf = new Map();
|
|
102
|
+
const toolResults = new Map();
|
|
103
|
+
const notifications = [];
|
|
104
|
+
const calledBy = new Map();
|
|
105
|
+
let previewText;
|
|
106
|
+
let segment = 0;
|
|
107
|
+
let currentGroup;
|
|
108
|
+
for (const [index, line] of lines.entries()) {
|
|
109
|
+
const group = promptGroupId(line);
|
|
110
|
+
if (group !== undefined && group !== currentGroup) {
|
|
111
|
+
currentGroup = group;
|
|
112
|
+
segment += 1;
|
|
113
|
+
}
|
|
114
|
+
segmentOf.push(segment);
|
|
115
|
+
const blocks = line.uuid === undefined ? NO_BLOCKS : contentBlocks(line);
|
|
116
|
+
blocksAt.push(blocks);
|
|
117
|
+
humanAt.push(isHumanPrompt(line).human);
|
|
118
|
+
if (line.kind === 'assistant')
|
|
119
|
+
assistantAt.push(index);
|
|
120
|
+
if (line.uuid !== undefined && !leadOf.has(segment))
|
|
121
|
+
leadOf.set(segment, index);
|
|
122
|
+
if (line.kind === 'system' && line.subtype === 'compact_boundary') {
|
|
123
|
+
compactedSegments.add(segment);
|
|
124
|
+
}
|
|
125
|
+
const reported = turnDurationMs(line);
|
|
126
|
+
if (reported !== undefined && !reportedDuration.has(segment)) {
|
|
127
|
+
reportedDuration.set(segment, reported);
|
|
128
|
+
}
|
|
129
|
+
const failures = blocks.filter((block) => block.kind === 'tool_result' && block.is_error).length;
|
|
130
|
+
if (failures > 0)
|
|
131
|
+
errorsOf.set(segment, (errorsOf.get(segment) ?? 0) + failures);
|
|
132
|
+
for (const [blockIndex, block] of blocks.entries()) {
|
|
133
|
+
if (block.kind !== 'tool_result' || toolResults.has(block.tool_call_id))
|
|
134
|
+
continue;
|
|
135
|
+
const output = resultText(block.children);
|
|
136
|
+
toolResults.set(block.tool_call_id, {
|
|
137
|
+
text: output,
|
|
138
|
+
is_error: block.is_error,
|
|
139
|
+
denial: toolDenialKind(line),
|
|
140
|
+
launch: asyncAgentLaunch(line, output),
|
|
141
|
+
ts: line.timestamp ?? '',
|
|
142
|
+
result_offset: line.byte_offset,
|
|
143
|
+
result_len: line.byte_length,
|
|
144
|
+
result_block: blockIndex,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const text = prose(blocks);
|
|
148
|
+
const notification = taskNotification(text);
|
|
149
|
+
if (notification !== undefined) {
|
|
150
|
+
notifications.push(notification);
|
|
151
|
+
const called = notification.toolCallId;
|
|
152
|
+
if (called !== undefined && !calledBy.has(segment))
|
|
153
|
+
calledBy.set(segment, called);
|
|
154
|
+
}
|
|
155
|
+
if (text === '')
|
|
156
|
+
continue;
|
|
157
|
+
if (!titleOf.has(segment))
|
|
158
|
+
titleOf.set(segment, text);
|
|
159
|
+
if (humanAt[index] === true && previewText === undefined)
|
|
160
|
+
previewText = text;
|
|
161
|
+
}
|
|
162
|
+
const usageAt = new Map();
|
|
163
|
+
const modelAt = new Map();
|
|
164
|
+
let member = 0;
|
|
165
|
+
for (const group of groupByRequestId(assistantAt.map((index) => lines[index].raw))) {
|
|
166
|
+
usageAt.set(assistantAt[member], foldRequestGroup(group));
|
|
167
|
+
modelAt.set(assistantAt[member], modelOfRequestGroup(group));
|
|
168
|
+
member += group.length;
|
|
169
|
+
}
|
|
170
|
+
const events = [];
|
|
171
|
+
const ownerOfEvent = [];
|
|
172
|
+
for (const [index, line] of lines.entries()) {
|
|
173
|
+
const uuid = line.uuid;
|
|
174
|
+
if (uuid === undefined)
|
|
175
|
+
continue;
|
|
176
|
+
const blocks = blocksAt[index];
|
|
177
|
+
const human = humanAt[index];
|
|
178
|
+
const usage = usageAt.get(index);
|
|
179
|
+
let stamped = false;
|
|
180
|
+
const emit = (block, blockIndex) => {
|
|
181
|
+
if (block?.kind === 'unknown_block')
|
|
182
|
+
ctx.drift.noteUnknownBlock(block.raw_type);
|
|
183
|
+
const kind = eventKind(line, block, human);
|
|
184
|
+
const id = block?.kind === 'tool_use' && block.id !== '' ? block.id : `${uuid}:${blockIndex}`;
|
|
185
|
+
const tokens = stamped ? undefined : usage;
|
|
186
|
+
const model = stamped ? undefined : modelAt.get(index);
|
|
187
|
+
stamped = true;
|
|
188
|
+
events.push({
|
|
189
|
+
id,
|
|
190
|
+
session_id: ctx.session_id,
|
|
191
|
+
turn_id: '',
|
|
192
|
+
seq: events.length,
|
|
193
|
+
kind,
|
|
194
|
+
ts: line.timestamp ?? '',
|
|
195
|
+
request_id: requestIdOf(line.raw),
|
|
196
|
+
block_index: blockIndex,
|
|
197
|
+
name: block?.kind === 'tool_use' ? block.name : undefined,
|
|
198
|
+
status: kind === 'tool_call' ? 'running' : undefined,
|
|
199
|
+
duration_ms: undefined,
|
|
200
|
+
duration_source: undefined,
|
|
201
|
+
child_session_id: undefined,
|
|
202
|
+
agent_type: undefined,
|
|
203
|
+
...(block?.kind === 'tool_use' ? toolInput(block.input) : NO_INPUT),
|
|
204
|
+
text: kind === 'tool_call' ? undefined : blockText(block),
|
|
205
|
+
text_bytes: undefined,
|
|
206
|
+
output_storage: kind === 'tool_call' ? 'absent' : undefined,
|
|
207
|
+
spill_path: undefined,
|
|
208
|
+
agent_status: undefined,
|
|
209
|
+
src_offset: line.byte_offset,
|
|
210
|
+
src_len: line.byte_length,
|
|
211
|
+
result_offset: undefined,
|
|
212
|
+
result_len: undefined,
|
|
213
|
+
result_block: undefined,
|
|
214
|
+
model,
|
|
215
|
+
tokens_in: tokens?.input_tokens,
|
|
216
|
+
tokens_out: tokens?.output_tokens,
|
|
217
|
+
tokens_cache_read: tokens?.cache_read_input_tokens,
|
|
218
|
+
tokens_cache_write: tokens?.cache_creation_input_tokens,
|
|
219
|
+
raw_type: rawTypeOf(line),
|
|
220
|
+
raw_subtype: rawSubtypeOf(line),
|
|
221
|
+
attrs: '{}',
|
|
222
|
+
});
|
|
223
|
+
ownerOfEvent.push(segmentOf[index]);
|
|
224
|
+
};
|
|
225
|
+
if (blocks.length === 0) {
|
|
226
|
+
emit(undefined, 0);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
for (const [blockIndex, block] of blocks.entries()) {
|
|
230
|
+
if (block.kind !== 'tool_result')
|
|
231
|
+
emit(block, blockIndex);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const eventsOf = new Map();
|
|
235
|
+
for (const [index, event] of events.entries()) {
|
|
236
|
+
const owner = ownerOfEvent[index];
|
|
237
|
+
const own = eventsOf.get(owner);
|
|
238
|
+
if (own === undefined)
|
|
239
|
+
eventsOf.set(owner, [event]);
|
|
240
|
+
else
|
|
241
|
+
own.push(event);
|
|
242
|
+
}
|
|
243
|
+
const nameOfEvent = new Map();
|
|
244
|
+
for (const event of events) {
|
|
245
|
+
if (event.name !== undefined)
|
|
246
|
+
nameOfEvent.set(event.id, event.name);
|
|
247
|
+
}
|
|
248
|
+
const turns = [];
|
|
249
|
+
for (const [owner, own] of eventsOf) {
|
|
250
|
+
const seq = turns.length;
|
|
251
|
+
for (const event of own)
|
|
252
|
+
event.turn_id = `${ctx.session_id}:${seq}`;
|
|
253
|
+
const stamps = own.map((event) => event.ts).filter((ts) => ts !== '');
|
|
254
|
+
const started_at = stamps.reduce((a, b) => (a < b ? a : b), stamps[0] ?? '');
|
|
255
|
+
const ended_at = stamps.reduce((a, b) => (a > b ? a : b), stamps[0] ?? '');
|
|
256
|
+
const reported = reportedDuration.get(owner);
|
|
257
|
+
const lead = leadOf.get(owner);
|
|
258
|
+
const kind = turnKind(lines[lead], humanAt[lead], prose(blocksAt[lead]), compactedSegments.has(owner));
|
|
259
|
+
const called = kind === 'task_notification' ? calledBy.get(owner) : undefined;
|
|
260
|
+
turns.push({
|
|
261
|
+
id: `${ctx.session_id}:${seq}`,
|
|
262
|
+
session_id: ctx.session_id,
|
|
263
|
+
seq,
|
|
264
|
+
kind,
|
|
265
|
+
parent_event_id: called !== undefined && nameOfEvent.get(called) === 'Agent' ? called : undefined,
|
|
266
|
+
title: (titleOf.get(owner) ?? '').slice(0, MAX_TITLE_CHARS),
|
|
267
|
+
started_at,
|
|
268
|
+
ended_at,
|
|
269
|
+
duration_ms: reported ?? (stamps.length === 0 ? undefined : epochMs(ended_at) - epochMs(started_at)),
|
|
270
|
+
duration_source: reported === undefined ? 'derived' : 'turn_duration',
|
|
271
|
+
tokens_in: sum(own, (event) => event.tokens_in),
|
|
272
|
+
tokens_out: sum(own, (event) => event.tokens_out),
|
|
273
|
+
tokens_cache_read: sum(own, (event) => event.tokens_cache_read),
|
|
274
|
+
tokens_cache_write: sum(own, (event) => event.tokens_cache_write),
|
|
275
|
+
tool_call_count: own.filter((event) => event.kind === 'tool_call').length,
|
|
276
|
+
error_count: errorsOf.get(owner) ?? 0,
|
|
277
|
+
first_seq: own[0].seq,
|
|
278
|
+
last_seq: own[own.length - 1].seq,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
joinToolCalls(events, toolResults, notifications, ctx.drift);
|
|
282
|
+
const launches = new Map();
|
|
283
|
+
for (const [callId, result] of toolResults) {
|
|
284
|
+
const agentId = result.launch?.agentId;
|
|
285
|
+
if (agentId !== undefined)
|
|
286
|
+
launches.set(callId, agentId);
|
|
287
|
+
}
|
|
288
|
+
const envelope = foldSessionEnvelope(lines);
|
|
289
|
+
const { started_at, last_activity_at } = envelope;
|
|
290
|
+
const header = events.length === 0 || started_at === undefined || last_activity_at === undefined
|
|
291
|
+
? undefined
|
|
292
|
+
: {
|
|
293
|
+
session_id: ctx.session_id,
|
|
294
|
+
project_path: envelope.project_path,
|
|
295
|
+
git_branch: envelope.git_branch,
|
|
296
|
+
harness_version: envelope.harness_version,
|
|
297
|
+
model: envelope.model,
|
|
298
|
+
title: foldControlLines(lines).ai_title,
|
|
299
|
+
preview: previewText?.slice(0, MAX_TITLE_CHARS),
|
|
300
|
+
started_at,
|
|
301
|
+
last_activity_at,
|
|
302
|
+
};
|
|
303
|
+
return { header, turns, events, drift: ctx.drift.serialize(), launches };
|
|
304
|
+
}
|
|
305
|
+
function sum(events, of) {
|
|
306
|
+
return events.reduce((total, event) => total + (of(event) ?? 0), 0);
|
|
307
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { epochMs } from './pipeline.js';
|
|
2
|
+
export function linkSubagents(events, sidecars, launches, drift) {
|
|
3
|
+
const byToolUseId = new Map();
|
|
4
|
+
for (const sidecar of sidecars) {
|
|
5
|
+
const key = sidecar.meta.toolUseId;
|
|
6
|
+
if (key !== undefined)
|
|
7
|
+
byToolUseId.set(key, sidecar);
|
|
8
|
+
}
|
|
9
|
+
const rows = [];
|
|
10
|
+
for (const event of events) {
|
|
11
|
+
if (event.kind !== 'tool_call')
|
|
12
|
+
continue;
|
|
13
|
+
const sidecar = byToolUseId.get(event.id);
|
|
14
|
+
if (sidecar === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
const launched = launches.get(event.id);
|
|
17
|
+
if (launched !== undefined && launched !== sidecar.agent_id)
|
|
18
|
+
drift.noteSidecarMismatch();
|
|
19
|
+
event.child_session_id = sidecar.agent_id;
|
|
20
|
+
event.agent_type = sidecar.meta.agentType;
|
|
21
|
+
event.duration_ms =
|
|
22
|
+
epochMs(sidecar.envelope.last_activity_at) - epochMs(sidecar.envelope.started_at);
|
|
23
|
+
event.duration_source = 'sidecar_span';
|
|
24
|
+
rows.push({
|
|
25
|
+
id: sidecar.agent_id,
|
|
26
|
+
source_path: sidecar.source_path,
|
|
27
|
+
archive_path: sidecar.archive_path,
|
|
28
|
+
file_mtime_ms: sidecar.mtime_ms,
|
|
29
|
+
file_size: sidecar.size,
|
|
30
|
+
project_path: sidecar.envelope.project_path,
|
|
31
|
+
started_at: sidecar.envelope.started_at,
|
|
32
|
+
last_activity_at: sidecar.envelope.last_activity_at,
|
|
33
|
+
parent_session_id: event.session_id,
|
|
34
|
+
spawned_by_event_id: event.id,
|
|
35
|
+
agent_type: sidecar.meta.agentType,
|
|
36
|
+
agent_description: sidecar.meta.description,
|
|
37
|
+
spawn_depth: sidecar.meta.spawnDepth,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return rows;
|
|
41
|
+
}
|