This commit was merged in pull request #842.
This commit is contained in:
127
packages/mosaic/src/mutator-gate/pi-compaction-lifecycle.spec.ts
Normal file
127
packages/mosaic/src/mutator-gate/pi-compaction-lifecycle.spec.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
type LifecycleRunner = (args: string[]) => boolean;
|
||||
type LifecycleRegister = (api: unknown, runner: LifecycleRunner) => void;
|
||||
type Handler = (event: Record<string, unknown>, ctx: Record<string, unknown>) => unknown;
|
||||
|
||||
const lifecycleModuleUrl = new URL('../../framework/runtime/pi/lease-lifecycle.ts', import.meta.url)
|
||||
.href;
|
||||
const { registerLeaseLifecycleHooks } = (await import(lifecycleModuleUrl)) as {
|
||||
registerLeaseLifecycleHooks: LifecycleRegister;
|
||||
};
|
||||
|
||||
function fakePi() {
|
||||
const handlers = new Map<string, Handler[]>();
|
||||
return {
|
||||
handlers,
|
||||
api: {
|
||||
on(event: string, handler: Handler) {
|
||||
handlers.set(event, [...(handlers.get(event) ?? []), handler]);
|
||||
},
|
||||
},
|
||||
async emit(event: string, value: Record<string, unknown> = {}) {
|
||||
const results = [];
|
||||
for (const handler of handlers.get(event) ?? []) {
|
||||
results.push(await handler(value, {}));
|
||||
}
|
||||
return results;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('Pi compaction and runtime-generation lease lifecycle', () => {
|
||||
test('pre-compaction and first post-compaction context independently revoke', async () => {
|
||||
const pi = fakePi();
|
||||
const calls: string[][] = [];
|
||||
registerLeaseLifecycleHooks(pi.api as never, (args) => {
|
||||
calls.push(args);
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(await pi.emit('session_before_compact', { reason: 'threshold' })).toEqual([undefined]);
|
||||
expect(calls.at(-1)).toEqual([
|
||||
'--runtime',
|
||||
'pi',
|
||||
'--reason',
|
||||
'pi-session-before-compact:threshold',
|
||||
]);
|
||||
|
||||
await pi.emit('session_compact', { reason: 'threshold' });
|
||||
await pi.emit('context', { messages: [] });
|
||||
expect(calls.at(-1)).toEqual([
|
||||
'--runtime',
|
||||
'pi',
|
||||
'--reason',
|
||||
'pi-context-after-compact:threshold',
|
||||
]);
|
||||
const afterFirstContext = calls.length;
|
||||
await pi.emit('context', { messages: [] });
|
||||
expect(calls).toHaveLength(afterFirstContext);
|
||||
});
|
||||
|
||||
test.each(['reload', 'new', 'resume', 'fork'])(
|
||||
'%s bumps generation before reuse',
|
||||
async (reason) => {
|
||||
const pi = fakePi();
|
||||
const calls: string[][] = [];
|
||||
registerLeaseLifecycleHooks(pi.api as never, (args) => {
|
||||
calls.push(args);
|
||||
return true;
|
||||
});
|
||||
|
||||
await pi.emit('session_start', { reason });
|
||||
expect(calls).toEqual([
|
||||
['--runtime', 'pi', '--reason', `pi-session-start:${reason}`, '--bump-generation'],
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test('startup leaves the launcher generation intact and tools locally open', async () => {
|
||||
const pi = fakePi();
|
||||
const calls: string[][] = [];
|
||||
registerLeaseLifecycleHooks(pi.api as never, (args) => {
|
||||
calls.push(args);
|
||||
return true;
|
||||
});
|
||||
|
||||
await pi.emit('session_start', { reason: 'startup' });
|
||||
await pi.emit('session_start');
|
||||
expect(calls).toEqual([]);
|
||||
expect(await pi.emit('tool_call', { toolName: 'bash' })).toEqual([undefined]);
|
||||
});
|
||||
|
||||
test('failed post-compaction revoke blocks tools until context retries successfully', async () => {
|
||||
const pi = fakePi();
|
||||
const outcomes = [false, true];
|
||||
registerLeaseLifecycleHooks(pi.api as never, () => outcomes.shift() ?? true);
|
||||
|
||||
await pi.emit('session_compact');
|
||||
await pi.emit('context');
|
||||
expect(await pi.emit('tool_call', { toolName: 'bash' })).toEqual([
|
||||
{
|
||||
block: true,
|
||||
reason: expect.stringContaining('lease lifecycle revoke failed'),
|
||||
},
|
||||
]);
|
||||
|
||||
await pi.emit('context');
|
||||
expect(await pi.emit('tool_call', { toolName: 'bash' })).toEqual([undefined]);
|
||||
});
|
||||
|
||||
test('failed lifecycle revoke cancels compaction and closes later tool calls', async () => {
|
||||
const pi = fakePi();
|
||||
registerLeaseLifecycleHooks(pi.api as never, () => false);
|
||||
|
||||
const compact = await pi.emit('session_before_compact', { reason: 'manual' });
|
||||
expect(compact).toEqual([{ cancel: true }]);
|
||||
|
||||
await pi.emit('session_start', { reason: 'resume' });
|
||||
const tool = await pi.emit('tool_call', { toolName: 'bash' });
|
||||
expect(tool).toEqual([
|
||||
{
|
||||
block: true,
|
||||
reason: expect.stringContaining('lease lifecycle revoke failed'),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user