@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.
Files changed (112) hide show
  1. package/dist/conductor/conductor-auth.d.ts +36 -6
  2. package/dist/conductor/conductor-auth.d.ts.map +1 -1
  3. package/dist/conductor/conductor-auth.js +59 -7
  4. package/dist/conductor/conductor-auth.js.map +1 -1
  5. package/dist/conductor/conductor-pi-auth.d.ts +75 -0
  6. package/dist/conductor/conductor-pi-auth.d.ts.map +1 -0
  7. package/dist/conductor/conductor-pi-auth.js +656 -0
  8. package/dist/conductor/conductor-pi-auth.js.map +1 -0
  9. package/dist/conductor/conductor-session-driver.d.ts +2 -0
  10. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  11. package/dist/conductor/conductor-session-driver.js +18 -1
  12. package/dist/conductor/conductor-session-driver.js.map +1 -1
  13. package/dist/conductor/conductor-session.d.ts.map +1 -1
  14. package/dist/conductor/conductor-session.js +69 -7
  15. package/dist/conductor/conductor-session.js.map +1 -1
  16. package/dist/conductor/conductor-tool-pi-adapter.d.ts +6 -8
  17. package/dist/conductor/conductor-tool-pi-adapter.d.ts.map +1 -1
  18. package/dist/conductor/conductor-tool-pi-adapter.js +5 -8
  19. package/dist/conductor/conductor-tool-pi-adapter.js.map +1 -1
  20. package/dist/conductor/index.d.ts +7 -8
  21. package/dist/conductor/index.d.ts.map +1 -1
  22. package/dist/conductor/index.js +4 -4
  23. package/dist/conductor/index.js.map +1 -1
  24. package/dist/conductor/list-conductor-models.d.ts +7 -2
  25. package/dist/conductor/list-conductor-models.d.ts.map +1 -1
  26. package/dist/conductor/list-conductor-models.js +9 -1
  27. package/dist/conductor/list-conductor-models.js.map +1 -1
  28. package/dist/conductor/pi-conductor-agent.d.ts +23 -6
  29. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  30. package/dist/conductor/pi-conductor-agent.js +317 -130
  31. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  32. package/dist/conductor/pi-resource-loader.d.ts +7 -4
  33. package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
  34. package/dist/conductor/pi-resource-loader.js +6 -4
  35. package/dist/conductor/pi-resource-loader.js.map +1 -1
  36. package/dist/conductor/session/dispatch-hold.d.ts +14 -0
  37. package/dist/conductor/session/dispatch-hold.d.ts.map +1 -1
  38. package/dist/conductor/session/dispatch-hold.js +30 -0
  39. package/dist/conductor/session/dispatch-hold.js.map +1 -1
  40. package/dist/conductor/session/events/index.d.ts +1 -1
  41. package/dist/conductor/session/events/index.d.ts.map +1 -1
  42. package/dist/conductor/session/events/index.js.map +1 -1
  43. package/dist/conductor/session/events/session-log-event-groups.d.ts +2 -2
  44. package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
  45. package/dist/conductor/session/events/session-log-event-groups.js +1 -0
  46. package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
  47. package/dist/conductor/session/events/session-log-event.d.ts +10 -1
  48. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  49. package/dist/conductor/session/format-session-event.js +3 -0
  50. package/dist/conductor/session/format-session-event.js.map +1 -1
  51. package/dist/conductor/session/session-logger.d.ts.map +1 -1
  52. package/dist/conductor/session/session-logger.js +1 -0
  53. package/dist/conductor/session/session-logger.js.map +1 -1
  54. package/dist/github/fetch-github-updates.d.ts.map +1 -1
  55. package/dist/github/fetch-github-updates.js +109 -80
  56. package/dist/github/fetch-github-updates.js.map +1 -1
  57. package/dist/github/github-client.d.ts.map +1 -1
  58. package/dist/github/github-client.js +8 -0
  59. package/dist/github/github-client.js.map +1 -1
  60. package/dist/github/github-monitor-cursor.d.ts +19 -8
  61. package/dist/github/github-monitor-cursor.d.ts.map +1 -1
  62. package/dist/github/github-monitor-cursor.js +8 -52
  63. package/dist/github/github-monitor-cursor.js.map +1 -1
  64. package/dist/github/github-monitor.d.ts +2 -0
  65. package/dist/github/github-monitor.d.ts.map +1 -1
  66. package/dist/github/github-monitor.js +13 -0
  67. package/dist/github/github-monitor.js.map +1 -1
  68. package/dist/github/github-test-fixtures.d.ts +9 -0
  69. package/dist/github/github-test-fixtures.d.ts.map +1 -1
  70. package/dist/github/github-test-fixtures.js +6 -0
  71. package/dist/github/github-test-fixtures.js.map +1 -1
  72. package/dist/github/github-update-types.d.ts +10 -0
  73. package/dist/github/github-update-types.d.ts.map +1 -1
  74. package/dist/github/index.d.ts +3 -1
  75. package/dist/github/index.d.ts.map +1 -1
  76. package/dist/github/index.js +1 -0
  77. package/dist/github/index.js.map +1 -1
  78. package/dist/github/register-github-watch-tool.d.ts +5 -0
  79. package/dist/github/register-github-watch-tool.d.ts.map +1 -1
  80. package/dist/github/register-github-watch-tool.js +30 -24
  81. package/dist/github/register-github-watch-tool.js.map +1 -1
  82. package/dist/github/unregister-github-watch-tool.d.ts +11 -0
  83. package/dist/github/unregister-github-watch-tool.d.ts.map +1 -0
  84. package/dist/github/unregister-github-watch-tool.js +52 -0
  85. package/dist/github/unregister-github-watch-tool.js.map +1 -0
  86. package/dist/mcp/load-mcp-config.d.ts +8 -0
  87. package/dist/mcp/load-mcp-config.d.ts.map +1 -1
  88. package/dist/mcp/load-mcp-config.js +24 -0
  89. package/dist/mcp/load-mcp-config.js.map +1 -1
  90. package/dist/prompt/modules/ensemble/base-module.d.ts.map +1 -1
  91. package/dist/prompt/modules/ensemble/base-module.js +2 -1
  92. package/dist/prompt/modules/ensemble/base-module.js.map +1 -1
  93. package/dist/prompt/modules/ensemble/conductor-base-module.d.ts.map +1 -1
  94. package/dist/prompt/modules/ensemble/conductor-base-module.js +4 -1
  95. package/dist/prompt/modules/ensemble/conductor-base-module.js.map +1 -1
  96. package/dist/runtime/worker-session.d.ts +5 -0
  97. package/dist/runtime/worker-session.d.ts.map +1 -1
  98. package/dist/runtime/worker-session.js +5 -1
  99. package/dist/runtime/worker-session.js.map +1 -1
  100. package/dist/session/session-sidecar.d.ts +6 -0
  101. package/dist/session/session-sidecar.d.ts.map +1 -1
  102. package/dist/session/session-sidecar.js +17 -0
  103. package/dist/session/session-sidecar.js.map +1 -1
  104. package/package.json +4 -4
  105. package/dist/conductor/pi-conductor-session.d.ts +0 -20
  106. package/dist/conductor/pi-conductor-session.d.ts.map +0 -1
  107. package/dist/conductor/pi-conductor-session.js +0 -60
  108. package/dist/conductor/pi-conductor-session.js.map +0 -1
  109. package/dist/conductor/pi-mcp-bridge.d.ts +0 -65
  110. package/dist/conductor/pi-mcp-bridge.d.ts.map +0 -1
  111. package/dist/conductor/pi-mcp-bridge.js +0 -259
  112. package/dist/conductor/pi-mcp-bridge.js.map +0 -1
