@faithfulalabi/agent-lens 0.13.0 → 0.15.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.
@@ -44,6 +44,10 @@ export function readCacheStats(dataDir) {
44
44
  row.drifting += 1;
45
45
  mergeNamed(row, 'unknown_line_types', session.counts.unknown_line_types);
46
46
  mergeNamed(row, 'unknown_top_level_fields', session.counts.unknown_top_level_fields);
47
+ const mismatches = session.counts.requested_model_mismatch;
48
+ if (mismatches !== undefined) {
49
+ row.requested_model_mismatch = (row.requested_model_mismatch ?? 0) + mismatches;
50
+ }
47
51
  }
48
52
  return {
49
53
  state: 'ready',
@@ -105,6 +109,10 @@ export function formatCacheSection(cache) {
105
109
  lines.push(` ${row.version}: ${row.projected} projected, ${row.drifting} drifting`);
106
110
  lines.push(...namedDrift('unknown record types', row.unknown_line_types));
107
111
  lines.push(...namedDrift('unknown top-level fields', row.unknown_top_level_fields));
112
+ const mismatches = row.requested_model_mismatch;
113
+ if (mismatches !== undefined) {
114
+ lines.push(` requested model differed from served model: ${mismatches} line${mismatches === 1 ? '' : 's'}`);
115
+ }
108
116
  }
109
117
  return lines;
110
118
  }
@@ -25,7 +25,10 @@ const SESSION_LIST_COLUMNS = `id, title, preview, project_path, git_branch, mode
25
25
  sub_tokens_cache_read, sub_tokens_cache_write, sub_est_cost, sub_models, rollup_state, drift_json`;
26
26
  const TOP_LEVEL_ONLY = 'parent_session_id IS NULL';
27
27
  const NO_DRIFT = '{}';
28
- export const FIELD_TIER_BUCKETS = new Set(['unknown_top_level_fields']);
28
+ export const FIELD_TIER_BUCKETS = new Set([
29
+ 'requested_model_mismatch',
30
+ 'unknown_top_level_fields',
31
+ ]);
29
32
  export function hasStructuralDrift(counts) {
30
33
  return Object.keys(counts).some((bucket) => !FIELD_TIER_BUCKETS.has(bucket));
31
34
  }
