@faithfulalabi/agent-lens 0.9.0 → 0.11.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.
@@ -197,11 +197,6 @@ CREATE TABLE events (
197
197
  seq INTEGER NOT NULL, -- thread order; deterministic from the file bytes
198
198
  kind TEXT NOT NULL, -- prompt | text | thinking | tool_call
199
199
  -- | error | compaction | unknown
200
- -- MEASURED 2026-09-11 (30,286 events): 'error'
201
- -- has 0 rows, REACHABLE BUT UNOBSERVED — writer
202
- -- pipeline.ts:306, gated on the harness line
203
- -- subtype 'api_error'. Keep the arm; see
204
- -- task 0.12.
205
200
  ts TEXT NOT NULL,
206
201
  request_id TEXT, -- groups the N lines of ONE assistant response
207
202
  -- (1:1 with message.id; 13,629/13,630 lines = 1 block)
@@ -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 { foldControlLines, foldSessionEnvelope, promptGroupId, toolDenialKind, turnDurationMs, } from '../transcript/line.js';
5
+ import { apiErrorText, foldControlLines, foldSessionEnvelope, permissionDecision, 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';
@@ -137,6 +137,7 @@ export function runPipeline(lines, ctx) {
137
137
  text: output,
138
138
  is_error: block.is_error,
139
139
  denial: toolDenialKind(line),
140
+ decision: permissionDecision(line),
140
141
  launch: asyncAgentLaunch(line, output),
141
142
  ts: line.timestamp ?? '',
142
143
  result_offset: line.byte_offset,
@@ -177,7 +178,7 @@ export function runPipeline(lines, ctx) {
177
178
  const blocks = blocksAt[index];
178
179
  const human = humanAt[index];
179
180
  const usage = usageAt.get(index);
180
- const lineText = blocks.length === 0 ? attachmentRecord(line)?.text : undefined;
181
+ const lineText = blocks.length === 0 ? (attachmentRecord(line)?.text ?? apiErrorText(line)) : undefined;
181
182
  let stamped = false;
182
183
  const emit = (block, blockIndex) => {
183
184
  if (block?.kind === 'unknown_block')
@@ -49,8 +49,15 @@ function elapsedMs(call, result) {
49
49
  const ms = epochMs(result) - epochMs(call);
50
50
  return ms < 0 ? 0 : ms;
51
51
  }
52
+ function statedDenial(decision) {
53
+ if (decision === 'reject')
54
+ return true;
55
+ if (decision === 'accept')
56
+ return false;
57
+ return undefined;
58
+ }
52
59
  function statusOf(result) {
53
- if (result.denial !== undefined)
60
+ if (statedDenial(result.decision) ?? result.denial !== undefined)
54
61
  return 'denied';
55
62
  return result.is_error ? 'error' : 'ok';
56
63
  }
@@ -102,6 +109,10 @@ export function joinToolCalls(events, results, notifications, drift) {
102
109
  event.duration_ms = elapsedMs(event.ts, result.ts);
103
110
  event.duration_source = 'elapsed';
104
111
  event.status = statusOf(result);
112
+ if (result.decision !== undefined &&
113
+ statedDenial(result.decision) !== (result.denial !== undefined)) {
114
+ drift.notePermissionDecisionMismatch();
115
+ }
105
116
  if (event.name === 'Agent' && result.launch !== undefined) {
106
117
  backPatch(event, answers.get(event.id));
107
118
  continue;
@@ -12,6 +12,7 @@ export class DriftCounter {
12
12
  blockTypes = new Map();
13
13
  unjoinedToolUses = 0;
14
14
  sidecarAgentIdMismatches = 0;
15
+ permissionDecisionMismatches = 0;
15
16
  noteLine(line, knownFields) {
16
17
  for (const name of Object.keys(line)) {
17
18
  if (!knownFields.has(name))
@@ -30,8 +31,12 @@ export class DriftCounter {
30
31
  noteSidecarMismatch() {
31
32
  this.sidecarAgentIdMismatches += 1;
32
33
  }
34
+ notePermissionDecisionMismatch() {
35
+ this.permissionDecisionMismatches += 1;
36
+ }
33
37
  serialize() {
34
38
  return JSON.stringify({
39
+ permission_decision_mismatch: this.permissionDecisionMismatches === 0 ? undefined : this.permissionDecisionMismatches,
35
40
  sidecar_agent_id_mismatch: this.sidecarAgentIdMismatches === 0 ? undefined : this.sidecarAgentIdMismatches,
36
41
  unjoined_tool_uses: this.unjoinedToolUses === 0 ? undefined : this.unjoinedToolUses,
37
42
  unknown_block_types: bucket(this.blockTypes),
@@ -75,6 +75,7 @@ const LINE_TYPES = new Map([
75
75
  'classifierMetaLines',
76
76
  'toolEndsTurn',
77
77
  'toolDenialKind',
78
+ 'permissionDecision',
78
79
  'imagePasteIds',
79
80
  'serverClassifierContext',
80
81
  'turnOrigin',
@@ -106,6 +107,11 @@ const LINE_TYPES = new Map([
106
107
  'hookAdditionalContext',
107
108
  'pendingBackgroundAgentCount',
108
109
  'pendingWorkflowCount',
110
+ 'error',
111
+ 'maxRetries',
112
+ 'retryAttempt',
113
+ 'retryInMs',
114
+ 'source',
109
115
  ]),
110
116
  },
111
117
  ],
@@ -264,11 +270,43 @@ export function promptGroupId(line) {
264
270
  export function toolDenialKind(line) {
265
271
  return str(line.raw.toolDenialKind, undefined);
266
272
  }
273
+ export function permissionDecision(line) {
274
+ if (line.raw.permissionDecision === undefined)
275
+ return undefined;
276
+ return str(obj(line.raw.permissionDecision, undefined)?.decision, '');
277
+ }
267
278
  export function turnDurationMs(line) {
268
279
  return line.kind === 'system' && line.subtype === 'turn_duration'
269
280
  ? num(line.raw.durationMs, undefined)
270
281
  : undefined;
271
282
  }
283
+ function phrase(value) {
284
+ let text = str(value, '').trim();
285
+ while (/[.!?:;,]/.test(text.slice(-1)))
286
+ text = text.slice(0, -1).trimEnd();
287
+ return text === '' ? undefined : text;
288
+ }
289
+ export function apiErrorText(line) {
290
+ if (line.kind !== 'system' || line.subtype !== 'api_error')
291
+ return undefined;
292
+ const error = obj(line.raw.error, undefined);
293
+ const cause = phrase(error?.formatted) ?? phrase(error?.message);
294
+ const code = phrase(obj(error?.connection, undefined)?.code);
295
+ const ceiling = num(line.raw.maxRetries, undefined);
296
+ const delayMs = num(line.raw.retryInMs, undefined);
297
+ const retry = [
298
+ num(line.raw.retryAttempt, undefined),
299
+ ceiling === undefined ? undefined : `of ${ceiling}`,
300
+ delayMs === undefined ? undefined : `in ${delayMs} ms`,
301
+ ].filter((part) => part !== undefined);
302
+ const clauses = [];
303
+ const what = cause !== undefined && code !== undefined ? `${cause} (${code})` : (cause ?? code);
304
+ if (what !== undefined)
305
+ clauses.push(`${what}.`);
306
+ if (retry.length > 0)
307
+ clauses.push(`Retry ${retry.join(' ')}.`);
308
+ return clauses.length === 0 ? undefined : clauses.join(' ');
309
+ }
272
310
  const SYNTHETIC_MODEL = '<synthetic>';
273
311
  export function foldSessionEnvelope(lines) {
274
312
  const envelope = {
@@ -1 +1 @@
1
- export const PROJECTOR_VERSION = 16;
1
+ export const PROJECTOR_VERSION = 17;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faithfulalabi/agent-lens",
3
- "version": "0.9.0",
3
+ "version": "0.11.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",