| 1 | import test from 'node:test'; |
| 2 | import assert from 'node:assert/strict'; |
| 3 | import { readFileSync } from 'node:fs'; |
| 4 | import { PetEngineTelemetry } from '../dist/core/pet-engine.js'; |
| 5 | import { PetNative } from '../dist/core/pet-native.js'; |
| 6 | import { compilePetTelemetry, encodePetJSONL } from '../dist/core/pet-telemetry.js'; |
| 7 | import { petDemoEvents } from '../dist/core/pet-demo.js'; |
| 8 | |
| 9 | const points = JSON.stringify(readFileSync(new URL('../public/whale-points.tsv', import.meta.url), 'utf8').trim().split('\n').map(row => row.trim().split(/\s+/).map(Number))); |
| 10 | test('typed operation activity carries a kind, never a tool name, and goes stale', () => { |
| 11 | const e = new PetEngineTelemetry(); |
| 12 | for (const [i, kind] of ['reading','searching','editing','executing','testing','browsing','computer'].entries()) { |
| 13 | const at = i * 20_000; |
| 14 | e.observe({event:'turn_started',turn_id:'turn-'+i},at); |
| 15 | e.observe({event:'operation_activity_started',span_id:'private-span-'+i,activity_kind:kind},at); |
| 16 | const activity=e.activity(at+100); |
| 17 | assert.equal(activity.activityKind,kind); assert.equal(activity.authoritativePresence,'working'); |
| 18 | assert.equal(activity.freshness,'fresh'); assert.equal(activity.turnId,'turn-'+i); |
| 19 | assert.deepEqual(Object.keys(activity).sort(),['activeSpans','activityKind','authoritativePresence','cursor','doneEffectId', |
| 20 | 'failedToolAge','freshness','observed','observedAtMs','parallelAgentCount','schemaVersion','sessionId','turnId','turnOutcome']); |
| 21 | assert.equal(JSON.stringify(activity).includes('private-span'),false); |
| 22 | const stale=e.activity(at+12_001); |
| 23 | assert.equal(stale.freshness,'stale'); assert.equal(stale.activityKind,null); assert.deepEqual(stale.activeSpans,[]); |
| 24 | } |
| 25 | // The pre-contract events carried tool names; the boundary refuses them. |
| 26 | assert.throws(() => e.observe({event:'tool_call_started',tool_call_id:'a',tool_name:'read_file'},200_000),/Invalid Engine pet metadata fields/); |
| 27 | assert.throws(() => e.observe({event:'turn_started'},200_000),/Missing Engine turn_id/); |
| 28 | assert.throws(() => e.observe({event:'operation_activity_started',span_id:'a',activity_kind:'shell'},200_000)); |
| 29 | }); |
| 30 | test('only a completed turn is Done; interrupted and failed turns are idle', () => { |
| 31 | for (const [outcome, presence] of [['completed','done'],['interrupted','idle'],['failed','idle']]) { |
| 32 | const e = new PetEngineTelemetry(); |
| 33 | e.observe({event:'turn_started',turn_id:'t'},0); |
| 34 | e.observe({event:'operation_activity_started',span_id:'s',activity_kind:'editing'},10); |
| 35 | e.observe({event:'turn_complete',turn_id:'t',turn_outcome:outcome},20); |
| 36 | const activity=e.activity(30); |
| 37 | assert.equal(activity.authoritativePresence,presence,outcome); |
| 38 | assert.equal(activity.doneEffectId,outcome==='completed'?'t':null); |
| 39 | assert.equal(activity.activityKind,null); assert.deepEqual(activity.activeSpans,[]); |
| 40 | } |
| 41 | const untracked = new PetEngineTelemetry(); |
| 42 | untracked.observe({event:'turn_complete',turn_outcome:'completed'},0); |
| 43 | assert.equal(untracked.activity(10).authoritativePresence,'idle'); |
| 44 | assert.throws(() => untracked.observe({event:'turn_complete',turn_id:null,turn_outcome:'completed'},20)); |
| 45 | // `/purge`, an edit rejection or a mid-turn session switch complete with no |
| 46 | // turn id. An outcome with no turn would fail the Rust projection |
| 47 | // invariant (`turn_outcome` requires `turn_id`) and drop the shared frame. |
| 48 | for (const outcome of ['completed','interrupted','failed']) { |
| 49 | const e = new PetEngineTelemetry(); |
| 50 | e.observe({event:'turn_started',turn_id:'t'},0); |
| 51 | e.observe({event:'turn_complete',turn_outcome:outcome},20); |
| 52 | const activity=e.activity(30); |
| 53 | assert.equal(activity.turnId,null,outcome); assert.equal(activity.turnOutcome,null,outcome); |
| 54 | assert.equal(activity.authoritativePresence,'idle',outcome); assert.equal(activity.doneEffectId,null,outcome); |
| 55 | } |
| 56 | }); |
| 57 | test('a new turn drops spans left open by the previous turn', () => { |
| 58 | const e = new PetEngineTelemetry(); |
| 59 | e.observe({event:'turn_started',turn_id:'t1'},0); |
| 60 | e.observe({event:'operation_activity_started',span_id:'orphan',activity_kind:'editing'},10); |
| 61 | e.observe({event:'turn_started',turn_id:'t2'},20); |
| 62 | const activity=e.activity(30); |
| 63 | assert.equal(activity.turnId,'t2'); assert.equal(activity.activityKind,null); assert.deepEqual(activity.activeSpans,[]); |
| 64 | }); |
| 65 | test('waiting and concurrent activity stay bounded and clear after disconnect', () => { |
| 66 | const pet=new PetNative(points,'','[]',true); |
| 67 | pet.observeEngineBatch(JSON.stringify([{event:'turn_started',turn_id:'t'},{event:'operation_activity_started',span_id:'a',activity_kind:'computer'}, |
| 68 | ...Array.from({length:3},(_,i)=>({event:'agent_spawned',id:'private-'+i}))]),0); |
| 69 | pet.advanceEngine(200,true,false); |
| 70 | const frame=JSON.parse(pet.presentation()); |
| 71 | assert.equal(frame.activity.activityKind,'computer');assert.equal(frame.activity.parallelAgentCount,3); |
| 72 | assert.equal(JSON.stringify(frame.activity).includes('private-'),false); |
| 73 | pet.observeEngine(JSON.stringify({event:'approval_required',id:'private'}),200); |
| 74 | pet.advanceEngine(600,true,true); |
| 75 | const waiting=JSON.parse(pet.presentation()).activity; |
| 76 | assert.equal(waiting.authoritativePresence,'needs_you');assert.equal(waiting.activityKind,null); |
| 77 | pet.observeEngine(JSON.stringify({event:'approval_resolved',id:'private',outcome:'denied'}),700); |
| 78 | pet.advanceEngine(800,true,false); |
| 79 | assert.equal(JSON.parse(pet.presentation()).activity.authoritativePresence,'working'); |
| 80 | const before=pet.recording(true);pet.presentation();assert.equal(pet.recording(true),before); |
| 81 | pet.disconnectEngine(); |
| 82 | const gone=JSON.parse(pet.presentation()).activity; |
| 83 | assert.equal(gone.observed,false);assert.equal(gone.freshness,'missing'); |
| 84 | }); |
| 85 | test('the incremental bucket range uses the same measured projection as full replay', () => { |
| 86 | const events = petDemoEvents(), full = compilePetTelemetry(events, 80_000); |
| 87 | for (let i = 0; i < full.length; i++) assert.deepEqual(compilePetTelemetry(events, 80_000, i)[0], full[i]); |
| 88 | }); |
| 89 | test('Engine pulses expire; a late failed completion tears at receipt time without rewriting history', () => { |
| 90 | const engine = new PetEngineTelemetry(); |
| 91 | engine.observe({ event: 'operation_activity_started', span_id: 'a', activity_kind: 'executing' }, 0); |
| 92 | engine.observe({ event: 'tool_call_heartbeat' }, 300); |
| 93 | const first = engine.bucket(0); |
| 94 | assert.equal(first.channel, 'code'); assert.equal(first.activeMs[3], 300); |
| 95 | assert.equal(engine.bucket(3).observed, 0); |
| 96 | engine.observe({ event: 'operation_activity_completed', span_id: 'a', activity_kind: 'executing', outcome: 'failed' }, 5900); |
| 97 | assert.equal(engine.bucket(13).observed, 0); |
| 98 | assert.equal(engine.bucket(14).channel, 'error'); assert.equal(engine.bucket(14).errors, 1); |
| 99 | assert.deepEqual(engine.bucket(0), first); |
| 100 | }); |
| 101 | test('an authoritative waiting request escalates, accepted tape replays, and silence stays unknown', () => { |
| 102 | const pet = new PetNative(points, '', '[]', true); |
| 103 | pet.observeEngine(JSON.stringify({ event: 'approval_required', id: 'permission' }), 100); |
| 104 | for (let ms = 400; ms <= 30_000; ms += 400) pet.advanceEngine(ms, true, true); |
| 105 | assert.equal(JSON.parse(pet.snapshot()).needs, 'call'); |
| 106 | assert.equal(JSON.parse(pet.snapshot()).state.channel, 'human'); |
| 107 | const saved = JSON.parse(pet.recording()), replay = new PetNative(points, encodePetJSONL(saved.tape), JSON.stringify(saved.interactions)); |
| 108 | for (let i = 0; i < 900; i++) replay.step(1 / 30, true); |
| 109 | assert.deepEqual(JSON.parse(replay.snapshot()).state, JSON.parse(pet.snapshot()).state); |
| 110 | assert.equal(JSON.parse(replay.snapshot()).digest, JSON.parse(pet.snapshot()).digest); |
| 111 | pet.observeEngine(JSON.stringify({ event: 'turn_complete', turn_outcome: 'interrupted' }), 30_100); |
| 112 | pet.advanceEngine(32_000, true, false); |
| 113 | assert.equal(JSON.parse(pet.snapshot()).state.observed, 0); |
| 114 | }); |
| 115 | test('Engine worker boundary rejects payloads, invalid clocks and oversized active sets', () => { |
| 116 | const e = new PetEngineTelemetry(); |
| 117 | assert.throws(() => e.observe({ event: 'response_delta', index: 0, delta: 'private' }, 0)); |
| 118 | assert.throws(() => e.observe({ event: 'response_delta', index: 0, channel: 'fabricated' }, 0)); |
| 119 | e.observe({ event: 'thinking_started', index: 0 }, 100); |
| 120 | assert.throws(() => e.observe({ event: 'thinking_complete', index: 0 }, 90)); |
| 121 | for (let i = 1; i < 256; i++) e.observe({ event: 'thinking_started', index: i }, 100); |
| 122 | assert.throws(() => e.observe({ event: 'thinking_started', index: 257 }, 100)); |
| 123 | }); |
| 124 | |
| 125 | test('failed shared metadata batches accept no partial spans or private input', () => { |
| 126 | const control = new PetNative(points, '', '[]', true), pet = new PetNative(points, '', '[]', true); |
| 127 | assert.throws(() => pet.observeEngineBatch(JSON.stringify([{event:'thinking_started',index:1},{event:'response_delta',index:1,content:'PRIVATE'}]),0)); |
| 128 | pet.advanceEngine(400,true,false); control.advanceEngine(400,true,false); |
| 129 | assert.deepEqual(JSON.parse(pet.recording(true)),JSON.parse(control.recording(true))); |
| 130 | pet.observeEngineBatch(JSON.stringify([{event:'thinking_started',index:1},{event:'response_delta',index:1,channel:'reasoning'}]),400); |
| 131 | pet.advanceEngine(800,true,false); |
| 132 | assert.equal(JSON.parse(pet.snapshot()).state.channel,'reasoning'); |
| 133 | }); |
| 134 | |
| 135 | test('successive shared batches preserve active and waiting coverage exactly', () => { |
| 136 | let shared = new PetEngineTelemetry(); |
| 137 | const direct = new PetEngineTelemetry(); |
| 138 | for (const [at, event, waiting] of [ |
| 139 | [0, {event:'operation_activity_started', span_id:'build', activity_kind:'executing'}, false], |
| 140 | [300, {event:'tool_call_heartbeat'}, false], |
| 141 | [600, {event:'tool_call_heartbeat'}, false], |
| 142 | [800, {event:'operation_activity_completed', span_id:'build', activity_kind:'executing', outcome:'succeeded'}, false], |
| 143 | [900, {event:'approval_required', id:'permission'}, true], |
| 144 | [1200, {event:'agent_spawned', id:'worker'}, true], |
| 145 | [1500, {event:'agent_progress', id:'worker', worker_status:'running'}, true], |
| 146 | ]) { |
| 147 | shared = shared.clone(); |
| 148 | for (const engine of [shared, direct]) { |
| 149 | engine.observe(event, at); |
| 150 | engine.confirmWaiting(at, waiting); |
| 151 | } |
| 152 | for (let bucket=0; bucket<=Math.floor(at/400); bucket++) |
| 153 | assert.deepEqual(shared.bucket(bucket), direct.bucket(bucket)); |
| 154 | } |
| 155 | }); |
| 156 | |
| 157 | test('shared presentation and still projections cannot change physics, clock, journal or score', () => { |
| 158 | const pet = new PetNative(points, '', '[]', true), control = new PetNative(points, '', '[]', true); |
| 159 | for(let i=1;i<=120;i++) { |
| 160 | pet.advanceEngine(i*1000/30,true,false); control.advanceEngine(i*1000/30,true,false); |
| 161 | const before=pet.recording(true), frame=JSON.parse(pet.presentation()); |
| 162 | assert.equal(frame.points.length,980);assert.equal(frame.still.points.length,980); |
| 163 | for(let view=0;view<3;view++) pet.presentation(); |
| 164 | assert.equal(pet.recording(true),before); |
| 165 | } |
| 166 | assert.deepEqual(JSON.parse(pet.recording(true)),JSON.parse(control.recording(true))); |
| 167 | }); |
| 168 |