mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
fix(agent): cap inspection cursors per owner and session
Cursors shared one global FIFO of 1,024 and were never retired on use, so one agent polling every second evicted every other caller's cursors after about 17 minutes. Each owner and session now keeps its newest 8 cursors, older ones retired first, under the same global backstop; the cursor just used stays among the newest, so a retry of the last call still works.
This commit is contained in:
1 parent
a29192a248
commit
ecaddb40ae
4 files changed
+46
-7
No files matched your search
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, expect, it } from 'vitest';
|
||||
import { mkdtemp, writeFile, appendFile, rename, rm } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { InspectionReader } from '../src/agent-inspection.js';
|
||||
import { INSPECT_CURSORS_PER_SCOPE, InspectionReader } from '../src/agent-inspection.js';
|
||||
let dir: string;
|
||||
let file: string;
|
||||
let reader: InspectionReader;
|
||||
@@ -150,6 +150,28 @@ it('keeps cursor capacity bounded across concurrent reads', async () => {
|
||||
const resumed = await Promise.allSettled(results.map((result) => reader.read(file, scope, 100, result.cursor)));
|
||||
expect(resumed.filter((result) => result.status === 'fulfilled').length).toBeLessThanOrEqual(2);
|
||||
});
|
||||
it('caps cursors per owner and session, so one polling scope cannot evict another', async () => {
|
||||
await writeFile(file, '{}\n');
|
||||
const other = { ...scope, owner: 'bob', sessionId: 't' };
|
||||
const bobs = await reader.read(file, other, 100);
|
||||
// Alice polls every second for well past the old global FIFO of 1,024.
|
||||
let last = await reader.read(file, scope, 100);
|
||||
for (let i = 0; i < 1100; i++) last = await reader.read(file, scope, 100, last.cursor);
|
||||
// Bob's cursor survives, and so does a retry of Alice's last call.
|
||||
await expect(reader.read(file, other, 100, bobs.cursor)).resolves.toMatchObject({ evidence: '' });
|
||||
await expect(reader.read(file, scope, 100, last.cursor)).resolves.toMatchObject({ evidence: '' });
|
||||
// Alice keeps only her newest cursors: the oldest of one more than the cap is retired.
|
||||
const issued: string[] = [];
|
||||
for (let i = 0; i <= INSPECT_CURSORS_PER_SCOPE; i++) issued.push((await reader.read(file, scope, 100)).cursor);
|
||||
await expect(reader.read(file, scope, 100, issued[0])).rejects.toThrow(/cursor/i);
|
||||
await expect(reader.read(file, scope, 100, issued[issued.length - 1])).resolves.toBeDefined();
|
||||
// The cap counts owner and session: a new pane generation of Alice's session shares
|
||||
// it, Bob's session does not.
|
||||
const nextPane = { ...scope, generation: 'pane-2' };
|
||||
for (let i = 0; i < INSPECT_CURSORS_PER_SCOPE; i++) await reader.read(file, nextPane, 100);
|
||||
await expect(reader.read(file, scope, 100, issued[issued.length - 1])).rejects.toThrow(/cursor/i);
|
||||
await expect(reader.read(file, other, 100, bobs.cursor)).resolves.toBeDefined();
|
||||
});
|
||||
it('keeps an incomplete UTF8 sequence pending until its JSONL record is complete', async () => {
|
||||
const row = Buffer.from('{"text":"😀"}\n');
|
||||
const split = row.indexOf(Buffer.from('😀')) + 2;
|
||||
|
||||
Reference in new issue
Block a user