@monotykamary/pi-supervisor 0.5.9

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +21 -0
  3. package/README.md +341 -0
  4. package/media/demo.mp4 +0 -0
  5. package/media/screenshot.png +0 -0
  6. package/package.json +87 -0
  7. package/src/compaction/brief.ts +841 -0
  8. package/src/compaction/build-sections.ts +340 -0
  9. package/src/compaction/causal-keys.ts +138 -0
  10. package/src/compaction/content.ts +68 -0
  11. package/src/compaction/extract/commits.ts +78 -0
  12. package/src/compaction/extract/goals.ts +79 -0
  13. package/src/compaction/extract/preferences.ts +52 -0
  14. package/src/compaction/extract/shared-symbols.ts +376 -0
  15. package/src/compaction/filter-noise.ts +47 -0
  16. package/src/compaction/format.ts +89 -0
  17. package/src/compaction/index.ts +38 -0
  18. package/src/compaction/normalize.ts +73 -0
  19. package/src/compaction/sanitize.ts +5 -0
  20. package/src/compaction/sections.ts +19 -0
  21. package/src/compaction/skill-collapse.ts +35 -0
  22. package/src/compaction/tool-args.ts +14 -0
  23. package/src/compaction/types.ts +26 -0
  24. package/src/core/analyzer.ts +58 -0
  25. package/src/core/index.ts +8 -0
  26. package/src/core/inference.ts +77 -0
  27. package/src/core/prompt-builder.ts +137 -0
  28. package/src/core/prompt-loader.ts +125 -0
  29. package/src/core/reframe.ts +27 -0
  30. package/src/fabric-provider.ts +115 -0
  31. package/src/global-config.ts +65 -0
  32. package/src/index.ts +514 -0
  33. package/src/session/client.ts +46 -0
  34. package/src/session/response-parser.ts +37 -0
  35. package/src/session/supervisor-session.ts +102 -0
  36. package/src/state/manager.ts +133 -0
  37. package/src/state/mid-run-signals.ts +103 -0
  38. package/src/state/patterns.ts +82 -0
  39. package/src/state/reframe.ts +27 -0
  40. package/src/subagent-detector.ts +94 -0
  41. package/src/types.ts +42 -0
  42. package/src/ui/animations.ts +95 -0
  43. package/src/ui/model-picker.ts +72 -0
  44. package/src/ui/model-settings-selector.ts +440 -0
  45. package/src/ui/model-sort.ts +101 -0
  46. package/src/ui/renderer.ts +314 -0
  47. package/src/ui/types.ts +48 -0
  48. package/tests/compaction.test.ts +507 -0
  49. package/tests/engine.test.ts +622 -0
  50. package/tests/ephemeral-supervision.test.ts +347 -0
  51. package/tests/fabric-provider.test.ts +55 -0
  52. package/tests/full-fidelity-snapshot.test.ts +250 -0
  53. package/tests/global-config.test.ts +74 -0
  54. package/tests/model-sort.test.ts +157 -0
  55. package/tests/parsing.test.ts +303 -0
  56. package/tests/state.test.ts +474 -0
  57. package/tests/status-widget.test.ts +539 -0
  58. package/tests/subagent-detector.test.ts +191 -0
  59. package/tests/supervise-command.test.ts +363 -0
  60. package/tests/supervise-model-command.test.ts +184 -0
  61. package/tsconfig.json +14 -0
  62. package/vitest.config.ts +15 -0