@@ -1,46 +1,58 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { Agent, } from '@earendil-works/pi-agent-core';
3
- import { getEnvApiKey, getModel, getModels, getProviders, } from '@earendil-works/pi-ai/compat';
4
- import { toPiAgentTools } from './conductor-tool-pi-adapter.js';
5
- import { PiConductorSession } from './pi-conductor-session.js';
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
- import { createPiMcpBridge, } from './pi-mcp-bridge.js';
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
- return resolvePiModelConfigFromResources(options, resources);
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(agent, session, mcpBridge, resources, agentId, modelId, onStreamText) {
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.agent.subscribe((event) => this.handleEvent(event));
72
+ this.unsubscribe = this.session.subscribe((event) => this.handleEvent(event));
61
73
  }
62
74
  static async create(options) {
63
- const agentId = randomUUID();
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
- const resources = await loadPiResources(options);
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.agent.prompt(expandPiResourcePrompt(prompt, this.resources));
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.agent.state.messages);
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
- /** Pi's core agent has no reload operation; settings are loaded at create. */
147
- async reload() { }
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.agent.state.systemPrompt = withPiSkills(systemPrompt, this.resources);
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.agent.abort();
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
- await this.mcpBridge?.close();
178
+ this.session.dispose();
179
179
  }
180
180
  }
181
181
  })();
182
182
  return this.closePromise;
183
183
  }
184
- async handleEvent(event) {
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 createPiAgent(agentId, options, resolved, messages, mcpBridge, resources) {
225
- const tools = uniquePiTools(options.customTools ? toPiAgentTools(options.customTools) : [], mcpBridge?.tools ?? [], resources.extensionTools);
226
- return new Agent({
227
- initialState: {
228
- systemPrompt: withPiSkills(options.systemPrompt, resources),
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
- // Do not add Pi's built-in coding tools. Harness tools are the complete
231
- // loadout; repository work is delegated to worker ACP sessions.
232
- tools,
233
- messages: [...messages],
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
- sessionId: agentId,
236
- getApiKey: (provider) => provider === resolved.provider
237
- ? (resolved.apiKey ?? getEnvApiKey(provider))
238
- : getEnvApiKey(provider),
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
- return Object.assign({}, ...headers.filter((value) => Boolean(value)));
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