@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.1

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 (101) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/package.json +8 -8
  3. package/src/async/index.ts +1 -0
  4. package/src/async/job-manager.ts +43 -10
  5. package/src/async/support.ts +5 -0
  6. package/src/cli/list-models.ts +96 -57
  7. package/src/commit/agentic/tools/analyze-file.ts +1 -2
  8. package/src/commit/model-selection.ts +16 -13
  9. package/src/config/mcp-schema.json +1 -1
  10. package/src/config/model-equivalence.ts +675 -0
  11. package/src/config/model-registry.ts +242 -45
  12. package/src/config/model-resolver.ts +282 -65
  13. package/src/config/settings-schema.ts +27 -3
  14. package/src/config/settings.ts +1 -1
  15. package/src/cursor.ts +64 -23
  16. package/src/edit/index.ts +254 -89
  17. package/src/edit/modes/chunk.ts +336 -57
  18. package/src/edit/modes/hashline.ts +51 -26
  19. package/src/edit/modes/patch.ts +16 -10
  20. package/src/edit/modes/replace.ts +15 -7
  21. package/src/edit/renderer.ts +248 -94
  22. package/src/export/html/template.css +82 -0
  23. package/src/export/html/template.generated.ts +1 -1
  24. package/src/export/html/template.js +614 -97
  25. package/src/extensibility/custom-tools/types.ts +0 -3
  26. package/src/extensibility/extensions/loader.ts +16 -0
  27. package/src/extensibility/extensions/runner.ts +2 -7
  28. package/src/extensibility/extensions/types.ts +8 -4
  29. package/src/internal-urls/docs-index.generated.ts +4 -4
  30. package/src/internal-urls/jobs-protocol.ts +2 -1
  31. package/src/ipy/executor.ts +447 -52
  32. package/src/ipy/kernel.ts +39 -13
  33. package/src/lsp/client.ts +55 -1
  34. package/src/lsp/index.ts +8 -0
  35. package/src/lsp/types.ts +6 -0
  36. package/src/main.ts +6 -2
  37. package/src/memories/index.ts +7 -6
  38. package/src/modes/acp/acp-agent.ts +4 -1
  39. package/src/modes/components/bash-execution.ts +16 -4
  40. package/src/modes/components/model-selector.ts +221 -64
  41. package/src/modes/components/status-line/presets.ts +17 -6
  42. package/src/modes/components/status-line/segments.ts +15 -0
  43. package/src/modes/components/status-line-segment-editor.ts +1 -0
  44. package/src/modes/components/status-line.ts +7 -1
  45. package/src/modes/components/tool-execution.ts +145 -75
  46. package/src/modes/controllers/command-controller.ts +42 -1
  47. package/src/modes/controllers/event-controller.ts +4 -1
  48. package/src/modes/controllers/extension-ui-controller.ts +28 -5
  49. package/src/modes/controllers/input-controller.ts +9 -3
  50. package/src/modes/controllers/selector-controller.ts +17 -6
  51. package/src/modes/interactive-mode.ts +19 -3
  52. package/src/modes/print-mode.ts +13 -4
  53. package/src/modes/prompt-action-autocomplete.ts +3 -5
  54. package/src/modes/rpc/rpc-mode.ts +8 -2
  55. package/src/modes/shared.ts +2 -2
  56. package/src/modes/types.ts +1 -0
  57. package/src/modes/utils/ui-helpers.ts +1 -0
  58. package/src/prompts/system/system-prompt.md +5 -1
  59. package/src/prompts/tools/bash.md +16 -1
  60. package/src/prompts/tools/cancel-job.md +1 -1
  61. package/src/prompts/tools/chunk-edit.md +191 -163
  62. package/src/prompts/tools/hashline.md +11 -11
  63. package/src/prompts/tools/patch.md +10 -5
  64. package/src/prompts/tools/{await.md → poll.md} +1 -1
  65. package/src/prompts/tools/read-chunk.md +12 -3
  66. package/src/prompts/tools/read.md +9 -0
  67. package/src/prompts/tools/task.md +2 -2
  68. package/src/prompts/tools/vim.md +98 -0
  69. package/src/prompts/tools/write.md +1 -0
  70. package/src/sdk.ts +758 -725
  71. package/src/session/agent-session.ts +187 -40
  72. package/src/session/session-manager.ts +50 -4
  73. package/src/slash-commands/builtin-registry.ts +17 -0
  74. package/src/task/executor.ts +9 -5
  75. package/src/task/index.ts +3 -5
  76. package/src/task/types.ts +2 -2
  77. package/src/tools/bash.ts +240 -57
  78. package/src/tools/cancel-job.ts +2 -1
  79. package/src/tools/find.ts +5 -2
  80. package/src/tools/grep.ts +77 -8
  81. package/src/tools/index.ts +48 -19
  82. package/src/tools/inspect-image.ts +1 -1
  83. package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
  84. package/src/tools/python.ts +293 -278
  85. package/src/tools/read.ts +218 -1
  86. package/src/tools/sqlite-reader.ts +623 -0
  87. package/src/tools/submit-result.ts +5 -2
  88. package/src/tools/todo-write.ts +8 -2
  89. package/src/tools/vim.ts +966 -0
  90. package/src/tools/write.ts +187 -1
  91. package/src/utils/commit-message-generator.ts +1 -0
  92. package/src/utils/edit-mode.ts +2 -1
  93. package/src/utils/git.ts +24 -1
  94. package/src/utils/session-color.ts +55 -0
  95. package/src/utils/title-generator.ts +16 -7
  96. package/src/vim/buffer.ts +309 -0
  97. package/src/vim/commands.ts +382 -0
  98. package/src/vim/engine.ts +2426 -0
  99. package/src/vim/parser.ts +151 -0
  100. package/src/vim/render.ts +252 -0
  101. package/src/vim/types.ts +197 -0
