@monotykamary/pi-tps 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/package.json +9 -4
- package/.github/FUNDING.yml +0 -4
- package/.github/workflows/test.yml +0 -57
- package/.pi/autoresearch/session-id +0 -1
- package/.pi/fabric/mesh/actors/actors.json +0 -4
- package/.prettierrc +0 -7
- package/commitlint.config.cjs +0 -1
- package/extensions/pi-tps/__tests__/cost-rate.test.ts +0 -503
- package/extensions/pi-tps/__tests__/dynamic-tps-cap.test.ts +0 -390
- package/extensions/pi-tps/__tests__/export-command.test.ts +0 -307
- package/extensions/pi-tps/__tests__/extension-setup.test.ts +0 -41
- package/extensions/pi-tps/__tests__/format-duration.test.ts +0 -83
- package/extensions/pi-tps/__tests__/helpers.ts +0 -177
- package/extensions/pi-tps/__tests__/precision-timing.test.ts +0 -701
- package/extensions/pi-tps/__tests__/rehydration.test.ts +0 -282
- package/extensions/pi-tps/__tests__/session-export.test.ts +0 -204
- package/extensions/pi-tps/__tests__/stall-detection.test.ts +0 -209
- package/extensions/pi-tps/__tests__/stall-reduction.test.ts +0 -139
- package/extensions/pi-tps/__tests__/telemetry-flow.test.ts +0 -654
- package/extensions/pi-tps/__tests__/volume-gate.test.ts +0 -372
- package/knip.json +0 -10
- package/npm-shrinkwrap.json +0 -6900
- package/pnpm-workspace.yaml +0 -9
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -15
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { createTestFixture, activateExtension } from './helpers';
|
|
3
|
-
|
|
4
|
-
describe('pi-tps extension — setup', () => {
|
|
5
|
-
let fixture: ReturnType<typeof createTestFixture>;
|
|
6
|
-
|
|
7
|
-
beforeEach(async () => {
|
|
8
|
-
fixture = createTestFixture();
|
|
9
|
-
await activateExtension(fixture);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
vi.restoreAllMocks();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should register all required event handlers and commands', () => {
|
|
17
|
-
const { mockPi, registerCommandSpy } = fixture;
|
|
18
|
-
|
|
19
|
-
expect(mockPi.on).toHaveBeenCalledWith('session_start', expect.any(Function));
|
|
20
|
-
expect(mockPi.on).toHaveBeenCalledWith('session_tree', expect.any(Function));
|
|
21
|
-
expect(mockPi.on).toHaveBeenCalledWith('turn_start', expect.any(Function));
|
|
22
|
-
expect(mockPi.on).toHaveBeenCalledWith('message_start', expect.any(Function));
|
|
23
|
-
expect(mockPi.on).toHaveBeenCalledWith('message_update', expect.any(Function));
|
|
24
|
-
expect(mockPi.on).toHaveBeenCalledWith('message_end', expect.any(Function));
|
|
25
|
-
expect(mockPi.on).toHaveBeenCalledWith('turn_end', expect.any(Function));
|
|
26
|
-
expect(registerCommandSpy).toHaveBeenCalledWith(
|
|
27
|
-
'tps-export',
|
|
28
|
-
expect.objectContaining({
|
|
29
|
-
description: expect.any(String),
|
|
30
|
-
handler: expect.any(Function),
|
|
31
|
-
})
|
|
32
|
-
);
|
|
33
|
-
expect(registerCommandSpy).toHaveBeenCalledWith(
|
|
34
|
-
'session-export',
|
|
35
|
-
expect.objectContaining({
|
|
36
|
-
description: expect.any(String),
|
|
37
|
-
handler: expect.any(Function),
|
|
38
|
-
})
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
describe('formatDuration', () => {
|
|
4
|
-
const importFormatDuration = async () => {
|
|
5
|
-
const mod = await import('../index.js');
|
|
6
|
-
return mod.formatDuration;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
it('formats sub-minute durations with 1 decimal', async () => {
|
|
10
|
-
const formatDuration = await importFormatDuration();
|
|
11
|
-
expect(formatDuration(0.8)).toBe('0.8s');
|
|
12
|
-
expect(formatDuration(1.0)).toBe('1.0s');
|
|
13
|
-
expect(formatDuration(2.3)).toBe('2.3s');
|
|
14
|
-
expect(formatDuration(9.9)).toBe('9.9s');
|
|
15
|
-
expect(formatDuration(10.5)).toBe('10.5s');
|
|
16
|
-
expect(formatDuration(45.0)).toBe('45.0s');
|
|
17
|
-
expect(formatDuration(59.4)).toBe('59.4s');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('formats minute+ durations as minutes and seconds', async () => {
|
|
21
|
-
const formatDuration = await importFormatDuration();
|
|
22
|
-
expect(formatDuration(60)).toBe('1m 0s');
|
|
23
|
-
expect(formatDuration(90)).toBe('1m 30s');
|
|
24
|
-
expect(formatDuration(300)).toBe('5m 0s');
|
|
25
|
-
expect(formatDuration(323)).toBe('5m 23s');
|
|
26
|
-
expect(formatDuration(3599)).toBe('59m 59s');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('formats hour+ durations as hours and minutes', async () => {
|
|
30
|
-
const formatDuration = await importFormatDuration();
|
|
31
|
-
expect(formatDuration(3600)).toBe('1h 0m');
|
|
32
|
-
expect(formatDuration(4500)).toBe('1h 15m');
|
|
33
|
-
expect(formatDuration(7200)).toBe('2h 0m');
|
|
34
|
-
expect(formatDuration(86399)).toBe('23h 59m');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('formats day+ durations as days and hours', async () => {
|
|
38
|
-
const formatDuration = await importFormatDuration();
|
|
39
|
-
expect(formatDuration(86400)).toBe('1d 0h');
|
|
40
|
-
expect(formatDuration(129600)).toBe('1d 12h');
|
|
41
|
-
expect(formatDuration(172800)).toBe('2d 0h');
|
|
42
|
-
expect(formatDuration(302400)).toBe('3d 12h');
|
|
43
|
-
expect(formatDuration(518400)).toBe('6d 0h');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('formats week+ durations as weeks and days', async () => {
|
|
47
|
-
const formatDuration = await importFormatDuration();
|
|
48
|
-
expect(formatDuration(604800)).toBe('1w 0d');
|
|
49
|
-
expect(formatDuration(907200)).toBe('1w 3d');
|
|
50
|
-
expect(formatDuration(1209600)).toBe('2w 0d');
|
|
51
|
-
expect(formatDuration(1814400)).toBe('3w 0d');
|
|
52
|
-
expect(formatDuration(2419200)).toBe('4w 0d');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('formats month+ durations as months and days', async () => {
|
|
56
|
-
const formatDuration = await importFormatDuration();
|
|
57
|
-
expect(formatDuration(2592000)).toBe('1mo 0d');
|
|
58
|
-
expect(formatDuration(2851200)).toBe('1mo 3d');
|
|
59
|
-
expect(formatDuration(5184000)).toBe('2mo 0d');
|
|
60
|
-
expect(formatDuration(7776000)).toBe('3mo 0d');
|
|
61
|
-
expect(formatDuration(9504000)).toBe('3mo 2w');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('formats year+ durations as years and days', async () => {
|
|
65
|
-
const formatDuration = await importFormatDuration();
|
|
66
|
-
expect(formatDuration(31536000)).toBe('1y 0d');
|
|
67
|
-
expect(formatDuration(31622400)).toBe('1y 1d');
|
|
68
|
-
expect(formatDuration(34128000)).toBe('1y 1mo');
|
|
69
|
-
expect(formatDuration(63072000)).toBe('2y 0d');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('handles large multi-year durations', async () => {
|
|
73
|
-
const formatDuration = await importFormatDuration();
|
|
74
|
-
expect(formatDuration(15552000)).toBe('6mo 0d');
|
|
75
|
-
expect(formatDuration(315360000)).toBe('10y 0d');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('rounds correctly for multi-unit durations', async () => {
|
|
79
|
-
const formatDuration = await importFormatDuration();
|
|
80
|
-
expect(formatDuration(89.9)).toBe('1m 30s');
|
|
81
|
-
expect(formatDuration(90.1)).toBe('1m 30s');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { vi } from 'vitest';
|
|
2
|
-
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
3
|
-
import type { AssistantMessage } from '@earendil-works/pi-ai';
|
|
4
|
-
|
|
5
|
-
// ─── Event types (mirrors extension/index.ts — not exported from pi's public API) ────
|
|
6
|
-
|
|
7
|
-
export interface TurnStartEvent {
|
|
8
|
-
type: 'turn_start';
|
|
9
|
-
turnIndex: number;
|
|
10
|
-
timestamp: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface TurnEndEvent {
|
|
14
|
-
type: 'turn_end';
|
|
15
|
-
turnIndex: number;
|
|
16
|
-
message: unknown;
|
|
17
|
-
toolResults: unknown[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface MessageStartEvent {
|
|
21
|
-
type: 'message_start';
|
|
22
|
-
message: unknown;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface MessageUpdateEvent {
|
|
26
|
-
type: 'message_update';
|
|
27
|
-
message: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface MessageEndEvent {
|
|
31
|
-
type: 'message_end';
|
|
32
|
-
message: unknown;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
/** Advance real time by ms (for integration-style timing tests) */
|
|
38
|
-
export const tick = (ms = 10) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
-
|
|
40
|
-
/** Create a standard AssistantMessage with overridable fields */
|
|
41
|
-
export function makeAssistantMessage(
|
|
42
|
-
overrides: { output?: number; input?: number; provider?: string; model?: string } = {}
|
|
43
|
-
): AssistantMessage {
|
|
44
|
-
const { output = 20, input = 10, provider = 'openai', model = 'gpt-4' } = overrides;
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
role: 'assistant',
|
|
48
|
-
content: [{ type: 'text' as const, text: 'Hello' }],
|
|
49
|
-
api: 'openai-completions',
|
|
50
|
-
provider,
|
|
51
|
-
model,
|
|
52
|
-
usage: {
|
|
53
|
-
input,
|
|
54
|
-
output,
|
|
55
|
-
cacheRead: 0,
|
|
56
|
-
cacheWrite: 0,
|
|
57
|
-
totalTokens: input + output,
|
|
58
|
-
cost: {
|
|
59
|
-
input: 0.001,
|
|
60
|
-
output: 0.002,
|
|
61
|
-
cacheRead: 0,
|
|
62
|
-
cacheWrite: 0,
|
|
63
|
-
total: 0.003,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
stopReason: 'stop',
|
|
67
|
-
timestamp: Date.now(),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ─── Mock setup ──────────────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
export interface TestFixture {
|
|
74
|
-
mockPi: Partial<ExtensionAPI>;
|
|
75
|
-
handlers: Record<string, (...args: unknown[]) => void>;
|
|
76
|
-
commands: Record<string, { handler: (args: string, ctx: any) => Promise<void> }>;
|
|
77
|
-
notifySpy: ReturnType<typeof vi.fn>;
|
|
78
|
-
appendEntrySpy: ReturnType<typeof vi.fn>;
|
|
79
|
-
eventsEmitSpy: ReturnType<typeof vi.fn>;
|
|
80
|
-
/** Emit an event on the mock event bus, dispatching to registered listeners (and the emit spy). */
|
|
81
|
-
emitEvent: (event: string, payload: unknown) => void;
|
|
82
|
-
registerCommandSpy: ReturnType<typeof vi.fn>;
|
|
83
|
-
mockEntries: Array<{ type?: string; role?: string; customType?: string; data?: unknown }>;
|
|
84
|
-
mockCtx: ExtensionContext;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Create a fresh set of mocks for one test.
|
|
89
|
-
* Call `activateExtension()` on the result to wire up the extension.
|
|
90
|
-
*/
|
|
91
|
-
export function createTestFixture(): TestFixture {
|
|
92
|
-
const handlers: Record<string, (...args: unknown[]) => void> = {};
|
|
93
|
-
const commands: Record<string, { handler: (args: string, ctx: any) => Promise<void> }> = {};
|
|
94
|
-
const notifySpy = vi.fn();
|
|
95
|
-
const appendEntrySpy = vi.fn();
|
|
96
|
-
const registerCommandSpy = vi.fn((name: string, options: any) => {
|
|
97
|
-
commands[name] = options;
|
|
98
|
-
});
|
|
99
|
-
const mockEntries: Array<{
|
|
100
|
-
type?: string;
|
|
101
|
-
role?: string;
|
|
102
|
-
customType?: string;
|
|
103
|
-
data?: unknown;
|
|
104
|
-
}> = [];
|
|
105
|
-
|
|
106
|
-
const mockCtx = {
|
|
107
|
-
hasUI: true,
|
|
108
|
-
ui: { notify: notifySpy } as any,
|
|
109
|
-
sessionManager: {
|
|
110
|
-
getEntries: vi.fn().mockReturnValue(mockEntries),
|
|
111
|
-
getBranch: vi.fn().mockReturnValue(mockEntries),
|
|
112
|
-
getSessionId: vi.fn(),
|
|
113
|
-
},
|
|
114
|
-
modelRegistry: undefined as any,
|
|
115
|
-
model: undefined,
|
|
116
|
-
cwd: '/tmp',
|
|
117
|
-
isIdle: vi.fn(),
|
|
118
|
-
signal: undefined,
|
|
119
|
-
abort: vi.fn(),
|
|
120
|
-
hasPendingMessages: vi.fn(),
|
|
121
|
-
shutdown: vi.fn(),
|
|
122
|
-
getContextUsage: vi.fn(),
|
|
123
|
-
compact: vi.fn(),
|
|
124
|
-
getSystemPrompt: vi.fn(),
|
|
125
|
-
} as any as ExtensionContext;
|
|
126
|
-
|
|
127
|
-
const eventsEmitSpy = vi.fn();
|
|
128
|
-
// Real event bus: listeners registered via events.on are dispatched on emit.
|
|
129
|
-
// eventsEmitSpy still captures every emit for assertions (e.g. 'tps:telemetry').
|
|
130
|
-
const eventListeners = new Map<string, ((payload: unknown) => void)[]>();
|
|
131
|
-
const dispatchEvent = (event: string, payload: unknown) => {
|
|
132
|
-
eventsEmitSpy(event, payload);
|
|
133
|
-
for (const listener of eventListeners.get(event) ?? []) {
|
|
134
|
-
try {
|
|
135
|
-
listener(payload);
|
|
136
|
-
} catch {
|
|
137
|
-
// listener errors shouldn't break emit dispatch in tests
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const mockPi: Partial<ExtensionAPI> = {
|
|
143
|
-
on: vi.fn((event: string, handler: any) => {
|
|
144
|
-
handlers[event] = handler;
|
|
145
|
-
return mockPi as ExtensionAPI;
|
|
146
|
-
}),
|
|
147
|
-
appendEntry: appendEntrySpy,
|
|
148
|
-
registerCommand: registerCommandSpy,
|
|
149
|
-
events: {
|
|
150
|
-
emit: dispatchEvent,
|
|
151
|
-
on: vi.fn((event: string, listener: (payload: unknown) => void) => {
|
|
152
|
-
const list = eventListeners.get(event) ?? [];
|
|
153
|
-
list.push(listener);
|
|
154
|
-
eventListeners.set(event, list);
|
|
155
|
-
}),
|
|
156
|
-
} as any,
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
mockPi,
|
|
161
|
-
handlers,
|
|
162
|
-
commands,
|
|
163
|
-
notifySpy,
|
|
164
|
-
appendEntrySpy,
|
|
165
|
-
eventsEmitSpy,
|
|
166
|
-
emitEvent: dispatchEvent,
|
|
167
|
-
registerCommandSpy,
|
|
168
|
-
mockEntries,
|
|
169
|
-
mockCtx,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/** Import the extension module and wire it to the test fixture's mockPi */
|
|
174
|
-
export async function activateExtension(fixture: TestFixture) {
|
|
175
|
-
const { default: tpsExtension } = await import('../index.js');
|
|
176
|
-
tpsExtension(fixture.mockPi as ExtensionAPI);
|
|
177
|
-
}
|