@monotykamary/pi-supervisor 0.5.22 → 0.5.23
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/CHANGELOG.md +6 -0
- package/README.md +4 -0
- package/package.json +7 -7
- package/src/session/supervisor-session.ts +48 -3
- package/tests/native-session.test.ts +117 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.23
|
|
4
|
+
|
|
5
|
+
- Pin development SDKs to Pi 1.0.0 while retaining host-owned wildcard peers.
|
|
6
|
+
- Exercise actual SDK and bundled host lifecycle, identity, native nested validation/loadouts, reload and shutdown offline.
|
|
7
|
+
- Fix real child sessions losing parent-native providers: forward through the public model registry, preserve cwd, scope reuse to the parent registry, and honor pre-aborted/active cancellation.
|
|
8
|
+
|
|
3
9
|
## 0.5.22
|
|
4
10
|
|
|
5
11
|
- Validate against Pi 0.99.0, including an offline real-host package-loading probe.
|
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ _Observe every turn, steer when the agent drifts, signal when the goal is reache
|
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
+
## Pi 1.0 compatibility (0.5.23)
|
|
15
|
+
|
|
16
|
+
Development SDKs are pinned to **1.0.0**; host-provided dependencies remain wildcard peers. Run `bun run test:pi` for offline real-host registrations, prompt/tool loadouts, nested calls, reload and shutdown. Set `PI1_HOST_PACKAGE` to an installed Pi package directory and `PI1_HOST_ENTRY=bundle` to check its bundled runtime.
|
|
17
|
+
|
|
14
18
|
---
|
|
15
19
|
|
|
16
20
|
> A supervisor as the intelligent overseer keeping the agent on track.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-supervisor",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.23",
|
|
4
4
|
"description": "A pi extension that supervises the chat and steers it towards a defined outcome",
|
|
5
5
|
"author": "tintinweb (forked by monotykamary)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@commitlint/cli": "21.0.1",
|
|
43
43
|
"@commitlint/config-conventional": "21.0.1",
|
|
44
|
-
"@earendil-works/pi-tui": "0.
|
|
44
|
+
"@earendil-works/pi-tui": "1.0.0",
|
|
45
45
|
"@types/node": "25.9.1",
|
|
46
46
|
"knip": "6.14.1",
|
|
47
47
|
"lint-staged": "17.0.5",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"standard-version": "9.5.0",
|
|
51
51
|
"typescript": "6.0.3",
|
|
52
52
|
"vitest": "4.1.7",
|
|
53
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
54
|
-
"@earendil-works/pi-ai": "0.
|
|
53
|
+
"@earendil-works/pi-coding-agent": "1.0.0",
|
|
54
|
+
"@earendil-works/pi-ai": "1.0.0",
|
|
55
55
|
"typebox": "1.3.27"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"fast-xml-builder": "1.2.0",
|
|
89
89
|
"protobufjs": "8.4.0",
|
|
90
90
|
"ws": "8.20.1",
|
|
91
|
-
"@earendil-works/pi-ai": "0.
|
|
92
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
93
|
-
"@earendil-works/pi-tui": "0.
|
|
91
|
+
"@earendil-works/pi-ai": "1.0.0",
|
|
92
|
+
"@earendil-works/pi-coding-agent": "1.0.0",
|
|
93
|
+
"@earendil-works/pi-tui": "1.0.0"
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -15,6 +15,8 @@ export class SupervisorSession {
|
|
|
15
15
|
private session: Awaited<ReturnType<typeof createAgentSession>>['session'] | null = null;
|
|
16
16
|
private model: any = null;
|
|
17
17
|
private systemPrompt: string = '';
|
|
18
|
+
private parentRegistry: ExtensionContext['modelRegistry'] | null = null;
|
|
19
|
+
private cwd: string | null = null;
|
|
18
20
|
|
|
19
21
|
async ensureStarted(
|
|
20
22
|
ctx: ExtensionContext,
|
|
@@ -26,7 +28,13 @@ export class SupervisorSession {
|
|
|
26
28
|
const newModel = ctx.modelRegistry.find(provider, modelId);
|
|
27
29
|
if (!newModel) return false;
|
|
28
30
|
|
|
29
|
-
if (
|
|
31
|
+
if (
|
|
32
|
+
this.session &&
|
|
33
|
+
this.model === newModel &&
|
|
34
|
+
this.systemPrompt === systemPrompt &&
|
|
35
|
+
this.parentRegistry === ctx.modelRegistry &&
|
|
36
|
+
this.cwd === ctx.cwd
|
|
37
|
+
) {
|
|
30
38
|
// Session reusable
|
|
31
39
|
return true;
|
|
32
40
|
}
|
|
@@ -42,12 +50,45 @@ export class SupervisorSession {
|
|
|
42
50
|
noPromptTemplates: true,
|
|
43
51
|
noThemes: true,
|
|
44
52
|
systemPromptOverride: () => systemPrompt,
|
|
53
|
+
noContextFiles: true,
|
|
54
|
+
appendSystemPromptOverride: () => [],
|
|
55
|
+
// A fresh SDK session otherwise loses extension/native providers owned
|
|
56
|
+
// by the parent runtime. Delegate through the public model registry so
|
|
57
|
+
// parent auth, environment and request hooks remain authoritative.
|
|
58
|
+
extensionFactories: [
|
|
59
|
+
(pi) => {
|
|
60
|
+
pi.registerProvider(newModel.provider, {
|
|
61
|
+
api: newModel.api,
|
|
62
|
+
baseUrl: newModel.baseUrl,
|
|
63
|
+
apiKey: 'supervisor-parent-runtime',
|
|
64
|
+
models: [
|
|
65
|
+
{
|
|
66
|
+
id: newModel.id,
|
|
67
|
+
name: newModel.name,
|
|
68
|
+
api: newModel.api,
|
|
69
|
+
reasoning: newModel.reasoning,
|
|
70
|
+
input: newModel.input,
|
|
71
|
+
cost: newModel.cost,
|
|
72
|
+
contextWindow: newModel.contextWindow,
|
|
73
|
+
maxTokens: newModel.maxTokens,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
streamSimple: (_model, context, options) => {
|
|
77
|
+
// The child credential is a local admission token, never provider
|
|
78
|
+
// authentication. Let the parent's runtime resolve real auth.
|
|
79
|
+
const { apiKey: _childKey, ...forwarded } = options ?? {};
|
|
80
|
+
return ctx.modelRegistry.streamSimple(newModel, context, forwarded);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
],
|
|
45
85
|
});
|
|
46
86
|
await loader.reload();
|
|
47
87
|
|
|
48
88
|
try {
|
|
49
89
|
const result = await createAgentSession({
|
|
50
|
-
|
|
90
|
+
cwd: ctx.cwd,
|
|
91
|
+
sessionManager: SessionManager.inMemory(ctx.cwd),
|
|
51
92
|
agentDir: getAgentDir(),
|
|
52
93
|
model: newModel,
|
|
53
94
|
tools: [],
|
|
@@ -56,6 +97,8 @@ export class SupervisorSession {
|
|
|
56
97
|
this.session = result.session;
|
|
57
98
|
this.model = newModel;
|
|
58
99
|
this.systemPrompt = systemPrompt;
|
|
100
|
+
this.parentRegistry = ctx.modelRegistry;
|
|
101
|
+
this.cwd = ctx.cwd;
|
|
59
102
|
return true;
|
|
60
103
|
} catch {
|
|
61
104
|
return false;
|
|
@@ -67,7 +110,7 @@ export class SupervisorSession {
|
|
|
67
110
|
signal?: AbortSignal,
|
|
68
111
|
onDelta?: (accumulated: string) => void
|
|
69
112
|
): Promise<string | null> {
|
|
70
|
-
if (!this.session) return null;
|
|
113
|
+
if (!this.session || signal?.aborted) return null;
|
|
71
114
|
|
|
72
115
|
const onAbort = () => this.session?.abort();
|
|
73
116
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
@@ -98,5 +141,7 @@ export class SupervisorSession {
|
|
|
98
141
|
this.session = null;
|
|
99
142
|
}
|
|
100
143
|
this.model = null;
|
|
144
|
+
this.parentRegistry = null;
|
|
145
|
+
this.cwd = null;
|
|
101
146
|
}
|
|
102
147
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
ModelRegistry,
|
|
7
|
+
ModelRuntime,
|
|
8
|
+
type ExtensionContext,
|
|
9
|
+
} from '@earendil-works/pi-coding-agent';
|
|
10
|
+
import {
|
|
11
|
+
fauxAssistantMessage,
|
|
12
|
+
fauxProvider,
|
|
13
|
+
getCurrentSystemPrompt,
|
|
14
|
+
getCurrentTools,
|
|
15
|
+
} from '@earendil-works/pi-ai/compat';
|
|
16
|
+
import { SupervisorSession } from '../src/session/supervisor-session.js';
|
|
17
|
+
|
|
18
|
+
it('inherits the parent native provider runtime for real SDK supervisor sessions', async () => {
|
|
19
|
+
const root = mkdtempSync(join(tmpdir(), 'pi1-supervisor-'));
|
|
20
|
+
const previous = process.env.PI_CODING_AGENT_DIR;
|
|
21
|
+
const previousFetch = globalThis.fetch;
|
|
22
|
+
globalThis.fetch = async () => new Response('', { status: 503 });
|
|
23
|
+
process.env.PI_CODING_AGENT_DIR = root;
|
|
24
|
+
const supervisor = new SupervisorSession();
|
|
25
|
+
try {
|
|
26
|
+
const runtime = await ModelRuntime.create({
|
|
27
|
+
authPath: join(root, 'auth.json'),
|
|
28
|
+
modelsPath: null,
|
|
29
|
+
refreshOnCreate: false,
|
|
30
|
+
});
|
|
31
|
+
const faux = fauxProvider({
|
|
32
|
+
provider: 'supervisor-offline',
|
|
33
|
+
api: 'supervisor-offline-api',
|
|
34
|
+
models: [{ id: 'test', name: 'Offline', reasoning: false }],
|
|
35
|
+
tokenSize: { min: 100, max: 100 },
|
|
36
|
+
});
|
|
37
|
+
runtime.registerNativeProvider(faux.provider);
|
|
38
|
+
await runtime.refresh({ allowNetwork: false });
|
|
39
|
+
const ctx = { cwd: root, modelRegistry: new ModelRegistry(runtime) } as ExtensionContext;
|
|
40
|
+
faux.setResponses([
|
|
41
|
+
(context, options) => {
|
|
42
|
+
expect(options?.apiKey).not.toBe('supervisor-parent-runtime');
|
|
43
|
+
expect(getCurrentSystemPrompt(context.messages)).toContain(
|
|
44
|
+
'Analyze only the offline goal.'
|
|
45
|
+
);
|
|
46
|
+
expect(getCurrentSystemPrompt(context.messages)).toContain(root);
|
|
47
|
+
expect(getCurrentTools(context.messages)).toEqual([]);
|
|
48
|
+
return fauxAssistantMessage('local supervisor decision');
|
|
49
|
+
},
|
|
50
|
+
fauxAssistantMessage('second decision'),
|
|
51
|
+
]);
|
|
52
|
+
expect(
|
|
53
|
+
await supervisor.ensureStarted(
|
|
54
|
+
ctx,
|
|
55
|
+
'supervisor-offline',
|
|
56
|
+
'test',
|
|
57
|
+
'Analyze only the offline goal.'
|
|
58
|
+
)
|
|
59
|
+
).toBe(true);
|
|
60
|
+
expect(await supervisor.prompt('first analysis')).toBe('local supervisor decision');
|
|
61
|
+
expect(
|
|
62
|
+
await supervisor.ensureStarted(
|
|
63
|
+
ctx,
|
|
64
|
+
'supervisor-offline',
|
|
65
|
+
'test',
|
|
66
|
+
'Analyze only the offline goal.'
|
|
67
|
+
)
|
|
68
|
+
).toBe(true);
|
|
69
|
+
expect(await supervisor.prompt('second analysis')).toBe('second decision');
|
|
70
|
+
expect(faux.state.callCount).toBe(2);
|
|
71
|
+
faux.setResponses([
|
|
72
|
+
(context) => {
|
|
73
|
+
expect(JSON.stringify(context.messages)).not.toContain('first analysis');
|
|
74
|
+
return fauxAssistantMessage('fresh parent decision');
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
77
|
+
const replacement = { ...ctx, modelRegistry: new ModelRegistry(runtime) };
|
|
78
|
+
expect(
|
|
79
|
+
await supervisor.ensureStarted(
|
|
80
|
+
replacement,
|
|
81
|
+
'supervisor-offline',
|
|
82
|
+
'test',
|
|
83
|
+
'Analyze only the offline goal.'
|
|
84
|
+
)
|
|
85
|
+
).toBe(true);
|
|
86
|
+
expect(await supervisor.prompt('fresh analysis')).toBe('fresh parent decision');
|
|
87
|
+
expect(faux.state.callCount).toBe(3);
|
|
88
|
+
const alreadyAborted = new AbortController();
|
|
89
|
+
alreadyAborted.abort();
|
|
90
|
+
expect(await supervisor.prompt('must not start', alreadyAborted.signal)).toBeNull();
|
|
91
|
+
expect(faux.state.callCount).toBe(3);
|
|
92
|
+
let enter!: () => void;
|
|
93
|
+
const entered = new Promise<void>((resolve) => {
|
|
94
|
+
enter = resolve;
|
|
95
|
+
});
|
|
96
|
+
faux.setResponses([
|
|
97
|
+
async (_context, options) => {
|
|
98
|
+
enter();
|
|
99
|
+
await new Promise<void>((resolve) => {
|
|
100
|
+
options!.signal!.addEventListener('abort', () => resolve(), { once: true });
|
|
101
|
+
});
|
|
102
|
+
return fauxAssistantMessage('must not finish normally');
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
const cancellation = new AbortController();
|
|
106
|
+
const cancelled = supervisor.prompt('cancel local analysis', cancellation.signal);
|
|
107
|
+
await entered;
|
|
108
|
+
cancellation.abort();
|
|
109
|
+
expect(await cancelled).not.toBe('must not finish normally');
|
|
110
|
+
} finally {
|
|
111
|
+
supervisor.dispose();
|
|
112
|
+
globalThis.fetch = previousFetch;
|
|
113
|
+
if (previous === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
|
114
|
+
else process.env.PI_CODING_AGENT_DIR = previous;
|
|
115
|
+
rmSync(root, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|