@@ -1,6 +1,5 @@
1
1
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
2
  import type { ToolChoice } from "@oh-my-pi/pi-ai";
3
- import type { SearchDb } from "@oh-my-pi/pi-natives";
4
3
  import { $env, $flag, isBunTestRuntime, logger } from "@oh-my-pi/pi-utils";
5
4
  import type { AsyncJobManager } from "../async";
6
5
  import type { PromptTemplate } from "../config/prompt-templates";
@@ -8,7 +7,7 @@ import type { Settings } from "../config/settings";
8
7
  import { EditTool } from "../edit";
9
8
  import type { Skill } from "../extensibility/skills";
10
9
  import type { InternalUrlRouter } from "../internal-urls";
11
- import { getPreludeDocs, warmPythonEnvironment } from "../ipy/executor";
10
+ import { getPreludeDocs, resetPreludeDocsCache, warmPythonEnvironment } from "../ipy/executor";
12
11
  import { checkPythonKernelAvailability } from "../ipy/kernel";
13
12
  import { LspTool } from "../lsp";
14
13
  import type { DiscoverableMCPSearchIndex, DiscoverableMCPTool } from "../mcp/discoverable-tool-metadata";
@@ -22,10 +21,8 @@ import { SearchTool } from "../web/search";
22
21
  import { AskTool } from "./ask";
23
22
  import { AstEditTool } from "./ast-edit";
24
23
  import { AstGrepTool } from "./ast-grep";
25
- import { AwaitTool } from "./await-tool";
26
24
  import { BashTool } from "./bash";
27
25
  import { BrowserTool } from "./browser";
28
-
29
26
  import { CalculatorTool } from "./calculator";
30
27
  import { CancelJobTool } from "./cancel-job";
31
28
  import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
@@ -47,6 +44,7 @@ import { GrepTool } from "./grep";
47
44
  import { InspectImageTool } from "./inspect-image";
48
45
  import { NotebookTool } from "./notebook";
49
46
  import { wrapToolWithMetaNotice } from "./output-meta";
47
+ import { PollTool } from "./poll-tool";
50
48
  import { PythonTool } from "./python";
51
49
  import { ReadTool } from "./read";
52
50
  import { RenderMermaidTool } from "./render-mermaid";
@@ -71,7 +69,6 @@ export * from "../web/search";
71
69
  export * from "./ask";
72
70
  export * from "./ast-edit";
73
71
  export * from "./ast-grep";
