@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 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.22",
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.99.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.99.0",
54
- "@earendil-works/pi-ai": "0.99.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.99.0",
92
- "@earendil-works/pi-coding-agent": "0.99.0",
93
- "@earendil-works/pi-tui": "0.99.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 (this.session && this.model === newModel && this.systemPrompt === systemPrompt) {
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
- sessionManager: SessionManager.inMemory(),
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
+ });