@agents-ensemble/core 0.7.0 → 0.9.0
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/dist/conductor/conductor-auth.d.ts +36 -6
- package/dist/conductor/conductor-auth.d.ts.map +1 -1
- package/dist/conductor/conductor-auth.js +59 -7
- package/dist/conductor/conductor-auth.js.map +1 -1
- package/dist/conductor/conductor-pi-auth.d.ts +75 -0
- package/dist/conductor/conductor-pi-auth.d.ts.map +1 -0
- package/dist/conductor/conductor-pi-auth.js +656 -0
- package/dist/conductor/conductor-pi-auth.js.map +1 -0
- package/dist/conductor/conductor-session-driver.d.ts +2 -0
- package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
- package/dist/conductor/conductor-session-driver.js +18 -1
- package/dist/conductor/conductor-session-driver.js.map +1 -1
- package/dist/conductor/conductor-session.d.ts.map +1 -1
- package/dist/conductor/conductor-session.js +69 -7
- package/dist/conductor/conductor-session.js.map +1 -1
- package/dist/conductor/conductor-tool-pi-adapter.d.ts +6 -8
- package/dist/conductor/conductor-tool-pi-adapter.d.ts.map +1 -1
- package/dist/conductor/conductor-tool-pi-adapter.js +5 -8
- package/dist/conductor/conductor-tool-pi-adapter.js.map +1 -1
- package/dist/conductor/index.d.ts +7 -8
- package/dist/conductor/index.d.ts.map +1 -1
- package/dist/conductor/index.js +4 -4
- package/dist/conductor/index.js.map +1 -1
- package/dist/conductor/list-conductor-models.d.ts +7 -2
- package/dist/conductor/list-conductor-models.d.ts.map +1 -1
- package/dist/conductor/list-conductor-models.js +9 -1
- package/dist/conductor/list-conductor-models.js.map +1 -1
- package/dist/conductor/pi-conductor-agent.d.ts +23 -6
- package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/pi-conductor-agent.js +317 -130
- package/dist/conductor/pi-conductor-agent.js.map +1 -1
- package/dist/conductor/pi-resource-loader.d.ts +7 -4
- package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
- package/dist/conductor/pi-resource-loader.js +6 -4
- package/dist/conductor/pi-resource-loader.js.map +1 -1
- package/dist/conductor/session/dispatch-hold.d.ts +14 -0
- package/dist/conductor/session/dispatch-hold.d.ts.map +1 -1
- package/dist/conductor/session/dispatch-hold.js +30 -0
- package/dist/conductor/session/dispatch-hold.js.map +1 -1
- package/dist/conductor/session/events/index.d.ts +1 -1
- package/dist/conductor/session/events/index.d.ts.map +1 -1
- package/dist/conductor/session/events/index.js.map +1 -1
- package/dist/conductor/session/events/session-log-event-groups.d.ts +2 -2
- package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
- package/dist/conductor/session/events/session-log-event-groups.js +1 -0
- package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
- package/dist/conductor/session/events/session-log-event.d.ts +10 -1
- package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
- package/dist/conductor/session/format-session-event.js +3 -0
- package/dist/conductor/session/format-session-event.js.map +1 -1
- package/dist/conductor/session/session-logger.d.ts.map +1 -1
- package/dist/conductor/session/session-logger.js +1 -0
- package/dist/conductor/session/session-logger.js.map +1 -1
- package/dist/github/fetch-github-updates.d.ts.map +1 -1
- package/dist/github/fetch-github-updates.js +109 -80
- package/dist/github/fetch-github-updates.js.map +1 -1
- package/dist/github/github-client.d.ts.map +1 -1
- package/dist/github/github-client.js +8 -0
- package/dist/github/github-client.js.map +1 -1
- package/dist/github/github-monitor-cursor.d.ts +19 -8
- package/dist/github/github-monitor-cursor.d.ts.map +1 -1
- package/dist/github/github-monitor-cursor.js +8 -52
- package/dist/github/github-monitor-cursor.js.map +1 -1
- package/dist/github/github-monitor.d.ts +2 -0
- package/dist/github/github-monitor.d.ts.map +1 -1
- package/dist/github/github-monitor.js +13 -0
- package/dist/github/github-monitor.js.map +1 -1
- package/dist/github/github-test-fixtures.d.ts +9 -0
- package/dist/github/github-test-fixtures.d.ts.map +1 -1
- package/dist/github/github-test-fixtures.js +6 -0
- package/dist/github/github-test-fixtures.js.map +1 -1
- package/dist/github/github-update-types.d.ts +10 -0
- package/dist/github/github-update-types.d.ts.map +1 -1
- package/dist/github/index.d.ts +3 -1
- package/dist/github/index.d.ts.map +1 -1
- package/dist/github/index.js +1 -0
- package/dist/github/index.js.map +1 -1
- package/dist/github/register-github-watch-tool.d.ts +5 -0
- package/dist/github/register-github-watch-tool.d.ts.map +1 -1
- package/dist/github/register-github-watch-tool.js +30 -24
- package/dist/github/register-github-watch-tool.js.map +1 -1
- package/dist/github/unregister-github-watch-tool.d.ts +11 -0
- package/dist/github/unregister-github-watch-tool.d.ts.map +1 -0
- package/dist/github/unregister-github-watch-tool.js +52 -0
- package/dist/github/unregister-github-watch-tool.js.map +1 -0
- package/dist/mcp/load-mcp-config.d.ts +8 -0
- package/dist/mcp/load-mcp-config.d.ts.map +1 -1
- package/dist/mcp/load-mcp-config.js +24 -0
- package/dist/mcp/load-mcp-config.js.map +1 -1
- package/dist/prompt/modules/ensemble/base-module.d.ts.map +1 -1
- package/dist/prompt/modules/ensemble/base-module.js +2 -1
- package/dist/prompt/modules/ensemble/base-module.js.map +1 -1
- package/dist/prompt/modules/ensemble/conductor-base-module.d.ts.map +1 -1
- package/dist/prompt/modules/ensemble/conductor-base-module.js +4 -1
- package/dist/prompt/modules/ensemble/conductor-base-module.js.map +1 -1
- package/dist/runtime/worker-session.d.ts +5 -0
- package/dist/runtime/worker-session.d.ts.map +1 -1
- package/dist/runtime/worker-session.js +5 -1
- package/dist/runtime/worker-session.js.map +1 -1
- package/dist/session/session-sidecar.d.ts +6 -0
- package/dist/session/session-sidecar.d.ts.map +1 -1
- package/dist/session/session-sidecar.js +17 -0
- package/dist/session/session-sidecar.js.map +1 -1
- package/package.json +4 -4
- package/dist/conductor/pi-conductor-session.d.ts +0 -20
- package/dist/conductor/pi-conductor-session.d.ts.map +0 -1
- package/dist/conductor/pi-conductor-session.js +0 -60
- package/dist/conductor/pi-conductor-session.js.map +0 -1
- package/dist/conductor/pi-mcp-bridge.d.ts +0 -65
- package/dist/conductor/pi-mcp-bridge.d.ts.map +0 -1
- package/dist/conductor/pi-mcp-bridge.js +0 -259
- package/dist/conductor/pi-mcp-bridge.js.map +0 -1
|
@@ -1,46 +1,58 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { getModel, getModels, getProviders, } from '@earendil-works/pi-ai/compat';
|
|
4
|
+
import { createAgentSession, createCodemodeExtension, createMcpExtension, createToolSearchExtension, DefaultResourceLoader, SessionManager, SettingsManager, } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { createPiConductorAuthContext, resolvePiConductorApiKey, } from './conductor-pi-auth.js';
|
|
6
|
+
import { toPiCodingAgentTools } from './conductor-tool-pi-adapter.js';
|
|
6
7
|
import { expandPiResourcePrompt, formatPiSkillsForPrompt, loadPiResources, } from './pi-resource-loader.js';
|
|
7
|
-
|
|
8
|
+
/** Project-local Pi transcript storage used by the conductor backend. */
|
|
9
|
+
export const PI_SESSION_ROOT = '.ensemble/pi/sessions';
|
|
10
|
+
const PI_MCP_STARTUP_OBSERVATION_MS = 2_000;
|
|
8
11
|
/** Resolve the Pi model/auth selection from the standard resource roots. */
|
|
9
12
|
export async function resolvePiModelConfig(options) {
|
|
10
13
|
const resources = await loadPiResources(options);
|
|
11
|
-
|
|
14
|
+
const authContext = await createPiConductorAuthContext(options, resources);
|
|
15
|
+
return resolvePiModelConfigFromResources(options, resources, authContext);
|
|
12
16
|
}
|
|
13
|
-
function resolvePiModelConfigFromResources(options, resources) {
|
|
17
|
+
async function resolvePiModelConfigFromResources(options, resources, authContext) {
|
|
14
18
|
const env = options.env ?? process.env;
|
|
15
19
|
const settings = resources.settings;
|
|
16
20
|
const selection = resolvePiModelSelection(options.modelId, settings, resources.models);
|
|
17
21
|
const providerConfig = resources.models.providers[selection.provider];
|
|
22
|
+
const projectProviderConfig = resources.modelsLayers.at(-1)?.providers[selection.provider];
|
|
23
|
+
const projectDefinesModel = projectProviderConfig?.models?.some((candidate) => candidate.id === selection.modelId);
|
|
24
|
+
const registryModel = projectDefinesModel
|
|
25
|
+
? undefined
|
|
26
|
+
: authContext.modelRegistry.find(selection.provider, selection.modelId);
|
|
27
|
+
const apiKey = await resolvePiConductorApiKey({
|
|
28
|
+
modelRuntime: authContext.modelRuntime,
|
|
29
|
+
resources,
|
|
30
|
+
provider: selection.provider,
|
|
31
|
+
explicitApiKey: options.apiKey,
|
|
32
|
+
env,
|
|
33
|
+
});
|
|
18
34
|
const builtInModel = getModel(selection.provider, selection.modelId);
|
|
19
|
-
const model = resolvePiModel(selection.provider, selection.modelId, providerConfig, builtInModel);
|
|
35
|
+
const model = resolvePiModel(selection.provider, selection.modelId, providerConfig, registryModel ?? builtInModel, registryModel);
|
|
20
36
|
if (!model) {
|
|
21
37
|
throw new Error(`Pi conductor model "${selection.provider}/${selection.modelId}" was not found in pi-ai or models.json. ` +
|
|
22
38
|
'Set defaultProvider/defaultModel in Pi settings.json or pass a built-in provider/model id.');
|
|
23
39
|
}
|
|
24
|
-
const apiKey = options.apiKey ??
|
|
25
|
-
resolvePiAuthKey(resources.authLayers, selection.provider, env) ??
|
|
26
|
-
resolvePiSettingsApiKey(settings, selection.provider, env) ??
|
|
27
|
-
resolvePiModelsApiKey(providerConfig, env) ??
|
|
28
|
-
getEnvApiKey(selection.provider, toProviderEnv(env));
|
|
29
40
|
return {
|
|
30
41
|
provider: selection.provider,
|
|
31
42
|
modelId: selection.modelId,
|
|
32
43
|
model: applyPiProviderRequestConfig(model, providerConfig, apiKey),
|
|
44
|
+
...(registryModel ? { usesModelRegistry: true } : {}),
|
|
33
45
|
...(apiKey !== undefined ? { apiKey } : {}),
|
|
34
46
|
};
|
|
35
47
|
}
|
|
36
48
|
/** Pi implementation of the backend-neutral conductor interface. */
|
|
37
49
|
export class PiConductorAgent {
|
|
38
|
-
agent;
|
|
39
50
|
session;
|
|
40
|
-
mcpBridge;
|
|
41
51
|
resources;
|
|
52
|
+
systemPromptRef;
|
|
42
53
|
agentId;
|
|
43
54
|
modelId;
|
|
55
|
+
mcpStartupState;
|
|
44
56
|
onStreamText;
|
|
45
57
|
unsubscribe;
|
|
46
58
|
activeSend;
|
|
@@ -49,46 +61,21 @@ export class PiConductorAgent {
|
|
|
49
61
|
hasUsage = false;
|
|
50
62
|
rawCostCents = 0;
|
|
51
63
|
chargedCents = 0;
|
|
52
|
-
constructor(
|
|
53
|
-
this.agent = agent;
|
|
64
|
+
constructor(session, resources, systemPromptRef, agentId, modelId, mcpStartupState, onStreamText) {
|
|
54
65
|
this.session = session;
|
|
55
|
-
this.mcpBridge = mcpBridge;
|
|
56
66
|
this.resources = resources;
|
|
67
|
+
this.systemPromptRef = systemPromptRef;
|
|
57
68
|
this.agentId = agentId;
|
|
58
69
|
this.modelId = modelId;
|
|
70
|
+
this.mcpStartupState = mcpStartupState;
|
|
59
71
|
this.onStreamText = onStreamText;
|
|
60
|
-
this.unsubscribe = this.
|
|
72
|
+
this.unsubscribe = this.session.subscribe((event) => this.handleEvent(event));
|
|
61
73
|
}
|
|
62
74
|
static async create(options) {
|
|
63
|
-
|
|
64
|
-
const resources = await loadPiResources(options);
|
|
65
|
-
const resolved = resolvePiModelConfigFromResources(options, resources);
|
|
66
|
-
const session = await PiConductorSession.create(options.cwd, agentId);
|
|
67
|
-
const mcpBridge = await createPiMcpBridge(options.mcpServers, {
|
|
68
|
-
cwd: options.cwd,
|
|
69
|
-
});
|
|
70
|
-
try {
|
|
71
|
-
return new PiConductorAgent(createPiAgent(agentId, options, resolved, session.messages, mcpBridge, resources), session, mcpBridge, resources, agentId, resolved.modelId, options.onStreamText);
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
await mcpBridge?.close();
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
75
|
+
return createPiConductorSession(randomUUID(), options, 'startup');
|
|
77
76
|
}
|
|
78
77
|
static async resume(agentId, options) {
|
|
79
|
-
|
|
80
|
-
const resolved = resolvePiModelConfigFromResources(options, resources);
|
|
81
|
-
const session = await PiConductorSession.resume(options.cwd, agentId);
|
|
82
|
-
const mcpBridge = await createPiMcpBridge(options.mcpServers, {
|
|
83
|
-
cwd: options.cwd,
|
|
84
|
-
});
|
|
85
|
-
try {
|
|
86
|
-
return new PiConductorAgent(createPiAgent(agentId, options, resolved, session.messages, mcpBridge, resources), session, mcpBridge, resources, agentId, resolved.modelId, options.onStreamText);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
await mcpBridge?.close();
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
78
|
+
return createPiConductorSession(agentId, options, 'resume');
|
|
92
79
|
}
|
|
93
80
|
async send(prompt, callbacks) {
|
|
94
81
|
const runId = randomUUID();
|
|
@@ -99,6 +86,17 @@ export class PiConductorAgent {
|
|
|
99
86
|
error: { message: 'Pi conductor agent is closed.' },
|
|
100
87
|
};
|
|
101
88
|
}
|
|
89
|
+
if (this.mcpStartupState.error) {
|
|
90
|
+
return {
|
|
91
|
+
runId,
|
|
92
|
+
status: 'error',
|
|
93
|
+
error: {
|
|
94
|
+
message: this.mcpStartupState.error,
|
|
95
|
+
code: 'MCP_CONNECTION_FAILED',
|
|
96
|
+
},
|
|
97
|
+
modelId: this.modelId,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
102
100
|
if (this.activeSend) {
|
|
103
101
|
return {
|
|
104
102
|
runId,
|
|
@@ -109,7 +107,10 @@ export class PiConductorAgent {
|
|
|
109
107
|
const state = { runId, callbacks };
|
|
110
108
|
this.activeSend = state;
|
|
111
109
|
try {
|
|
112
|
-
await this.
|
|
110
|
+
await this.session.prompt(expandPiResourcePrompt(prompt, this.resources), {
|
|
111
|
+
expandPromptTemplates: false,
|
|
112
|
+
source: 'rpc',
|
|
113
|
+
});
|
|
113
114
|
}
|
|
114
115
|
catch (error) {
|
|
115
116
|
return {
|
|
@@ -122,7 +123,7 @@ export class PiConductorAgent {
|
|
|
122
123
|
finally {
|
|
123
124
|
this.activeSend = undefined;
|
|
124
125
|
}
|
|
125
|
-
const finalMessage = state.finalMessage ?? lastAssistantMessage(this.
|
|
126
|
+
const finalMessage = state.finalMessage ?? lastAssistantMessage(this.session.state.messages);
|
|
126
127
|
if (!finalMessage) {
|
|
127
128
|
return {
|
|
128
129
|
runId,
|
|
@@ -143,8 +144,9 @@ export class PiConductorAgent {
|
|
|
143
144
|
modelId: finalMessage.model || this.modelId,
|
|
144
145
|
};
|
|
145
146
|
}
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
async reload() {
|
|
148
|
+
await this.session.reload();
|
|
149
|
+
}
|
|
148
150
|
async getUsage() {
|
|
149
151
|
return this.hasUsage
|
|
150
152
|
? {
|
|
@@ -157,7 +159,7 @@ export class PiConductorAgent {
|
|
|
157
159
|
}
|
|
158
160
|
/** Resume recompiles and reinstalls the native system prompt on each run. */
|
|
159
161
|
async setSystemPrompt(systemPrompt) {
|
|
160
|
-
this.
|
|
162
|
+
this.systemPromptRef.value = withPiSkills(systemPrompt, this.resources);
|
|
161
163
|
}
|
|
162
164
|
async close() {
|
|
163
165
|
if (this.closePromise) {
|
|
@@ -166,22 +168,20 @@ export class PiConductorAgent {
|
|
|
166
168
|
this.closed = true;
|
|
167
169
|
this.closePromise = (async () => {
|
|
168
170
|
try {
|
|
169
|
-
this.
|
|
170
|
-
await this.agent.waitForIdle();
|
|
171
|
-
await this.session.appendNewMessages(this.agent.state.messages);
|
|
171
|
+
await this.session.abort();
|
|
172
172
|
}
|
|
173
173
|
finally {
|
|
174
174
|
try {
|
|
175
175
|
this.unsubscribe();
|
|
176
176
|
}
|
|
177
177
|
finally {
|
|
178
|
-
|
|
178
|
+
this.session.dispose();
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
})();
|
|
182
182
|
return this.closePromise;
|
|
183
183
|
}
|
|
184
|
-
|
|
184
|
+
handleEvent(event) {
|
|
185
185
|
const activeSend = this.activeSend;
|
|
186
186
|
if (event.type === 'tool_execution_start') {
|
|
187
187
|
activeSend?.callbacks?.onToolCallStarted?.({
|
|
@@ -200,13 +200,6 @@ export class PiConductorAgent {
|
|
|
200
200
|
if (event.type === 'agent_end' && activeSend) {
|
|
201
201
|
activeSend.finalMessage = lastAssistantMessage(event.messages);
|
|
202
202
|
}
|
|
203
|
-
if (event.type === 'agent_end') {
|
|
204
|
-
// Pi's failure/abort event can contain only the failure assistant while
|
|
205
|
-
// agent.state.messages already contains the user prompt and failure.
|
|
206
|
-
// Persist the full in-memory suffix so every run is written once in
|
|
207
|
-
// transcript order; close() uses the same serialized operation.
|
|
208
|
-
await this.session.appendNewMessages(this.agent.state.messages);
|
|
209
|
-
}
|
|
210
203
|
}
|
|
211
204
|
recordUsage(message) {
|
|
212
205
|
this.hasUsage = true;
|
|
@@ -221,42 +214,274 @@ export function createPiConductorAgentFactory() {
|
|
|
221
214
|
resume: (agentId, options) => PiConductorAgent.resume(agentId, options),
|
|
222
215
|
};
|
|
223
216
|
}
|
|
224
|
-
function
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
async function createPiConductorSession(agentId, options, reason) {
|
|
218
|
+
const piMcpResolution = { cwd: options.cwd };
|
|
219
|
+
// Validate the harness-resolved map before creating the SDK session so an
|
|
220
|
+
// unsupported transport fails with an actionable Pi-specific error.
|
|
221
|
+
if (options.mcpServers)
|
|
222
|
+
loadPiMcpConfig(options.mcpServers, piMcpResolution);
|
|
223
|
+
const resources = await loadPiResources(options);
|
|
224
|
+
const authContext = await createPiConductorAuthContext(options, resources);
|
|
225
|
+
const resolved = await resolvePiModelConfigFromResources(options, resources, authContext);
|
|
226
|
+
if (options.apiKey !== undefined) {
|
|
227
|
+
await authContext.modelRuntime.setRuntimeApiKey(resolved.provider, options.apiKey);
|
|
228
|
+
}
|
|
229
|
+
const sessionDir = join(options.cwd, PI_SESSION_ROOT);
|
|
230
|
+
let sessionManager;
|
|
231
|
+
let previousSessionFile;
|
|
232
|
+
if (reason === 'resume') {
|
|
233
|
+
previousSessionFile = SessionManager.findById(options.cwd, agentId, sessionDir);
|
|
234
|
+
if (!previousSessionFile) {
|
|
235
|
+
throw new Error(`Pi session not found for resume (sessionId=${agentId}, cwd=${options.cwd}, ` +
|
|
236
|
+
`sessionsRoot=${PI_SESSION_ROOT})`);
|
|
237
|
+
}
|
|
238
|
+
sessionManager = SessionManager.open(previousSessionFile, sessionDir, options.cwd);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
sessionManager = SessionManager.create(options.cwd, sessionDir, { id: agentId });
|
|
242
|
+
}
|
|
243
|
+
const systemPromptRef = {
|
|
244
|
+
value: withPiSkills(options.systemPrompt, resources),
|
|
245
|
+
};
|
|
246
|
+
const settingsManager = SettingsManager.create(options.cwd, authContext.agentDir);
|
|
247
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
248
|
+
cwd: options.cwd,
|
|
249
|
+
agentDir: authContext.agentDir,
|
|
250
|
+
settingsManager,
|
|
251
|
+
noExtensions: true,
|
|
252
|
+
noSkills: true,
|
|
253
|
+
noPromptTemplates: true,
|
|
254
|
+
noThemes: true,
|
|
255
|
+
noContextFiles: true,
|
|
256
|
+
systemPrompt: systemPromptRef.value,
|
|
257
|
+
extensionFactories: [
|
|
258
|
+
createMcpExtension({
|
|
259
|
+
loadConfig: () => loadPiMcpConfig(options.mcpServers, piMcpResolution),
|
|
260
|
+
}),
|
|
261
|
+
createCodemodeExtension({ mode: 'on' }),
|
|
262
|
+
createToolSearchExtension(),
|
|
263
|
+
createPiSystemPromptExtension(systemPromptRef),
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
await resourceLoader.reload();
|
|
267
|
+
let session;
|
|
268
|
+
try {
|
|
269
|
+
const mcpStartupState = {};
|
|
270
|
+
const result = await createAgentSession({
|
|
271
|
+
cwd: options.cwd,
|
|
272
|
+
agentDir: authContext.agentDir,
|
|
273
|
+
modelRuntime: authContext.modelRuntime,
|
|
229
274
|
model: resolved.model,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
275
|
+
sessionManager,
|
|
276
|
+
settingsManager,
|
|
277
|
+
resourceLoader,
|
|
278
|
+
// Keep repository operations in worker ACP sessions. The conductor gets
|
|
279
|
+
// only harness tools plus the official MCP/codemode extensions.
|
|
280
|
+
noTools: 'builtin',
|
|
281
|
+
customTools: uniquePiTools(options.customTools ? toPiCodingAgentTools(options.customTools) : [], resources.extensionTools.map(toPiCodingAgentTool)),
|
|
282
|
+
sessionStartEvent: {
|
|
283
|
+
type: 'session_start',
|
|
284
|
+
reason,
|
|
285
|
+
...(previousSessionFile ? { previousSessionFile } : {}),
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
session = result.session;
|
|
289
|
+
await session.bindExtensions({
|
|
290
|
+
mode: 'rpc',
|
|
291
|
+
uiContext: createPiHeadlessExtensionUi(mcpStartupState),
|
|
292
|
+
onError: (error) => {
|
|
293
|
+
if (error.extensionPath.includes('mcp')) {
|
|
294
|
+
mcpStartupState.error =
|
|
295
|
+
`Pi MCP extension failed during ${error.event}: ${error.error}`;
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
await observePiMcpStartup(session, options.mcpServers, mcpStartupState);
|
|
300
|
+
if (mcpStartupState.error) {
|
|
301
|
+
throw new Error(mcpStartupState.error);
|
|
302
|
+
}
|
|
303
|
+
return new PiConductorAgent(session, resources, systemPromptRef, agentId, resolved.modelId, mcpStartupState, options.onStreamText);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
session?.dispose();
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function createPiHeadlessExtensionUi(state) {
|
|
311
|
+
return {
|
|
312
|
+
select: async () => undefined,
|
|
313
|
+
confirm: async () => false,
|
|
314
|
+
input: async () => undefined,
|
|
315
|
+
notify: (message, type) => {
|
|
316
|
+
if (message.startsWith('MCP failed to load:') ||
|
|
317
|
+
(type === 'warning' && message.startsWith('MCP servers need attention:'))) {
|
|
318
|
+
state.error = `Pi MCP startup failed: ${message}`;
|
|
319
|
+
}
|
|
234
320
|
},
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
321
|
+
onTerminalInput: () => () => { },
|
|
322
|
+
setStatus: () => { },
|
|
323
|
+
setWorkingMessage: () => { },
|
|
324
|
+
setWorkingVisible: () => { },
|
|
325
|
+
setWorkingIndicator: () => { },
|
|
326
|
+
setHiddenThinkingLabel: () => { },
|
|
327
|
+
setWidget: () => { },
|
|
328
|
+
setFooter: () => { },
|
|
329
|
+
setHeader: () => { },
|
|
330
|
+
setTitle: () => { },
|
|
331
|
+
pasteToEditor: () => { },
|
|
332
|
+
setEditorText: () => { },
|
|
333
|
+
getEditorText: () => '',
|
|
334
|
+
editor: async () => undefined,
|
|
335
|
+
custom: async () => undefined,
|
|
336
|
+
getToolsExpanded: () => false,
|
|
337
|
+
setToolsExpanded: () => { },
|
|
338
|
+
get theme() {
|
|
339
|
+
return undefined;
|
|
340
|
+
},
|
|
341
|
+
getAllThemes: () => [],
|
|
342
|
+
getTheme: () => undefined,
|
|
343
|
+
setTheme: () => ({ success: false, error: 'Headless conductor has no theme.' }),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async function observePiMcpStartup(session, servers, state) {
|
|
347
|
+
const names = Object.keys(servers ?? {});
|
|
348
|
+
if (names.length === 0 || typeof session.getAllTools !== 'function')
|
|
349
|
+
return;
|
|
350
|
+
const deadline = Date.now() + PI_MCP_STARTUP_OBSERVATION_MS;
|
|
351
|
+
while (Date.now() < deadline && !state.error) {
|
|
352
|
+
const tools = session.getAllTools();
|
|
353
|
+
if (names.every((name) => {
|
|
354
|
+
const namespace = name.replace(/[^A-Za-z0-9_]/g, '_');
|
|
355
|
+
return tools.some((tool) => tool.name.startsWith(`mcp__${namespace}__`));
|
|
356
|
+
})) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function toPiCodingAgentTool(tool) {
|
|
363
|
+
return {
|
|
364
|
+
name: tool.name,
|
|
365
|
+
label: tool.label,
|
|
366
|
+
description: tool.description,
|
|
367
|
+
parameters: tool.parameters,
|
|
368
|
+
execute: (toolCallId, params, signal, onUpdate) => tool.execute(toolCallId, params, signal, onUpdate),
|
|
369
|
+
};
|
|
240
370
|
}
|
|
241
371
|
function uniquePiTools(...groups) {
|
|
242
372
|
const tools = new Map();
|
|
243
373
|
for (const group of groups) {
|
|
244
374
|
for (const tool of group) {
|
|
245
|
-
if (!tools.has(tool.name))
|
|
375
|
+
if (!tools.has(tool.name))
|
|
246
376
|
tools.set(tool.name, tool);
|
|
247
|
-
}
|
|
248
377
|
}
|
|
249
378
|
}
|
|
250
379
|
return [...tools.values()];
|
|
251
380
|
}
|
|
381
|
+
function createPiSystemPromptExtension(systemPromptRef) {
|
|
382
|
+
return (pi) => {
|
|
383
|
+
pi.on('before_agent_start', (event) => {
|
|
384
|
+
// Change only Pi's custom preamble. MCP/codemode sections assembled by
|
|
385
|
+
// the official extensions remain intact on every request and resume.
|
|
386
|
+
event.systemPromptOptions.customPrompt = systemPromptRef.value;
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
export function loadPiMcpConfig(servers, options = {}) {
|
|
391
|
+
const resolution = {
|
|
392
|
+
cwd: options.cwd ?? process.cwd(),
|
|
393
|
+
env: options.env ?? process.env,
|
|
394
|
+
};
|
|
395
|
+
return {
|
|
396
|
+
servers: Object.entries(servers ?? {}).map(([name, config]) => ({
|
|
397
|
+
name,
|
|
398
|
+
config: toPiMcpServerConfig(name, config, resolution),
|
|
399
|
+
source: '<agents-ensemble-resolved-mcp.json>',
|
|
400
|
+
scope: 'project',
|
|
401
|
+
})),
|
|
402
|
+
autoEnableCodemode: true,
|
|
403
|
+
errors: [],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function toPiMcpServerConfig(name, config, resolution) {
|
|
407
|
+
if (config.type === 'sse') {
|
|
408
|
+
throw new Error(`MCP server "${name}" uses SSE, which Pi 1.0 does not support. ` +
|
|
409
|
+
'Use stdio or streamable HTTP instead.');
|
|
410
|
+
}
|
|
411
|
+
if (config.command) {
|
|
412
|
+
return {
|
|
413
|
+
type: 'stdio',
|
|
414
|
+
command: expandPiMcpString(config.command, name, 'command', resolution),
|
|
415
|
+
...(config.args
|
|
416
|
+
? {
|
|
417
|
+
args: config.args.map((value, index) => expandPiMcpString(value, name, `args[${index}]`, resolution)),
|
|
418
|
+
}
|
|
419
|
+
: {}),
|
|
420
|
+
...(config.env
|
|
421
|
+
? { env: expandPiMcpRecord(config.env, name, 'env', resolution) }
|
|
422
|
+
: {}),
|
|
423
|
+
...(config.cwd
|
|
424
|
+
? { cwd: expandPiMcpString(config.cwd, name, 'cwd', resolution) }
|
|
425
|
+
: {}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (config.url) {
|
|
429
|
+
return {
|
|
430
|
+
type: 'http',
|
|
431
|
+
url: expandPiMcpString(config.url, name, 'url', resolution),
|
|
432
|
+
...(config.headers
|
|
433
|
+
? { headers: expandPiMcpRecord(config.headers, name, 'headers', resolution) }
|
|
434
|
+
: {}),
|
|
435
|
+
...(config.auth
|
|
436
|
+
? {
|
|
437
|
+
oauth: {
|
|
438
|
+
clientId: expandPiMcpString(config.auth.CLIENT_ID, name, 'auth.CLIENT_ID', resolution),
|
|
439
|
+
...(config.auth.CLIENT_SECRET !== undefined
|
|
440
|
+
? {
|
|
441
|
+
clientSecret: expandPiMcpString(config.auth.CLIENT_SECRET, name, 'auth.CLIENT_SECRET', resolution),
|
|
442
|
+
}
|
|
443
|
+
: {}),
|
|
444
|
+
...(config.auth.scopes?.length
|
|
445
|
+
? {
|
|
446
|
+
scope: config.auth.scopes
|
|
447
|
+
.map((value, index) => expandPiMcpString(value, name, `auth.scopes[${index}]`, resolution))
|
|
448
|
+
.join(' '),
|
|
449
|
+
}
|
|
450
|
+
: {}),
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
: {}),
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
throw new Error(`MCP server "${name}" has no command or URL. ` +
|
|
457
|
+
'Use a valid resolved stdio or streamable HTTP configuration.');
|
|
458
|
+
}
|
|
459
|
+
function expandPiMcpRecord(values, server, field, resolution) {
|
|
460
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
461
|
+
key,
|
|
462
|
+
expandPiMcpString(value, server, `${field}.${key}`, resolution),
|
|
463
|
+
]));
|
|
464
|
+
}
|
|
465
|
+
function expandPiMcpString(value, server, field, resolution) {
|
|
466
|
+
return value.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}|\$\{workspaceFolder(Basename)?\}/g, (placeholder, variable, basenameSuffix) => {
|
|
467
|
+
if (variable) {
|
|
468
|
+
const resolved = resolution.env[variable];
|
|
469
|
+
if (resolved === undefined) {
|
|
470
|
+
throw new Error(`MCP server "${server}" references missing environment variable "${variable}" in ${field}.`);
|
|
471
|
+
}
|
|
472
|
+
return resolved;
|
|
473
|
+
}
|
|
474
|
+
return basenameSuffix ? basename(resolution.cwd) : resolution.cwd;
|
|
475
|
+
});
|
|
476
|
+
}
|
|
252
477
|
function withPiSkills(systemPrompt, resources) {
|
|
253
478
|
return `${systemPrompt}${formatPiSkillsForPrompt(resources.skills)}`;
|
|
254
479
|
}
|
|
255
|
-
function resolvePiModel(provider, modelId, providerConfig, builtInModel) {
|
|
480
|
+
function resolvePiModel(provider, modelId, providerConfig, builtInModel, registryModel) {
|
|
256
481
|
const definition = providerConfig?.models?.find((candidate) => candidate.id === modelId);
|
|
257
|
-
let model = definition && providerConfig
|
|
482
|
+
let model = registryModel ?? (definition && providerConfig
|
|
258
483
|
? createPiModel(provider, providerConfig, definition)
|
|
259
|
-
: builtInModel;
|
|
484
|
+
: builtInModel);
|
|
260
485
|
if (!model)
|
|
261
486
|
return undefined;
|
|
262
487
|
const override = providerConfig?.modelOverrides?.[modelId];
|
|
@@ -341,7 +566,16 @@ function numberValue(value, fallback) {
|
|
|
341
566
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
342
567
|
}
|
|
343
568
|
function mergePiHeaders(...headers) {
|
|
344
|
-
|
|
569
|
+
const merged = {};
|
|
570
|
+
for (const headerSet of headers) {
|
|
571
|
+
if (!headerSet)
|
|
572
|
+
continue;
|
|
573
|
+
for (const [name, value] of Object.entries(headerSet)) {
|
|
574
|
+
if (value !== null)
|
|
575
|
+
merged[name] = value;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return merged;
|
|
345
579
|
}
|
|
346
580
|
function mergePiObjects(...values) {
|
|
347
581
|
const merged = {};
|
|
@@ -383,50 +617,6 @@ function resolvePiModelSelection(requestedModelId, settings, models) {
|
|
|
383
617
|
}
|
|
384
618
|
return { provider, modelId };
|
|
385
619
|
}
|
|
386
|
-
function resolvePiAuthKey(authLayers, provider, env) {
|
|
387
|
-
for (let index = authLayers.length - 1; index >= 0; index -= 1) {
|
|
388
|
-
const value = resolvePiAuthEntry(authLayers[index]?.[provider], env);
|
|
389
|
-
if (value !== undefined)
|
|
390
|
-
return value;
|
|
391
|
-
}
|
|
392
|
-
return undefined;
|
|
393
|
-
}
|
|
394
|
-
function resolvePiAuthEntry(entry, env) {
|
|
395
|
-
if (typeof entry === 'string')
|
|
396
|
-
return expandEnvReference(entry, env);
|
|
397
|
-
if (!isRecord(entry))
|
|
398
|
-
return undefined;
|
|
399
|
-
for (const key of ['key', 'apiKey', 'token', 'accessToken', 'access']) {
|
|
400
|
-
const value = entry[key];
|
|
401
|
-
if (typeof value === 'string') {
|
|
402
|
-
return expandEnvReference(value, env);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
407
|
-
function resolvePiModelsApiKey(providerConfig, env) {
|
|
408
|
-
return providerConfig?.apiKey
|
|
409
|
-
? expandEnvReference(providerConfig.apiKey, env)
|
|
410
|
-
: undefined;
|
|
411
|
-
}
|
|
412
|
-
function resolvePiSettingsApiKey(settings, provider, env) {
|
|
413
|
-
if (typeof settings.apiKey === 'string') {
|
|
414
|
-
return expandEnvReference(settings.apiKey, env);
|
|
415
|
-
}
|
|
416
|
-
if (!isRecord(settings.apiKeys))
|
|
417
|
-
return undefined;
|
|
418
|
-
const value = settings.apiKeys[provider];
|
|
419
|
-
return typeof value === 'string' ? expandEnvReference(value, env) : undefined;
|
|
420
|
-
}
|
|
421
|
-
function expandEnvReference(value, env) {
|
|
422
|
-
if (value.startsWith('!')) {
|
|
423
|
-
// Pi supports command-backed credentials. Do not execute arbitrary commands
|
|
424
|
-
// from a harness config; #356/#358 can define a deliberate credential hook.
|
|
425
|
-
return undefined;
|
|
426
|
-
}
|
|
427
|
-
const variable = /^\$\{([^}]+)\}$/.exec(value) ?? /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
|
|
428
|
-
return variable ? env[variable[1]] : value;
|
|
429
|
-
}
|
|
430
620
|
function normalizeConfiguredString(value) {
|
|
431
621
|
if (typeof value !== 'string')
|
|
432
622
|
return undefined;
|
|
@@ -481,7 +671,4 @@ function isRecord(value) {
|
|
|
481
671
|
function errorMessage(error) {
|
|
482
672
|
return error instanceof Error ? error.message : String(error);
|
|
483
673
|
}
|
|
484
|
-
function toProviderEnv(env) {
|
|
485
|
-
return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === 'string'));
|
|
486
|
-
}
|
|
487
674
|
//# sourceMappingURL=pi-conductor-agent.js.map
|