const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const vm = require('node:vm') const ts = require('typescript') const exportsObject = {} vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/src/features/workspace/agentSessionSync.ts'), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText, { exports: exportsObject, console: { warn() {} } }) const { createAgentSessionSync } = exportsObject const message = (id, content = id) => ({ id, role: 'assistant', content }) const plain = value => JSON.parse(JSON.stringify(value)) const deferred = () => { let resolve const promise = new Promise(done => { resolve = done }) return { promise, resolve } } const storage = () => { const data = new Map() return { getItem: key => data.get(key) ?? null, setItem: (key, value) => data.set(key, value), removeItem: key => data.delete(key) } } test('saves replies in order and preserves a newer result arriving during an active save', async () => { const gate = deferred() const calls = [] const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { calls.push(plain(batch)) if (calls.length === 1) await gate.promise } }) sync.enqueue([message('command', 'running')]) await Promise.resolve() sync.enqueue([message('command', 'completed'), message('answer')]) assert.equal(calls.length, 1) gate.resolve() await sync.flush() assert.equal(calls.length, 2) assert.deepEqual(calls[1], [message('command', 'completed'), message('answer')]) }) test('keeps a reply cached before network completion and after failed persistence', async () => { const disk = storage() const sync = createAgentSessionSync({ storage: disk, storageKey: 'student-a', save: async () => { throw Error('offline') } }) sync.enqueue([message('answer')]) assert.deepEqual(JSON.parse(disk.getItem('student-a')), [message('answer')]) await sync.flush() assert.ok(sync.getError()) const batches = [] const reopened = createAgentSessionSync({ storage: disk, storageKey: 'student-a', save: async batch => { batches.push(plain(batch)) } }) assert.deepEqual(plain(reopened.restore([{ id: 'question', role: 'user', content: '?' }])), [ { id: 'question', role: 'user', content: '?' }, message('answer') ]) await reopened.flush() assert.deepEqual(batches, [[message('answer')]]) assert.equal(disk.getItem('student-a'), null) assert.equal(reopened.getError(), null) }) test('does not overwrite a new reply with an older history response', async () => { const sync = createAgentSessionSync({ storageKey: 'test', save: async () => {} }) sync.enqueue([message('answer', 'new answer')]) await sync.flush() assert.deepEqual(plain(sync.restore([message('answer', 'old answer')])), [message('answer', 'new answer')]) }) test('saves only modified messages even when restored history exceeds 100 messages', async () => { const batches = [] const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { batches.push(plain(batch)) } }) const history = Array.from({ length: 150 }, (_, index) => message(`message-${index}`)) sync.restore(history) sync.enqueue([...history, message('new-answer')]) await sync.flush() assert.deepEqual(batches, [[message('new-answer')]]) }) test('chunks a large backlog below the server message-count limit', async () => { const batches = [] const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { batches.push(plain(batch)) } }) sync.enqueue(Array.from({ length: 205 }, (_, index) => message(`message-${index}`))) await sync.flush() assert.deepEqual(batches.map(batch => batch.length), [100, 100, 5]) assert.equal(new Set(batches.flat().map(item => item.id)).size, 205) }) test('leaving the view does not cancel an accepted write', async () => { const gate = deferred() const disk = storage() let saved = false const sync = createAgentSessionSync({ storage: disk, storageKey: 'test', save: async () => { await gate.promise; saved = true } }) const unsubscribe = sync.subscribe(() => {}) sync.enqueue([message('answer')]) unsubscribe() gate.resolve() await sync.flush() assert.equal(saved, true) assert.equal(disk.getItem('test'), null) }) test('archives after outstanding writes complete and clears cached replies', async () => { const gate = deferred() const order = [] const disk = storage() const sync = createAgentSessionSync({ storage: disk, storageKey: 'test', save: async () => { await gate.promise; order.push('saved') } }) sync.enqueue([message('answer')]) const clearing = sync.clear(async () => { order.push('archived') }) gate.resolve() await clearing assert.deepEqual(order, ['saved', 'archived']) assert.deepEqual(plain(sync.restore([])), []) assert.equal(disk.getItem('test'), null) }) test('pending replies stay isolated by student and course scope', async () => { const disk = storage() const first = createAgentSessionSync({ storage: disk, storageKey: 'student-a:course-1', save: async () => { throw Error('offline') } }) first.enqueue([message('private-answer')]) await first.flush() const second = createAgentSessionSync({ storage: disk, storageKey: 'student-b:course-1', save: async () => {} }) assert.deepEqual(plain(second.restore([])), []) }) test('late command updates cannot recreate a conversation while it is being cleared', async () => { const archive = deferred() const calls = [] const sync = createAgentSessionSync({ storageKey: 'test', save: async batch => { calls.push(plain(batch)) } }) sync.enqueue([message('answer')]) await sync.flush() const clearing = sync.clear(() => archive.promise) sync.enqueue([message('answer', 'late command result')]) archive.resolve() await clearing await sync.flush() assert.equal(calls.length, 1) assert.deepEqual(plain(sync.restore([])), []) })