@@ -0,0 +1,184 @@
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+
3
+ // Mock collaborators so importing the extension entry point is lightweight.
4
+ vi.mock('../src/core/analyzer.js', () => ({ analyze: vi.fn() }));
5
+ vi.mock('../src/core/inference.js', () => ({ inferOutcome: vi.fn() }));
6
+ vi.mock('../src/core/prompt-loader.js', () => ({
7
+ loadSystemPrompt: vi.fn().mockReturnValue({ prompt: 'p', source: 'built-in' }),
8
+ }));
9
+ vi.mock('../src/ui/renderer.js', () => ({ updateUI: vi.fn(), toggleWidget: vi.fn() }));
10
+ vi.mock('../src/ui/model-picker.js', () => ({ pickModel: vi.fn() }));
11
+ vi.mock('../src/global-config.js', () => ({
12
+ loadGlobalModel: vi.fn().mockReturnValue(null),
13
+ saveGlobalModel: vi.fn(),
14
+ }));
15
+ vi.mock('../src/session/client.js', () => ({ disposeSession: vi.fn() }));
16
+ vi.mock('../src/subagent-detector.js', () => ({
17
+ checkChildPiProcesses: vi.fn().mockResolvedValue({ hasActiveSubagents: false, count: 0 }),
18
+ waitForSubagents: vi
19
+ .fn()
20
+ .mockResolvedValue({ completed: true, finalStatus: { hasActiveSubagents: false, count: 0 } }),
21
+ }));
22
+ vi.mock('../src/compaction/index.js', () => ({
23
+ extractMessages: vi.fn().mockReturnValue([]),
24
+ buildCompactionSummary: vi.fn(),
25
+ formatForSupervisor: vi.fn(),
26
+ }));
27
+
28
+ import piSupervisor from '../src/index.js';
29
+ import { pickModel } from '../src/ui/model-picker.js';
30
+ import { loadGlobalModel, saveGlobalModel } from '../src/global-config.js';
31
+ import { updateUI } from '../src/ui/renderer.js';
32
+
33
+ function createMockApi() {
34
+ let superviseDef:
35
+ | {
36
+ handler: (args: string, ctx: any) => Promise<void>;
37
+ getArgumentCompletions?: (prefix: string) => any[] | null;
38
+ }
39
+ | undefined;
40
+ const api = {
41
+ appendEntry: vi.fn(),
42
+ on: vi.fn(),
43
+ registerCommand: vi.fn((name: string, def: any) => {
44
+ if (name === 'supervise') superviseDef = def;
45
+ }),
46
+ registerTool: vi.fn(),
47
+ sendUserMessage: vi.fn(),
48
+ sendMessage: vi.fn(),
49
+ setModel: vi.fn().mockResolvedValue(true),
50
+ events: { emit: vi.fn(), on: vi.fn() },
51
+ } as any;
52
+ return { api, getSupervise: () => superviseDef };
53
+ }
54
+
55
+ function createMockCtx(overrides: { isIdle?: boolean; cwd?: string } = {}) {
56
+ return {
57
+ ui: {
58
+ notify: vi.fn(),
59
+ custom: vi.fn(),
60
+ setWidget: vi.fn(),
61
+ setFooter: vi.fn(),
62
+ setHeader: vi.fn(),
63
+ setStatus: vi.fn(),
64
+ setWorkingMessage: vi.fn(),
65
+ },
66
+ hasUI: true,
67
+ cwd: overrides.cwd ?? '/test/project',
68
+ sessionManager: { getBranch: vi.fn().mockReturnValue([]) },
69
+ modelRegistry: {
70
+ getApiKeyForProvider: vi.fn().mockResolvedValue('test-key'),
71
+ find: vi.fn(),
72
+ getAvailable: vi.fn().mockReturnValue([]),
73
+ },
74
+ model: { provider: 'anthropic', id: 'claude-haiku' },
75
+ isIdle: vi.fn().mockReturnValue(overrides.isIdle ?? true),
76
+ abort: vi.fn(),
77
+ hasPendingMessages: vi.fn().mockReturnValue(false),
78
+ shutdown: vi.fn(),
79
+ getContextUsage: vi.fn(),
80
+ compact: vi.fn(),
81
+ getSystemPrompt: vi.fn().mockReturnValue('test system prompt'),
82
+ } as any;
83
+ }
84
+
85
+ describe('/supervise model command', () => {
86
+ beforeEach(() => {
87
+ vi.clearAllMocks();
88
+ vi.mocked(loadGlobalModel).mockReturnValue(null);
89
+ });
90
+
91
+ it('exposes subcommand autocomplete including model', () => {
92
+ const { api, getSupervise } = createMockApi();
93
+ piSupervisor(api);
94
+ const getArgumentCompletions = getSupervise()!.getArgumentCompletions!;
95
+
96
+ expect(getArgumentCompletions('')).toEqual([
97
+ { value: 'model', label: 'model', description: 'Pick the supervisor model' },
98
+ { value: 'stop', label: 'stop', description: 'Stop active supervision' },
99
+ { value: 'widget', label: 'widget', description: 'Toggle the status widget' },
100
+ ]);
101
+ expect(getArgumentCompletions('m')).toEqual([
102
+ { value: 'model', label: 'model', description: 'Pick the supervisor model' },
103
+ ]);
104
+ expect(getArgumentCompletions('s')).toEqual([
105
+ { value: 'stop', label: 'stop', description: 'Stop active supervision' },
106
+ ]);
107
+ // Free-form goal text (no subcommand match) yields no suggestions
108
+ expect(getArgumentCompletions('refactor auth')).toBeNull();
109
+ });
110
+
111
+ it('saves the picked model and notifies when supervision is inactive', async () => {
112
+ const { api, getSupervise } = createMockApi();
113
+ piSupervisor(api);
114
+ const handler = getSupervise()!.handler;
115
+ const ctx = createMockCtx();
116
+
117
+ vi.mocked(pickModel).mockResolvedValue({ provider: 'openai', id: 'gpt-4o' } as any);
118
+
119
+ await handler('model', ctx);
120
+
121
+ // Pre-highlights the chat model (no active state, no global config)
122
+ expect(pickModel).toHaveBeenCalledWith(ctx, 'anthropic', 'claude-haiku');
123
+ expect(saveGlobalModel).toHaveBeenCalledWith('/test/project', {
124
+ provider: 'openai',
125
+ modelId: 'gpt-4o',
126
+ });
127
+ // Not active → widget not refreshed
128
+ expect(updateUI).not.toHaveBeenCalled();
129
+ expect(ctx.ui.notify).toHaveBeenCalledWith(
130
+ expect.stringContaining('Supervisor model set to openai/gpt-4o'),
131
+ 'info'
132
+ );
133
+ });
134
+
135
+ it('notifies cancelled and does not save when the picker is dismissed', async () => {
136
+ const { api, getSupervise } = createMockApi();
137
+ piSupervisor(api);
138
+ const handler = getSupervise()!.handler;
139
+ const ctx = createMockCtx();
140
+
141
+ vi.mocked(pickModel).mockResolvedValue(null);
142
+
143
+ await handler('model', ctx);
144
+
145
+ expect(saveGlobalModel).not.toHaveBeenCalled();
146
+ expect(updateUI).not.toHaveBeenCalled();
147
+ expect(ctx.ui.notify).toHaveBeenCalledWith('Supervisor model selection cancelled.', 'info');
148
+ });
149
+
150
+ it('updates the live session model and refreshes the widget when supervision is active', async () => {
151
+ const { api, getSupervise } = createMockApi();
152
+ piSupervisor(api);
153
+ const handler = getSupervise()!.handler;
154
+ const ctx = createMockCtx({ isIdle: true });
155
+
156
+ // Start active supervision with the explicit-goal path (API key present)
157
+ await handler('Refactor auth module', ctx);
158
+ expect(api.sendUserMessage).toHaveBeenCalled();
159
+
160
+ updateUI.mockClear();
161
+ api.appendEntry.mockClear();
162
+
163
+ vi.mocked(pickModel).mockResolvedValue({ provider: 'openai', id: 'gpt-4o' } as any);
164
+
165
+ await handler('model', ctx);
166
+
167
+ // Pre-highlights the active session model, not the chat model
168
+ expect(pickModel).toHaveBeenCalledWith(ctx, 'anthropic', 'claude-haiku');
169
+ expect(saveGlobalModel).toHaveBeenCalledWith('/test/project', {
170
+ provider: 'openai',
171
+ modelId: 'gpt-4o',
172
+ });
173
+ // Active → widget refreshed
174
+ expect(updateUI).toHaveBeenCalled();
175
+ // state.setModel() persisted the new model into the session
176
+ const lastEntry = api.appendEntry.mock.calls[api.appendEntry.mock.calls.length - 1];
177
+ expect(lastEntry[1].provider).toBe('openai');
178
+ expect(lastEntry[1].modelId).toBe('gpt-4o');
179
+ expect(ctx.ui.notify).toHaveBeenCalledWith(
180
+ expect.stringContaining('Supervisor model set to openai/gpt-4o'),
181
+ 'info'
182
+ );
183
+ });
184
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "rootDir": "src",
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ include: ['tests/**/*.test.ts'],
7
+ clearMocks: true,
8
+ restoreMocks: true,
9
+ coverage: {
10
+ provider: 'v8',
11
+ reporter: ['text', 'json', 'html'],
12
+ exclude: ['node_modules/', 'tests/', '**/*.d.ts', '**/*.config.ts'],
13
+ },
14
+ },
15
+ });