@@ -136,7 +139,9 @@ const EVENT_COLUMNS = `id, turn_id, seq, kind, ts, request_id, block_index, name
136
139
  text, text_bytes, output_storage, spill_path, spill_bytes,
137
140
  model, tokens_in, tokens_out, tokens_cache_read, tokens_cache_write, est_cost,
138
141
  child_session_id, agent_type, agent_status, raw_type, raw_subtype, image_count,
139
- json_extract(attrs, '$.renderedRole') AS rendered_role`;
142
+ json_extract(attrs, '$.renderedRole') AS rendered_role,
143
+ json_extract(attrs, '$.permissionSource') AS permission_source,
144
+ json_extract(attrs, '$.permissionReasonType') AS permission_reason_type`;
140
145
  export function buildEventPageSql(session_id, query) {
141
146
  return {
142
147
  sql: `SELECT ${EVENT_COLUMNS} FROM events WHERE session_id = ? AND seq >= ?` +
@@ -272,6 +272,7 @@ CREATE TABLE events (
272
272
  attrs TEXT NOT NULL DEFAULT '{}' -- ALLOWLIST ONLY: attributionSkill,
273
273
  -- attributionAgent, effort, permissionMode,
274
274
  -- compactMetadata, toolDenialKind, renderedRole,
275
+ -- permissionSource, permissionReasonType,
275
276
  -- unknown_fields[]
276
277
  );
277
278
  CREATE UNIQUE INDEX idx_events_session_seq ON events(session_id, seq);
@@ -2,7 +2,7 @@ import { asyncAgentLaunch, taskNotification } from '../transcript/agents.js';
2
2
  import { attachmentRecord } from '../transcript/attachments.js';
3
3
  import { contentBlocks, imageCount, resultText } from '../transcript/blocks.js';
4
4
  import { isHumanPrompt, MACHINERY_TAGS } from '../transcript/human.js';
5
- import { apiErrorText, foldControlLines, foldSessionEnvelope, permissionDecision, promptGroupId, toolDenialKind, turnDurationMs, } from '../transcript/line.js';
5
+ import { apiErrorText, foldControlLines, foldSessionEnvelope, permissionDecision, permissionGrounds, promptGroupId, toolDenialKind, turnDurationMs, } from '../transcript/line.js';
6
6
  import { foldRequestGroup, groupByRequestId, modelOfRequestGroup, requestIdOf, } from '../transcript/usage.js';
7
7
  import { joinToolCalls, NO_INPUT, NO_WIRE_INPUT, toolInput, wireInput, } from './tools.js';
8
8
  import { wireInputOf } from '../transcript/wire.js';
@@ -138,6 +138,7 @@ export function runPipeline(lines, ctx) {
138
138
  is_error: block.is_error,
139
139
  denial: toolDenialKind(line),
140
140
  decision: permissionDecision(line),
141
+ grounds: permissionGrounds(line),
141
142
  launch: asyncAgentLaunch(line, output),
142
143
  ts: line.timestamp ?? '',
143
144
  result_offset: line.byte_offset,
@@ -113,6 +113,14 @@ export function joinToolCalls(events, results, notifications, drift) {
113
113
  statedDenial(result.decision) !== (result.denial !== undefined)) {
114
114
  drift.notePermissionDecisionMismatch();
115
115
  }
116
+ const { source, reasonType } = result.grounds;
117
+ if (source !== undefined || reasonType !== undefined) {
118
+ event.attrs = JSON.stringify({
119
+ ...JSON.parse(event.attrs),
120
+ permissionSource: source,
121
+ permissionReasonType: reasonType,
122
+ });
123
+ }
116
124
  if (event.name === 'Agent' && result.launch !== undefined) {
117
125
  backPatch(event, answers.get(event.id));
118
126
  continue;
@@ -13,6 +13,7 @@ export class DriftCounter {
13
13
  unjoinedToolUses = 0;
14
14
  sidecarAgentIdMismatches = 0;
15
15
  permissionDecisionMismatches = 0;
16
+ requestedModelMismatches = 0;
16
17
  noteLine(line, knownFields) {
17
18
  for (const name of Object.keys(line)) {
18
19
  if (!knownFields.has(name))
@@ -34,9 +35,13 @@ export class DriftCounter {
34
35
  notePermissionDecisionMismatch() {
35
36
  this.permissionDecisionMismatches += 1;
36
37
  }
38
+ noteRequestedModelMismatch() {
39
+ this.requestedModelMismatches += 1;
40
+ }
37
41
  serialize() {
38
42
  return JSON.stringify({
39
43
  permission_decision_mismatch: this.permissionDecisionMismatches === 0 ? undefined : this.permissionDecisionMismatches,
44
+ requested_model_mismatch: this.requestedModelMismatches === 0 ? undefined : this.requestedModelMismatches,
40
45
  sidecar_agent_id_mismatch: this.sidecarAgentIdMismatches === 0 ? undefined : this.sidecarAgentIdMismatches,
41
46
  unjoined_tool_uses: this.unjoinedToolUses === 0 ? undefined : this.unjoinedToolUses,
42
47
  unknown_block_types: bucket(this.blockTypes),
@@ -243,6 +243,13 @@ function rawTypeKey(value) {
243
243
  return value;
244
244
  return value === null ? '<null>' : `<${typeof value}>`;
245
245
  }
246
+ function servedOtherThanRequested(raw) {
247
+ const served = str(obj(raw.message, undefined)?.model, undefined);
248
+ return (raw.requestedModel !== undefined &&
249
+ served !== undefined &&
250
+ served !== SYNTHETIC_MODEL &&
251
+ raw.requestedModel !== served);
252
+ }
246
253
  export function classifyLine(json, ctx) {
247
254
  const raw = obj(json, EMPTY_RECORD);
248
255
  const base = {
@@ -260,6 +267,9 @@ export function classifyLine(json, ctx) {
260
267
  return { ...base, kind: 'unknown', raw_type, raw_subtype: str(raw.subtype, '') };
261
268
  }
262
269
  ctx.drift.noteLine(raw, entry.knownFields);
270
+ if (entry.kind === 'assistant' && servedOtherThanRequested(raw)) {
271
+ ctx.drift.noteRequestedModelMismatch();
272
+ }
263
273
  if (entry.kind === 'system') {
264
274
  const subtype = str(raw.subtype, '');
265
275
  if (!isSystemSubtype(subtype)) {
@@ -298,6 +308,13 @@ export function permissionDecision(line) {
298
308
  return undefined;
299
309
  return str(obj(line.raw.permissionDecision, undefined)?.decision, '');
300
310
  }
311
+ export function permissionGrounds(line) {
312
+ const stated = obj(line.raw.permissionDecision, undefined);
313
+ return {
314
+ source: str(stated?.source, '') || undefined,
315
+ reasonType: str(stated?.reasonType, '') || undefined,
316
+ };
317
+ }
301
318
  export function turnDurationMs(line) {
302
319
  return line.kind === 'system' && line.subtype === 'turn_duration'
303
320
  ? num(line.raw.durationMs, undefined)
@@ -1 +1 @@
1
- export const PROJECTOR_VERSION = 19;
1
+ export const PROJECTOR_VERSION = 20;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faithfulalabi/agent-lens",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Local-first agentic tracing platform: inspect everything your agent did in a session.",
5
5
  "type": "module",
6
6
  "license": "MIT",