74
- export * from "./await-tool";
75
72
  export * from "./bash";
76
73
  export * from "./browser";
77
74
  export * from "./calculator";
@@ -85,6 +82,7 @@ export * from "./gh";
85
82
  export * from "./grep";
86
83
  export * from "./inspect-image";
87
84
  export * from "./notebook";
85
+ export * from "./poll-tool";
88
86
  export * from "./python";
89
87
  export * from "./read";
90
88
  export * from "./render-mermaid";
@@ -95,6 +93,7 @@ export * from "./search-tool-bm25";
95
93
  export * from "./ssh";
96
94
  export * from "./submit-result";
97
95
  export * from "./todo-write";
96
+ export * from "./vim";
98
97
  export * from "./write";
99
98
 
100
99
  /** Tool type (AgentTool from pi-ai) */
@@ -116,6 +115,8 @@ export interface ToolSession {
116
115
  hasUI: boolean;
117
116
  /** Skip Python kernel availability check and warmup */
118
117
  skipPythonPreflight?: boolean;
118
+ /** Force Python prelude warmup even when test env would normally skip it */
119
+ forcePythonWarmup?: boolean;
119
120
  /** Pre-loaded context files (AGENTS.md, etc) */
120
121
  contextFiles?: ContextFileEntry[];
121
122
  /** Pre-loaded skills */
@@ -124,7 +125,7 @@ export interface ToolSession {
124
125
  promptTemplates?: PromptTemplate[];
125
126
  /** Whether LSP integrations are enabled */
126
127
  enableLsp?: boolean;
127
- /** Whether the edit tool is available in this session (controls hashline output) */
128
+ /** Whether an edit-capable tool is available in this session (controls hashline output) */
128
129
  hasEditTool?: boolean;
129
130
  /** Event bus for tool/extension communication */
130
131
  eventBus?: EventBus;
@@ -136,6 +137,12 @@ export interface ToolSession {
136
137
  taskDepth?: number;
137
138
  /** Get session file */
138
139
  getSessionFile: () => string | null;
140
+ /** Get Python kernel owner ID for session-scoped retained-kernel cleanup */
141
+ getPythonKernelOwnerId?: () => string | null;
142
+ /** Reject new Python work once session disposal has started. */
143
+ assertPythonExecutionAllowed?: () => void;
144
+ /** Track tool-owned Python work so session disposal can await/abort it like direct session Python runs. */
145
+ trackPythonExecution?<T>(execution: Promise<T>, abortController: AbortController): Promise<T>;
139
146
  /** Get session ID */
140
147
  getSessionId?: () => string | null;
141
148
  /** Get artifacts directory for artifact:// URLs */
@@ -162,8 +169,6 @@ export interface ToolSession {
162
169
  asyncJobManager?: AsyncJobManager;
163
170
  /** Settings instance for passing to subagents */
164
171
  settings: Settings;
165
- /** Shared native search DB for grep/glob/fuzzyFind-backed workflows. */
166
- searchDb?: SearchDb;
167
172
  /** Plan mode state (if active) */
168
173
  getPlanModeState?: () => PlanModeState | undefined;
169
174
  /** Get compact conversation context for subagents (excludes tool results, system prompts) */
@@ -232,7 +237,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
232
237
  rewind: RewindTool.createIf,
233
238
  task: TaskTool.create,
234
239
  cancel_job: CancelJobTool.createIf,
235
- await: AwaitTool.createIf,
240
+ poll: PollTool.createIf,
236
241
  todo_write: s => new TodoWriteTool(s),
237
242
  web_search: s => new SearchTool(s),
238
243
  search_tool_bm25: SearchToolBm25Tool.createIf,
@@ -297,7 +302,11 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
297
302
  !skipPythonPreflight &&
298
303
  pythonMode !== "bash-only" &&
299
304
  (requestedTools === undefined || requestedTools.includes("python"));
300
- const skipPythonWarm = isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK");
305
+ const isTestEnv = isBunTestRuntime();
306
+ const forcePythonWarmup = session.forcePythonWarmup === true;
307
+ const skipPythonWarm = (isTestEnv && !forcePythonWarmup) || $flag("PI_PYTHON_SKIP_CHECK");
308
+ const cachedPreludeDocs = getPreludeDocs();
309
+ const shouldWarmPython = !skipPythonWarm && (forcePythonWarmup || cachedPreludeDocs.length === 0);
301
310
  if (shouldCheckPython) {
302
311
  const availability = await logger.time("createTools:pythonCheck", checkPythonKernelAvailability, session.cwd);
303
312
  pythonAvailable = availability.ok;
@@ -305,18 +314,38 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
305
314
  logger.warn("Python kernel unavailable, falling back to bash", {
306
315
  reason: availability.reason,
307
316
  });
308
- } else if (!skipPythonWarm && getPreludeDocs().length === 0) {
317
+ } else if (shouldWarmPython) {
309
318
  const sessionFile = session.getSessionFile?.() ?? undefined;
319
+ const kernelOwnerId = session.getPythonKernelOwnerId?.() ?? undefined;
310
320
  const warmSessionId = sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`;
321
+ const warmupAbortController = new AbortController();
311
322
  try {
312
- await logger.time(
313
- "createTools:warmPython",
314
- warmPythonEnvironment,
315
- session.cwd,
316
- warmSessionId,
317
- session.settings.get("python.sharedGateway"),
318
- sessionFile,
319
- );
323
+ session.assertPythonExecutionAllowed?.();
324
+ if (forcePythonWarmup && cachedPreludeDocs.length > 0) {
325
+ resetPreludeDocsCache();
326
+ }
327
+ const warmupExecution = session.trackPythonExecution
328
+ ? logger.time(
329
+ "createTools:warmPython",
330
+ warmPythonEnvironment,
331
+ session.cwd,
332
+ warmSessionId,
333
+ session.settings.get("python.sharedGateway"),
334
+ sessionFile,
335
+ kernelOwnerId,
336
+ warmupAbortController.signal,
337
+ )
338
+ : logger.time(
339
+ "createTools:warmPython",
340
+ warmPythonEnvironment,
341
+ session.cwd,
342
+ warmSessionId,
343
+ session.settings.get("python.sharedGateway"),
344
+ sessionFile,
345
+ kernelOwnerId,
346
+ );
347
+ await (session.trackPythonExecution?.(warmupExecution, warmupAbortController) ?? warmupExecution);
348
+ session.assertPythonExecutionAllowed?.();
320
349
  } catch (err) {
321
350
  logger.warn("Failed to warm Python environment", {
322
351
  error: err instanceof Error ? err.message : String(err),
@@ -79,7 +79,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
79
79
  const resolvePattern = (pattern: string | undefined): Model<Api> | undefined => {
80
80
  if (!pattern) return undefined;
81
81
  const expanded = expandRoleAlias(pattern, this.session.settings);
82
- return resolveModelFromString(expanded, availableModels, matchPreferences);
82
+ return resolveModelFromString(expanded, availableModels, matchPreferences, modelRegistry);
83
83
  };
84
84
 
85
85
  const activeModelPattern = this.session.getActiveModelString?.() ?? this.session.getModelString?.();
@@ -1,10 +1,11 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
2
  import { prompt } from "@oh-my-pi/pi-utils";
3
3
  import { type Static, Type } from "@sinclair/typebox";
4
- import awaitDescription from "../prompts/tools/await.md" with { type: "text" };
4
+ import { isBackgroundJobSupportEnabled } from "../async";
5
+ import pollDescription from "../prompts/tools/poll.md" with { type: "text" };
5
6
  import type { ToolSession } from "./index";
6
7
 
7
- const awaitSchema = Type.Object({
8
+ const pollSchema = Type.Object({
8
9
  jobs: Type.Optional(
9
10
  Type.Array(Type.String(), {
10
11
  description: "Specific job IDs to wait for. If omitted, waits for any running job.",
@@ -12,9 +13,9 @@ const awaitSchema = Type.Object({
12
13
  ),
13
14
  });
14
15
 
15
- type AwaitParams = Static<typeof awaitSchema>;
16
+ type PollParams = Static<typeof pollSchema>;
16
17
 
17
- interface AwaitResult {
18
+ interface PollResult {
18
19
  id: string;
19
20
  type: "bash" | "task";
20
21
  status: "running" | "completed" | "failed" | "cancelled";
@@ -24,33 +25,33 @@ interface AwaitResult {
24
25
  errorText?: string;
25
26
  }
26
27
 
27
- export interface AwaitToolDetails {
28
- jobs: AwaitResult[];
28
+ export interface PollToolDetails {
29
+ jobs: PollResult[];
29
30
  }
30
31
 
31
- export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails> {
32
- readonly name = "await";
33
- readonly label = "Await";
32
+ export class PollTool implements AgentTool<typeof pollSchema, PollToolDetails> {
33
+ readonly name = "poll";
34
+ readonly label = "Poll";
34
35
  readonly description: string;
35
- readonly parameters = awaitSchema;
36
+ readonly parameters = pollSchema;
36
37
  readonly strict = true;
37
38
 
38
39
  constructor(private readonly session: ToolSession) {
39
- this.description = prompt.render(awaitDescription);
40
+ this.description = prompt.render(pollDescription);
40
41
  }
41
42
 
42
- static createIf(session: ToolSession): AwaitTool | null {
43
- if (!session.settings.get("async.enabled")) return null;
44
- return new AwaitTool(session);
43
+ static createIf(session: ToolSession): PollTool | null {
44
+ if (!isBackgroundJobSupportEnabled(session.settings)) return null;
45
+ return new PollTool(session);
45
46
  }
46
47
 
47
48
  async execute(
48
49
  _toolCallId: string,
49
- params: AwaitParams,
50
+ params: PollParams,
50
51
  signal?: AbortSignal,
51
- _onUpdate?: AgentToolUpdateCallback<AwaitToolDetails>,
52
+ _onUpdate?: AgentToolUpdateCallback<PollToolDetails>,
52
53
  _context?: AgentToolContext,
53
- ): Promise<AgentToolResult<AwaitToolDetails>> {
54
+ ): Promise<AgentToolResult<PollToolDetails>> {
54
55
  const manager = this.session.asyncJobManager;
55
56
  if (!manager) {
56
57
  return {
@@ -84,19 +85,25 @@ export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails
84
85
 
85
86
  // Block until at least one running job finishes or the call is aborted
86
87
  const racePromises: Promise<unknown>[] = runningJobs.map(j => j.promise);
87
-
88
- if (signal) {
89
- const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<void>();
90
- const onAbort = () => abortResolve();
91
- signal.addEventListener("abort", onAbort, { once: true });
92
- racePromises.push(abortPromise);
93
- try {
88
+ const watchedJobIds = runningJobs.map(job => job.id);
89
+ manager.watchJobs(watchedJobIds);
90
+
91
+ try {
92
+ if (signal) {
93
+ const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<void>();
94
+ const onAbort = () => abortResolve();
95
+ signal.addEventListener("abort", onAbort, { once: true });
96
+ racePromises.push(abortPromise);
97
+ try {
98
+ await Promise.race(racePromises);
99
+ } finally {
100
+ signal.removeEventListener("abort", onAbort);
101
+ }
102
+ } else {
94
103
  await Promise.race(racePromises);
95
- } finally {
96
- signal.removeEventListener("abort", onAbort);
97
104
  }
98
- } else {
99
- await Promise.race(racePromises);
105
+ } finally {
106
+ manager.unwatchJobs(watchedJobIds);
100
107
  }
101
108
 
102
109
  if (signal?.aborted) {
@@ -117,12 +124,12 @@ export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails
117
124
  resultText?: string;
118
125
  errorText?: string;
119
126
  }[],
120
- ): AgentToolResult<AwaitToolDetails> {
127
+ ): AgentToolResult<PollToolDetails> {
121
128
  const now = Date.now();
122
- const jobResults: AwaitResult[] = jobs.map(j => ({
129
+ const jobResults: PollResult[] = jobs.map(j => ({
123
130
  id: j.id,
124
131
  type: j.type,
125
- status: j.status as AwaitResult["status"],
132
+ status: j.status as PollResult["status"],
126
133
  label: j.label,
127
134
  durationMs: Math.max(0, now - j.startTime),
128
135
  ...(j.resultText ? { resultText: j.resultText } : {}),