Files
NetMesh/infrastructure/ai/harness/sessionState.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

136 lines
5.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { SessionStateStore } from './sessionState.ts';
test('SessionStateStore tracks terminal commands and reinjection text', () => {
const store = new SessionStateStore();
store.mergeFromUserGoal('chat-1', 'Fix nginx upstream timeout');
store.updateFromToolResult(
'chat-1',
'terminal_execute',
{ sessionId: 'sess-1', command: 'tail -n 100 /var/log/nginx/error.log' },
'upstream timed out',
false,
);
const text = store.toReinjectionText('chat-1');
assert.ok(text?.includes('Fix nginx upstream timeout'));
assert.ok(text?.includes('sess-1'));
assert.ok(text?.includes('tail -n 100'));
});
test('SessionStateStore records tool errors as blockers', () => {
const store = new SessionStateStore();
store.updateFromToolResult(
'chat-1',
'terminal_execute',
{ sessionId: 'sess-1', command: 'systemctl restart nginx' },
'{ "error": "Job failed" }',
true,
);
const text = store.toReinjectionText('chat-1');
assert.ok(text?.includes('Open blockers'));
});
test('SessionStateStore restores active background jobs and poll cursors', () => {
const store = new SessionStateStore();
store.updateFromToolResult(
'chat-1',
'terminal_start',
{ sessionId: 'sess-1', command: 'npm run dev' },
JSON.stringify({ ok: true, jobId: 'job-1', status: 'running', nextOffset: 0 }),
);
store.updateFromToolResult(
'chat-1',
'terminal_poll',
{ jobId: 'job-1', offset: 0 },
JSON.stringify({ ok: true, jobId: 'job-1', status: 'running', nextOffset: 420 }),
);
const state = store.get('chat-1');
assert.equal(state.version, 1);
assert.equal(state.activeJobs['job-1'].nextOffset, 420);
const text = store.toReinjectionText('chat-1') ?? '';
assert.match(text, /job-1/);
assert.match(text, /offset=420/);
assert.match(text, /poll the existing job/i);
assert.match(text, /do not restart/i);
assert.match(text, /unverified after compaction/i);
});
test('SessionStateStore drops a remembered job after poll reports it missing', () => {
const store = new SessionStateStore();
store.updateFromToolResult(
'chat-1', 'terminal_start', { sessionId: 'sess-1', command: 'npm run dev' },
JSON.stringify({ jobId: 'job-lost', status: 'running' }), false,
);
store.updateFromToolResult(
'chat-1', 'terminal_poll', { jobId: 'job-lost', offset: 0 },
JSON.stringify({ error: 'Job not found' }), true,
);
assert.equal(store.get('chat-1').activeJobs['job-lost'], undefined);
assert.doesNotMatch(store.toReinjectionText('chat-1') ?? '', /Remembered terminal jobs/);
});
test('SessionStateStore preserves a running job after a transient poll error', () => {
const store = new SessionStateStore();
store.updateFromToolResult(
'chat-1', 'terminal_start', { sessionId: 'sess-1', command: 'npm run dev' },
JSON.stringify({ jobId: 'job-running', status: 'running', nextOffset: 420 }), false,
);
store.updateFromToolResult(
'chat-1', 'terminal_poll', { jobId: 'job-running', offset: 420 },
JSON.stringify({ error: 'temporary IPC timeout' }), true,
);
const job = store.get('chat-1').activeJobs['job-running'];
assert.equal(job.status, 'unverified');
assert.equal(job.nextOffset, 420);
assert.match(store.toReinjectionText('chat-1') ?? '', /job-running/);
assert.match(store.toReinjectionText('chat-1') ?? '', /do not restart/i);
});
test('SessionStateStore drops a job after poll confirms cancellation', () => {
const store = new SessionStateStore();
store.updateFromToolResult(
'chat-1', 'terminal_start', { sessionId: 'sess-1', command: 'npm run dev' },
JSON.stringify({ jobId: 'job-cancelled', status: 'running' }), false,
);
store.updateFromToolResult(
'chat-1', 'terminal_poll', { jobId: 'job-cancelled', offset: 0 },
JSON.stringify({ jobId: 'job-cancelled', status: 'cancelled', error: 'Cancelled' }), true,
);
assert.equal(store.get('chat-1').activeJobs['job-cancelled'], undefined);
});
test('SessionStateStore records the last terminal screen range read', () => {
const store = new SessionStateStore();
store.updateFromToolResult(
'chat-1',
'terminal_read_context',
{ sessionId: 'sess-1', range: 'tail', startLine: 80, maxLines: 20 },
JSON.stringify({ ok: true, sessionId: 'sess-1', startLine: 80, endLine: 99 }),
);
assert.deepEqual(store.get('chat-1').terminalReadCursors['sess-1'], {
range: 'tail',
startLine: 80,
endLine: 99,
});
});
test('SessionStateStore reinjects edited files and unfinished plan items', () => {
const store = new SessionStateStore();
store.mergeFileChanges('chat-1', ['/repo/src/a.ts', '/repo/src/b.ts']);
store.mergePlan('chat-1', [
{ text: 'inspect failure', completed: true },
{ text: 'run regression tests', completed: false },
]);
const text = store.toReinjectionText('chat-1') ?? '';
assert.match(text, /\/repo\/src\/a\.ts/);
assert.match(text, /\[done\] inspect failure/);
assert.match(text, /\[todo\] run regression tests/);
});