@oh-my-pi/pi-coding-agent 8.3.0 → 8.4.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.
@@ -5,10 +5,13 @@
5
5
  */
6
6
  import path from "node:path";
7
7
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
- import type { ToolSession } from "..";
8
+ import type { PromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
9
+ import type { Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
10
+ import { getPreludeDocs } from "@oh-my-pi/pi-coding-agent/ipy/executor";
11
+ import { checkPythonKernelAvailability } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
12
+ import type { ContextFileEntry, ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
9
13
  import type { ModelRegistry } from "../config/model-registry";
10
14
  import { formatModelString, parseModelPattern } from "../config/model-resolver";
11
- import { checkPythonKernelAvailability } from "../ipy/kernel";
12
15
  import { LspTool } from "../lsp";
13
16
  import type { LspParams } from "../lsp/types";
14
17
  import { callTool } from "../mcp/client";
@@ -37,6 +40,19 @@ import type {
37
40
  SubagentWorkerResponse,
38
41
  } from "./worker-protocol";
39
42
 
43
+ const DEFAULT_MODEL_ALIASES = new Set(["default", "pi/default", "omp/default"]);
44
+
45
+ function normalizeModelPatterns(value: string | string[] | undefined): string[] {
46
+ if (!value) return [];
47
+ if (Array.isArray(value)) {
48
+ return value.map(entry => entry.trim()).filter(Boolean);
49
+ }
50
+ return value
51
+ .split(",")
52
+ .map(entry => entry.trim())
53
+ .filter(Boolean);
54
+ }
55
+
40
56
  /** Options for worker execution */
41
57
  export interface ExecutorOptions {
42
58
  cwd: string;
@@ -47,7 +63,7 @@ export interface ExecutorOptions {
47
63
  index: number;
48
64
  id: string;
49
65
  context?: string;
50
- modelOverride?: string;
66
+ modelOverride?: string | string[];
51
67
  thinkingLevel?: ThinkingLevel;
52
68
  outputSchema?: unknown;
53
69
  enableLsp?: boolean;
@@ -57,6 +73,9 @@ export interface ExecutorOptions {
57
73
  persistArtifacts?: boolean;
58
74
  artifactsDir?: string;
59
75
  eventBus?: EventBus;
76
+ contextFiles?: ContextFileEntry[];
77
+ skills?: Skill[];
78
+ promptTemplates?: PromptTemplate[];
60
79
  mcpManager?: MCPManager;
61
80
  authStorage?: AuthStorage;
62
81
  modelRegistry?: ModelRegistry;
@@ -277,23 +296,30 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
277
296
  const serializedSettings = options.settingsManager?.serialize();
278
297
  const availableModels = options.modelRegistry?.getAvailable() ?? [];
279
298
 
280
- // Resolve model pattern to provider/modelId string
281
- const modelPattern = modelOverride ?? agent.model;
299
+ // Resolve model pattern list to provider/modelId string
300
+ const modelPatterns = normalizeModelPatterns(modelOverride ?? agent.model);
282
301
  let resolvedModel: string | undefined;
283
- if (modelPattern) {
284
- // Handle omp/<role> or pi/<role> aliases (e.g., "omp/slow", "pi/fast")
285
- let effectivePattern = modelPattern;
286
- const lower = modelPattern.toLowerCase();
287
- if (lower.startsWith("omp/") || lower.startsWith("pi/")) {
288
- const role = lower.startsWith("omp/") ? modelPattern.slice(4) : modelPattern.slice(3);
289
- const roles = serializedSettings?.modelRoles as Record<string, string> | undefined;
290
- const configured = roles?.[role] ?? roles?.[role.toLowerCase()];
291
- if (configured) {
292
- effectivePattern = configured;
302
+ if (modelPatterns.length > 0) {
303
+ const roles = serializedSettings?.modelRoles as Record<string, string> | undefined;
304
+ for (const pattern of modelPatterns) {
305
+ const normalized = pattern.trim().toLowerCase();
306
+ if (!normalized || DEFAULT_MODEL_ALIASES.has(normalized)) {
307
+ continue;
308
+ }
309
+ let effectivePattern = pattern;
310
+ if (normalized.startsWith("omp/") || normalized.startsWith("pi/")) {
311
+ const role = normalized.startsWith("omp/") ? pattern.slice(4) : pattern.slice(3);
312
+ const configured = roles?.[role] ?? roles?.[role.toLowerCase()];
313
+ if (configured) {
314
+ effectivePattern = configured;
315
+ }
316
+ }
317
+ const { model } = parseModelPattern(effectivePattern, availableModels);
318
+ if (model) {
319
+ resolvedModel = formatModelString(model);
320
+ break;
293
321
  }
294
322
  }
295
- const { model } = parseModelPattern(effectivePattern, availableModels);
296
- resolvedModel = model ? formatModelString(model) : undefined;
297
323
  }
298
324
  const sessionFile = subtaskSessionFile ?? null;
299
325
  const spawnsEnv = agent.spawns === undefined ? "" : agent.spawns === "*" ? "*" : agent.spawns.join(",");
@@ -307,6 +333,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
307
333
 
308
334
  const lspEnabled = enableLsp ?? true;
309
335
  const lspToolRequested = lspEnabled && (toolNames === undefined || toolNames.includes("lsp"));
336
+ const pythonPreludeDocs = getPreludeDocs();
337
+ const pythonPreludeDocsPayload = pythonPreludeDocs.length > 0 ? pythonPreludeDocs : undefined;
310
338
 
311
339
  let worker: Worker;
312
340
  try {
@@ -692,6 +720,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
692
720
  serializedAuth: options.authStorage?.serialize(),
693
721
  serializedModels: options.modelRegistry?.serialize(),
694
722
  serializedSettings,
723
+ pythonPreludeDocs: pythonPreludeDocsPayload,
724
+ contextFiles: options.contextFiles,
725
+ skills: options.skills,
726
+ promptTemplates: options.promptTemplates,
695
727
  mcpTools: options.mcpManager ? extractMCPToolMetadata(options.mcpManager) : undefined,
696
728
  pythonToolProxy: pythonProxyEnabled,
697
729
  lspToolProxy: Boolean(lspTool),
package/src/task/index.ts CHANGED
@@ -163,10 +163,14 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
163
163
  const { agent: agentName, context, output: outputSchema, isolated } = params;
164
164
  const isIsolated = isolated === true;
165
165
 
166
- const isDefaultModelAlias = (value: string | undefined): boolean => {
166
+ const isDefaultModelAlias = (value: string | string[] | undefined): boolean => {
167
167
  if (!value) return true;
168
- const normalized = value.trim().toLowerCase();
169
- return normalized === "default" || normalized === "pi/default" || normalized === "omp/default";
168
+ const values = Array.isArray(value) ? value : [value];
169
+ if (values.length === 0) return true;
170
+ return values.every(entry => {
171
+ const normalized = entry.trim().toLowerCase();
172
+ return normalized === "default" || normalized === "pi/default" || normalized === "omp/default";
173
+ });
170
174
  };
171
175
 
172
176
  // Validate agent exists
@@ -374,6 +378,9 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
374
378
 
375
379
  // Build full prompts with context prepended
376
380
  const tasksWithContext = tasksWithUniqueIds.map(t => renderTemplate(context, t));
381
+ const contextFiles = this.session.contextFiles;
382
+ const skills = this.session.skills;
383
+ const promptTemplates = this.session.promptTemplates;
377
384
 
378
385
  // Initialize progress for all tasks
379
386
  for (let i = 0; i < tasksWithContext.length; i++) {
@@ -427,6 +434,9 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
427
434
  modelRegistry: this.session.modelRegistry,
428
435
  settingsManager: this.session.settingsManager,
429
436
  mcpManager: this.session.mcpManager,
437
+ contextFiles,
438
+ skills,
439
+ promptTemplates,
430
440
  });
431
441
  }
432
442
 
@@ -467,6 +477,9 @@ export class TaskTool implements AgentTool<typeof taskSchema, TaskToolDetails, T
467
477
  modelRegistry: this.session.modelRegistry,
468
478
  settingsManager: this.session.settingsManager,
469
479
  mcpManager: this.session.mcpManager,
480
+ contextFiles,
481
+ skills,
482
+ promptTemplates,
470
483
  });
471
484
  const patch = await captureDeltaPatch(worktreeDir, baseline);
472
485
  const patchPath = path.join(effectiveArtifactsDir, `${task.id}.patch`);
package/src/task/types.ts CHANGED
@@ -97,7 +97,7 @@ export interface AgentDefinition {
97
97
  systemPrompt: string;
98
98
  tools?: string[];
99
99
  spawns?: string[] | "*";
100
- model?: string;
100
+ model?: string[];
101
101
  thinkingLevel?: ThinkingLevel;
102
102
  output?: unknown;
103
103
  source: AgentSource;
@@ -122,7 +122,7 @@ export interface AgentProgress {
122
122
  toolCount: number;
123
123
  tokens: number;
124
124
  durationMs: number;
125
- modelOverride?: string;
125
+ modelOverride?: string | string[];
126
126
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
127
127
  extractedToolData?: Record<string, unknown[]>;
128
128
  }
@@ -142,7 +142,7 @@ export interface SingleResult {
142
142
  truncated: boolean;
143
143
  durationMs: number;
144
144
  tokens: number;
145
- modelOverride?: string;
145
+ modelOverride?: string | string[];
146
146
  error?: string;
147
147
  aborted?: boolean;
148
148
  /** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
@@ -1,4 +1,8 @@
1
1
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
+ import type { PromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
3
+ import type { Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
4
+ import type { PreludeHelper } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
5
+ import type { ContextFileEntry } from "@oh-my-pi/pi-coding-agent/tools";
2
6
  import type { SerializedModelRegistry } from "../config/model-registry";
3
7
  import type { Settings } from "../config/settings-manager";
4
8
  import type { SerializedAuthStorage } from "../session/auth-storage";
@@ -100,6 +104,10 @@ export interface SubagentWorkerStartPayload {
100
104
  serializedAuth?: SerializedAuthStorage;
101
105
  serializedModels?: SerializedModelRegistry;
102
106
  serializedSettings?: Settings;
107
+ pythonPreludeDocs?: PreludeHelper[];
108
+ contextFiles?: ContextFileEntry[];
109
+ skills?: Skill[];
110
+ promptTemplates?: PromptTemplate[];
103
111
  mcpTools?: MCPToolMetadata[];
104
112
  pythonToolProxy?: boolean;
105
113
  lspToolProxy?: boolean;
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
16
16
  import type { Api, Model } from "@oh-my-pi/pi-ai";
17
+ import { setPreludeDocsCache } from "@oh-my-pi/pi-coding-agent/ipy/executor";
17
18
  import { logger, postmortem, untilAborted } from "@oh-my-pi/pi-utils";
18
19
  import type { TSchema } from "@sinclair/typebox";
19
20
  import { ModelRegistry } from "../config/model-registry";
@@ -527,6 +528,9 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
527
528
  let error: string | undefined;
528
529
  let aborted = false;
529
530
  const sessionAbortController = new AbortController();
531
+ if (payload.pythonPreludeDocs && payload.pythonPreludeDocs.length > 0) {
532
+ setPreludeDocsCache(payload.pythonPreludeDocs);
533
+ }
530
534
 
531
535
  // Helper to check abort status - throws if aborted to exit early
532
536
  const checkAbort = (): void => {
@@ -589,6 +593,9 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
589
593
  ? `You will work under this working tree: ${payload.worktree}. CRITICAL: Do not touch the original repository; only make changes inside this worktree.`
590
594
  : "";
591
595
 
596
+ const skipPythonPreflight =
597
+ payload.pythonToolProxy === true ||
598
+ (Array.isArray(payload.toolNames) && !payload.toolNames.includes("python"));
592
599
  const { session } = await createAgentSession({
593
600
  cwd: payload.worktree ?? payload.cwd,
594
601
  authStorage,
@@ -599,6 +606,9 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
599
606
  toolNames: payload.toolNames,
600
607
  outputSchema: payload.outputSchema,
601
608
  requireCompleteTool: true,
609
+ contextFiles: payload.contextFiles,
610
+ skills: payload.skills,
611
+ promptTemplates: payload.promptTemplates,
602
612
  // Append system prompt (equivalent to CLI's --append-system-prompt)
603
613
  systemPrompt: defaultPrompt =>
604
614
  `${defaultPrompt}\n\n${payload.systemPrompt}\n\n${worktreeNotice}\n\n${completionInstruction}`,
@@ -607,6 +617,7 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
607
617
  // Pass spawn restrictions to nested tasks
608
618
  spawns: payload.spawnsEnv,
609
619
  enableLsp: enableLsp && !lspProxyEnabled,
620
+ skipPythonPreflight,
610
621
  // Disable local MCP discovery if using proxy tools
611
622
  enableMCP: !payload.mcpTools,
612
623
  // Add proxy tools
@@ -1,4 +1,6 @@
1
1
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
+ import type { PromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
3
+ import type { Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
2
4
  import { logger } from "@oh-my-pi/pi-utils";
3
5
  import type { BashInterceptorRule } from "../config/settings-manager";
4
6
  import type { InternalUrlRouter } from "../internal-urls";
@@ -94,12 +96,26 @@ export { WriteTool, type WriteToolDetails } from "./write";
94
96
  /** Tool type (AgentTool from pi-ai) */
95
97
  export type Tool = AgentTool<any, any, any>;
96
98
 
99
+ export type ContextFileEntry = {
100
+ path: string;
101
+ content: string;
102
+ depth?: number;
103
+ };
104
+
97
105
  /** Session context for tool factories */
98
106
  export interface ToolSession {
99
107
  /** Current working directory */
100
108
  cwd: string;
101
109
  /** Whether UI is available */
102
110
  hasUI: boolean;
111
+ /** Skip Python kernel availability check and warmup */
112
+ skipPythonPreflight?: boolean;
113
+ /** Pre-loaded context files (AGENTS.md, etc) */
114
+ contextFiles?: ContextFileEntry[];
115
+ /** Pre-loaded skills */
116
+ skills?: Skill[];
117
+ /** Pre-loaded prompt templates */
118
+ promptTemplates?: PromptTemplate[];
103
119
  /** Whether LSP integrations are enabled */
104
120
  enableLsp?: boolean;
105
121
  /** Event bus for tool/extension communication */
@@ -219,10 +235,12 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
219
235
  const enableLsp = session.enableLsp ?? true;
220
236
  const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
221
237
  const pythonMode = getPythonModeFromEnv() ?? session.settings?.getPythonToolMode?.() ?? "ipy-only";
238
+ const skipPythonPreflight = session.skipPythonPreflight === true;
222
239
  let pythonAvailable = true;
223
240
  const shouldCheckPython =
241
+ !skipPythonPreflight &&
224
242
  pythonMode !== "bash-only" &&
225
- (requestedTools === undefined || requestedTools.includes("python") || pythonMode === "ipy-only");
243
+ (requestedTools === undefined || requestedTools.includes("python"));
226
244
  const isTestEnv = process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test";
227
245
  const skipPythonWarm = isTestEnv || process.env.OMP_PYTHON_SKIP_CHECK === "1";
228
246
  if (shouldCheckPython) {