Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
14a943616This file is pinned to the selected release. Download the original to open it in your design tool.
import { mkdtemp, rm, utimes, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { agentFrame } from './agentFrame.js'
import type { RegisteredSession } from './registry.js'
function session(grid: RegisteredSession['grid'], codexHome: RegisteredSession['codexHome'] = null): RegisteredSession {
return {
schemaVersion: 2,
active: true,
sessionId: 's1', engine: 'claude', agentId: 'h1', boundAt: 0, transcriptPath: null,
projectDir: 'tmp', cwd: '/tmp', tmuxPane: '%1', source: null, title: null, model: null,
runtimes: [{ backend: 'tmux', paneId: '%1' }], primaryRuntimeKey: 'tmux/%1',
cliVersion: '2.1.212', processIdentity: null, gateway: null, grid, codexHome,
registeredAt: 1, touchedAt: 1, lastHookAt: 1, lastTranscriptAt: 1,
}
}
const assignment = { baseUrl: 'https://grid.autonomous.ai/grid-abc/relay', model: 'DeepSeek-V4-Flash-0731' }
describe('agentFrame', () => {
it('carries only the owning machine’s cached token snapshot, including a measured zero', async () => {
const context = { selectedModel: null, terminalAvailable: true }
const usage = { totalTokens: 0, updatedAt: '2026-09-22T16:00:00Z' }
expect((await agentFrame(session(null), { ...context, tokenUsage: usage })).tokenUsage).toEqual(usage)
expect((await agentFrame(session(null), context)).tokenUsage).toBeNull()
})
// The regression this file exists for: `agent_synced` was built by a SECOND, hand-maintained copy
// of this shape that never grew a `grid` field. The desktop rebuilds its Agent from every push, so
// each sync reset an agent's grid to null and the "N agents are on an older target" banner came
// back minutes after the user had already moved them onto the grid they picked.
it('carries the grid assignment, so a push cannot erase what a list reported', async () => {
expect(await agentFrame(session(assignment), { selectedModel: null, terminalAvailable: true }))
.toMatchObject({ grid: assignment })
})
it('never carries the grid launch — the key stays in the registry', async () => {
const row = session(assignment)
row.gridLaunch = { networkId: 'grid-abc', networkName: 'Team grid', baseUrl: assignment.baseUrl, apiKey: 'gridkey-SECRET' }
const frame = await agentFrame(row, { selectedModel: null, terminalAvailable: true })
expect(frame).not.toHaveProperty('gridLaunch')
expect(JSON.stringify(frame)).not.toContain('gridkey-SECRET')
})
it('reports no assignment as null rather than omitting the field', async () => {
const frame = await agentFrame(session(null), { selectedModel: null, terminalAvailable: true })
expect(frame).toHaveProperty('grid', null)
})
it('carries the chosen Codex profile folder', async () => {
expect(await agentFrame(session(null, '/Users/x/.codex-personal'), { selectedModel: null, terminalAvailable: true }))
.toMatchObject({ codexHome: '/Users/x/.codex-personal' })
})
it('reports no Codex profile as null rather than omitting the field', async () => {
const frame = await agentFrame(session(null), { selectedModel: null, terminalAvailable: true })
expect(frame).toHaveProperty('codexHome', null)
})
it('passes through the caller-resolved model and terminal availability', async () => {
expect(await agentFrame(session(null), { selectedModel: 'opus', terminalAvailable: false }))
.toMatchObject({ selectedModel: 'opus', terminal: { available: false, primary: 'tmux/%1' } })
})
it('carries the viewer pane’s name with the harness, and null for a plain engine', async () => {
const withViewer = await agentFrame(session(null), {
selectedModel: null, terminalAvailable: true,
dsh: { id: 'autonomous/blender', name: 'Blender', viewerUrl: 'http://127.0.0.1:4100/', viewerName: '3D Viewer', verdict: null },
})
expect(withViewer).toMatchObject({ dsh: 'autonomous/blender', dshName: 'Blender', viewerName: '3D Viewer' })
const older = await agentFrame(session(null), {
selectedModel: null, terminalAvailable: true,
dsh: { id: 'autonomous/blender', name: 'Blender', viewerUrl: null, verdict: null },
})
expect(older).toHaveProperty('viewerName', null)
expect(await agentFrame(session(null), { selectedModel: null, terminalAvailable: true })).toHaveProperty('viewerName', null)
})
// What the desktop's Clone (⌘⇧N) sends back through `agent_create`: the choices only the registry
// row holds. Without them a clone of a Plan-mode reviewer would come up as an auto-mode general.
it('carries the launch choices a clone needs', async () => {
const row = session(null)
row.permissionMode = 'plan'
row.bypassPermission = false
row.agent = 'reviewer'
expect(await agentFrame(row, { selectedModel: null, terminalAvailable: true }))
.toMatchObject({ permissionMode: 'plan', bypassPermission: false, namedAgent: 'reviewer' })
})
it('reports unrecorded launch choices as null rather than omitting them', async () => {
const frame = await agentFrame(session(null), { selectedModel: null, terminalAvailable: true })
expect(frame).toHaveProperty('permissionMode', null)
expect(frame).toHaveProperty('bypassPermission', null)
expect(frame).toHaveProperty('namedAgent', null)
expect(frame).not.toHaveProperty('agent')
})
// The global "last used" order. Null — never an absent key — until an app opens the agent: a push
// without the key would erase an open an earlier frame reported, and a client reads "absent" as a
// daemon too old to keep the stamp.
it('always carries lastOpenedAt: null until an app opens the agent, then the stamp', async () => {
const context = { selectedModel: null, terminalAvailable: true }
expect(await agentFrame(session(null), context)).toHaveProperty('lastOpenedAt', null)
const opened = { ...session(null), lastOpenedAt: Date.UTC(2026, 8, 26, 9, 30) }
const frame = await agentFrame(opened, context)
expect(frame.lastOpenedAt).toBe('2026-09-26T09:30:00.000Z')
// Its own field: opening an agent is not activity, and activity is not an open.
expect(frame.updatedAt).toBe(new Date(1).toISOString())
})
it('reports launch state and defaults legacy agents to ready', async () => {
const legacy = session(null)
expect(await agentFrame(legacy, { selectedModel: null, terminalAvailable: true }))
.toMatchObject({ launch: { state: 'ready' } })
legacy.launch = { state: 'failed', error: 'ENGINE_DID_NOT_START', detail: 'See terminal.' }
expect(await agentFrame(legacy, { selectedModel: null, terminalAvailable: true }))
.toMatchObject({ launch: { state: 'failed', error: 'ENGINE_DID_NOT_START', detail: 'See terminal.' } })
})
})
describe('agentFrame updatedAt', () => {
const context = { selectedModel: null, terminalAvailable: true }
const lastHook = Date.UTC(2026, 8, 17, 7)
// The regression: discovery rewrites the registry row's `touchedAt` on every pass, so an agent with no
// readable transcript was stamped "now" forever and a phone sorting by recency put it on top.
it('follows the last hook without a transcript, never the bookkeeping clock', async () => {
const row = { ...session(null), touchedAt: Date.now(), lastHookAt: lastHook }
expect((await agentFrame(row, context)).updatedAt).toBe('2026-09-17T07:00:00.000Z')
})
it('an unreadable transcript falls back to the last hook too', async () => {
const row = { ...session(null), transcriptPath: '/nonexistent/agent-frame.jsonl', touchedAt: Date.now(), lastHookAt: lastHook }
expect((await agentFrame(row, context)).updatedAt).toBe('2026-09-17T07:00:00.000Z')
})
// A row no hook has ever reached — a bare terminal, an engine still starting, a harness opened from
// the catalog (`resumePendingAgent` writes `lastHookAt: 0`) — answered the epoch, and the desk
// rendered its age as "20719d" and sorted it below everything ever used.
it('falls back to when the agent came into being, never to the epoch', async () => {
const bound = Date.UTC(2026, 8, 20, 9)
const created = Date.UTC(2026, 8, 19, 8)
const never = { ...session(null), touchedAt: Date.now(), lastHookAt: 0 }
expect((await agentFrame({ ...never, boundAt: bound, registeredAt: created }, context)).updatedAt)
.toBe('2026-09-20T09:00:00.000Z')
expect((await agentFrame({ ...never, boundAt: null, registeredAt: created }, context)).updatedAt)
.toBe('2026-09-19T08:00:00.000Z')
// Only stamps the row already carries: a client compares this field to decide whether the agent
// changed, so reading a clock here would redraw the row on every sync and reset its age.
const row = { ...never, boundAt: null, registeredAt: created }
expect((await agentFrame(row, context)).updatedAt).toBe((await agentFrame(row, context)).updatedAt)
})
it('uses actual conversation activity even when an idle transcript was rewritten hours later', async () => {
const dir = await mkdtemp(join(tmpdir(), 'agent-frame-'))
try {
const transcriptPath = join(dir, 'session.jsonl')
const activity = '2026-09-18T01:02:03.000Z'
await writeFile(transcriptPath, [
JSON.stringify({ type: 'assistant', timestamp: activity, message: { content: [] } }),
JSON.stringify({ type: 'ai-title', timestamp: '2026-09-18T10:00:00.000Z', title: 'Playtest' }),
'',
].join('\n'))
const rewritten = new Date('2026-09-18T10:02:03.000Z')
await utimes(transcriptPath, rewritten, rewritten)
const row = { ...session(null), transcriptPath, updatedAt: Date.now(), lastHookAt: lastHook }
expect((await agentFrame(row, context)).updatedAt).toBe(activity)
} finally {
await rm(dir, { recursive: true, force: true })
}
})
})