Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
6d26a1aa9This file is pinned to the selected release. Download the original to open it in your design tool.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { closeSync, existsSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import {
ts, sid, preview, installTimestampedConsole, logFrame,
adoptLegacyLog, prepareLogFile, trimLogFile, LOG_MAX_BYTES,
} from './log.js'
describe('log helpers', () => {
afterEach(() => vi.restoreAllMocks())
it('formats ts() as YYYY-MM-DD HH:MM:SS.mmm', () => {
expect(ts(new Date(2026, 6, 24, 9, 5, 3, 7))).toBe('2026-07-24 09:05:03.007')
expect(ts()).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/)
})
it('shortens session ids', () => {
expect(sid('69aaaa14-89e9-7e21-871a-ee6ea943fc95')).toBe('69aaaa14')
})
describe('logFrame', () => {
const lastLine = (spy: ReturnType<typeof vi.spyOn>) => String(spy.mock.calls.at(-1)?.[0] ?? '')
it('prints direction, audience, type and the opaque ids that make a frame identifiable', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {})
logFrame('→', 'web', {
type: 'agent_synced',
payload: {
agentId: '26f49435-71e8-4461-ac59-65c0e854b374',
agent: { engine: 'claude', selectedModel: 'runtime-v1:x:claude:anthropic%2Fclaude-sonnet-5@xhigh' },
},
})
const line = lastLine(spy)
expect(line).toContain('[frame] → web agent_synced')
expect(line).toContain('agentId=26f49435')
expect(line).toContain('engine=claude')
expect(line).toContain('agent.selectedModel=runtime-v1:x:claude:anthropic%2Fclaude-sonnet-5@xhigh')
})
it('never prints a payload body — this log is the one place frames are in the clear', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {})
logFrame('←', 'conn:74dab280', {
type: 'message',
payload: {
sessionId: '9e9f7954-1111-2222-3333-444455556666',
text: 'my private prompt about the acquisition',
token: 'sk-or-v1-secret',
images: ['data:image/png;base64,AAAA'],
},
})
const line = lastLine(spy)
expect(line).toContain('sessionId=9e9f7954')
expect(line).not.toContain('private prompt')
expect(line).not.toContain('sk-or-v1-secret')
expect(line).not.toContain('base64')
})
it('summarizes a catalog by size, not by contents', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {})
logFrame('→', 'conn:1', { type: 'models_list_result', payload: { models: [{ id: 'a' }, { id: 'b' }] } })
expect(lastLine(spy)).toContain('models=2')
logFrame('→', 'conn:1', { type: 'models_list_result', payload: { models: [] } })
expect(lastLine(spy)).toContain('models=0')
})
})
it('flattens whitespace and caps preview with an ellipsis', () => {
expect(preview(' hello\n\n world ')).toBe('hello world')
expect(preview('abcdef', 3)).toBe('abc…')
expect(preview('')).toBe('')
})
it('prepends a timestamp yet preserves the original args (waitForReady marker intact)', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {})
// Capture the wrapper by re-reading console.log AFTER install.
installTimestampedConsole()
console.log('[backend] connected → wss://x')
const line = spy.mock.calls.at(-1)!.join(' ')
expect(line).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} \[backend\] connected/)
expect(line).toContain('[backend] connected') // substring the self-update readiness check scans
})
})
describe('log file cap', () => {
let dir: string
let file: string
let legacy: string
const line = (i: number): string => `line-${String(i).padStart(6, '0')} ${'x'.repeat(80)}\n`
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'adapter-log-'))
file = join(dir, 'harness.log')
legacy = join(dir, 'machine.log')
})
afterEach(() => rmSync(dir, { recursive: true, force: true }))
const fill = (bytes: number): number => {
let out = ''
let i = 0
while (out.length < bytes) out += line(i++)
writeFileSync(file, out)
return i
}
it('leaves a file under the cap alone', () => {
fill(2_000)
const before = readFileSync(file, 'utf-8')
expect(trimLogFile(file, 10_000)).toBe(false)
expect(readFileSync(file, 'utf-8')).toBe(before)
})
it('is a no-op when there is no log yet', () => {
expect(trimLogFile(join(dir, 'nope.log'))).toBe(false)
})
it('drops the OLDEST half and keeps the newest lines, at a line boundary', () => {
const cap = 40_000
const written = fill(cap * 2)
expect(trimLogFile(file, cap)).toBe(true)
const after = readFileSync(file, 'utf-8')
expect(statSync(file).size).toBeLessThanOrEqual(cap)
// Newest line survived, oldest is gone.
expect(after).toContain(`line-${String(written - 1).padStart(6, '0')}`)
expect(after).not.toContain('line-000000')
// First line is the trim marker; the second is a WHOLE line, not a fragment.
const lines = after.split('\n')
expect(lines[0]).toMatch(/\[log\] trimmed — dropped the oldest \d+ MB \(cap 0 MB\)$/)
expect(lines[1]).toMatch(/^line-\d{6} x+$/)
})
it('keeps an O_APPEND writer landing at the end — no sparse hole', () => {
// The daemon holds the log as its stdout fd across a trim; this is the property that makes an
// in-place rewrite safe (a non-append fd would resume at its old offset and leave NUL bytes).
const cap = 40_000
fill(cap * 2)
const fd = openSync(file, 'a')
try {
expect(trimLogFile(file, cap)).toBe(true)
writeSync(fd, 'after-the-trim\n')
} finally {
closeSync(fd)
}
const after = readFileSync(file)
expect(after.toString('utf-8').trimEnd().endsWith('after-the-trim')).toBe(true)
expect(after.includes(0)).toBe(false)
expect(after.length).toBeLessThanOrEqual(cap)
})
it('caps at 10 MB by default', () => {
expect(LOG_MAX_BYTES).toBe(10 * 1024 * 1024)
})
it('adopts a legacy log by RENAME, so an open fd keeps writing to the same file', () => {
writeFileSync(legacy, 'old-history\n')
const inode = statSync(legacy).ino
const fd = openSync(legacy, 'a') // stands in for a daemon started before the rename
try {
adoptLegacyLog(file, legacy)
writeSync(fd, 'written-after-rename\n')
} finally {
closeSync(fd)
}
expect(existsSync(legacy)).toBe(false)
expect(statSync(file).ino).toBe(inode)
expect(readFileSync(file, 'utf-8')).toBe('old-history\nwritten-after-rename\n')
})
it('never clobbers an existing log, and shrugs off a missing legacy one', () => {
writeFileSync(file, 'current\n')
writeFileSync(legacy, 'stale\n')
adoptLegacyLog(file, legacy)
expect(readFileSync(file, 'utf-8')).toBe('current\n')
expect(readFileSync(legacy, 'utf-8')).toBe('stale\n') // left for the user to delete
rmSync(legacy)
expect(() => adoptLegacyLog(file, legacy)).not.toThrow()
})
it('prepareLogFile adopts then trims in one pass', () => {
let out = ''
while (out.length < 30_000) out += line(out.length)
writeFileSync(legacy, out)
prepareLogFile(file, legacy, 10_000)
expect(existsSync(legacy)).toBe(false)
expect(statSync(file).size).toBeLessThanOrEqual(10_000)
})
})