@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.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 (168) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/examples/custom-tools/README.md +11 -7
  3. package/examples/custom-tools/hello/index.ts +2 -2
  4. package/examples/extensions/README.md +19 -8
  5. package/examples/extensions/api-demo.ts +15 -19
  6. package/examples/extensions/hello.ts +5 -6
  7. package/examples/extensions/plan-mode.ts +1 -1
  8. package/examples/extensions/reload-runtime.ts +4 -3
  9. package/examples/extensions/with-deps/index.ts +4 -3
  10. package/examples/sdk/06-extensions.ts +4 -2
  11. package/package.json +8 -18
  12. package/src/autoresearch/tools/init-experiment.ts +38 -41
  13. package/src/autoresearch/tools/log-experiment.ts +32 -41
  14. package/src/autoresearch/tools/run-experiment.ts +3 -3
  15. package/src/autoresearch/tools/update-notes.ts +11 -11
  16. package/src/commands/commit.ts +10 -0
  17. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  18. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  19. package/src/commit/agentic/tools/git-hunk.ts +5 -5
  20. package/src/commit/agentic/tools/git-overview.ts +4 -4
  21. package/src/commit/agentic/tools/propose-changelog.ts +13 -13
  22. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  23. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  24. package/src/commit/agentic/tools/schemas.ts +28 -28
  25. package/src/commit/agentic/tools/split-commit.ts +22 -21
  26. package/src/commit/analysis/summary.ts +4 -4
  27. package/src/commit/changelog/generate.ts +7 -11
  28. package/src/commit/shared-llm.ts +22 -34
  29. package/src/config/config-file.ts +35 -13
  30. package/src/config/model-registry.ts +40 -191
  31. package/src/config/models-config-schema.ts +166 -0
  32. package/src/config/settings-schema.ts +29 -0
  33. package/src/discovery/claude-plugins.ts +19 -7
  34. package/src/edit/index.ts +2 -2
  35. package/src/edit/modes/apply-patch.ts +7 -6
  36. package/src/edit/modes/patch.ts +18 -25
  37. package/src/edit/modes/replace.ts +18 -20
  38. package/src/eval/js/shared/rewrite-imports.ts +131 -10
  39. package/src/eval/py/executor.ts +233 -623
  40. package/src/eval/py/kernel.ts +27 -2
  41. package/src/eval/py/runner.py +42 -11
  42. package/src/eval/py/runtime.ts +1 -0
  43. package/src/exa/factory.ts +5 -4
  44. package/src/exa/mcp-client.ts +1 -1
  45. package/src/exa/researcher.ts +9 -20
  46. package/src/exa/search.ts +26 -52
  47. package/src/exa/types.ts +1 -1
  48. package/src/exa/websets.ts +54 -53
  49. package/src/exec/bash-executor.ts +2 -1
  50. package/src/extensibility/custom-commands/loader.ts +5 -3
  51. package/src/extensibility/custom-commands/types.ts +4 -2
  52. package/src/extensibility/custom-tools/loader.ts +5 -3
  53. package/src/extensibility/custom-tools/types.ts +7 -6
  54. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  55. package/src/extensibility/extensions/get-commands-handler.ts +77 -0
  56. package/src/extensibility/extensions/loader.ts +7 -3
  57. package/src/extensibility/extensions/types.ts +9 -5
  58. package/src/extensibility/extensions/wrapper.ts +1 -2
  59. package/src/extensibility/hooks/loader.ts +3 -1
  60. package/src/extensibility/hooks/tool-wrapper.ts +1 -1
  61. package/src/extensibility/hooks/types.ts +4 -2
  62. package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
  63. package/src/extensibility/shared-events.ts +1 -1
  64. package/src/extensibility/typebox.ts +391 -0
  65. package/src/goals/tools/goal-tool.ts +6 -12
  66. package/src/hashline/input.ts +2 -1
  67. package/src/hashline/parser.ts +27 -3
  68. package/src/hashline/types.ts +4 -4
  69. package/src/hindsight/state.ts +2 -2
  70. package/src/index.ts +0 -2
  71. package/src/internal-urls/docs-index.generated.ts +15 -15
  72. package/src/internal-urls/router.ts +8 -0
  73. package/src/internal-urls/types.ts +21 -0
  74. package/src/lsp/config.ts +15 -6
  75. package/src/lsp/defaults.json +6 -2
  76. package/src/lsp/types.ts +30 -38
  77. package/src/mcp/manager.ts +1 -1
  78. package/src/mcp/tool-bridge.ts +1 -1
  79. package/src/modes/acp/acp-agent.ts +248 -50
  80. package/src/modes/components/session-observer-overlay.ts +12 -1
  81. package/src/modes/components/status-line/segments.ts +39 -4
  82. package/src/modes/controllers/command-controller.ts +27 -2
  83. package/src/modes/controllers/event-controller.ts +3 -4
  84. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  85. package/src/modes/interactive-mode.ts +1 -1
  86. package/src/modes/rpc/host-tools.ts +1 -1
  87. package/src/modes/rpc/host-uris.ts +235 -0
  88. package/src/modes/rpc/rpc-client.ts +1 -1
  89. package/src/modes/rpc/rpc-mode.ts +27 -1
  90. package/src/modes/rpc/rpc-types.ts +58 -1
  91. package/src/modes/runtime-init.ts +2 -1
  92. package/src/modes/theme/defaults/dark-poimandres.json +1 -0
  93. package/src/modes/theme/defaults/light-poimandres.json +1 -0
  94. package/src/modes/theme/theme.ts +117 -117
  95. package/src/modes/types.ts +1 -1
  96. package/src/modes/utils/context-usage.ts +2 -2
  97. package/src/prompts/tools/github.md +4 -4
  98. package/src/prompts/tools/hashline.md +22 -26
  99. package/src/prompts/tools/read.md +55 -37
  100. package/src/sdk.ts +31 -8
  101. package/src/session/agent-session.ts +74 -104
  102. package/src/session/messages.ts +16 -51
  103. package/src/session/session-manager.ts +22 -2
  104. package/src/session/streaming-output.ts +16 -6
  105. package/src/task/discovery.ts +5 -2
  106. package/src/task/executor.ts +210 -87
  107. package/src/task/index.ts +15 -11
  108. package/src/task/render.ts +32 -5
  109. package/src/task/types.ts +54 -39
  110. package/src/tools/ask.ts +12 -12
  111. package/src/tools/ast-edit.ts +11 -15
  112. package/src/tools/ast-grep.ts +9 -10
  113. package/src/tools/bash-command-fixup.ts +47 -0
  114. package/src/tools/bash.ts +48 -38
  115. package/src/tools/browser/render.ts +2 -2
  116. package/src/tools/browser.ts +39 -53
  117. package/src/tools/calculator.ts +12 -11
  118. package/src/tools/checkpoint.ts +7 -7
  119. package/src/tools/debug.ts +40 -43
  120. package/src/tools/eval.ts +16 -10
  121. package/src/tools/find.ts +10 -13
  122. package/src/tools/gh.ts +108 -132
  123. package/src/tools/hindsight-recall.ts +4 -6
  124. package/src/tools/hindsight-reflect.ts +5 -5
  125. package/src/tools/hindsight-retain.ts +15 -17
  126. package/src/tools/image-gen.ts +31 -81
  127. package/src/tools/index.ts +4 -1
  128. package/src/tools/inspect-image.ts +8 -9
  129. package/src/tools/irc.ts +15 -27
  130. package/src/tools/job.ts +30 -28
  131. package/src/tools/output-meta.ts +26 -0
  132. package/src/tools/read.ts +39 -12
  133. package/src/tools/recipe/index.ts +7 -9
  134. package/src/tools/render-mermaid.ts +12 -12
  135. package/src/tools/report-tool-issue.ts +4 -4
  136. package/src/tools/resolve.ts +11 -11
  137. package/src/tools/review.ts +14 -26
  138. package/src/tools/search-tool-bm25.ts +7 -9
  139. package/src/tools/search.ts +19 -22
  140. package/src/tools/ssh.ts +10 -9
  141. package/src/tools/todo-write.ts +26 -34
  142. package/src/tools/vim.ts +10 -26
  143. package/src/tools/write.ts +25 -5
  144. package/src/tools/yield.ts +100 -54
  145. package/src/web/search/index.ts +9 -24
  146. package/src/web/search/providers/anthropic.ts +5 -0
  147. package/src/web/search/providers/exa.ts +3 -0
  148. package/src/web/search/providers/gemini.ts +5 -0
  149. package/src/web/search/providers/jina.ts +5 -2
  150. package/src/web/search/providers/zai.ts +5 -2
  151. package/src/prompts/compaction/branch-summary-context.md +0 -5
  152. package/src/prompts/compaction/branch-summary-preamble.md +0 -2
  153. package/src/prompts/compaction/branch-summary.md +0 -30
  154. package/src/prompts/compaction/compaction-short-summary.md +0 -9
  155. package/src/prompts/compaction/compaction-summary-context.md +0 -5
  156. package/src/prompts/compaction/compaction-summary.md +0 -38
  157. package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
  158. package/src/prompts/compaction/compaction-update-summary.md +0 -45
  159. package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
  160. package/src/prompts/system/file-operations.md +0 -10
  161. package/src/prompts/system/handoff-document.md +0 -49
  162. package/src/prompts/system/summarization-system.md +0 -3
  163. package/src/session/compaction/branch-summarization.ts +0 -324
  164. package/src/session/compaction/compaction.ts +0 -1420
  165. package/src/session/compaction/errors.ts +0 -31
  166. package/src/session/compaction/index.ts +0 -8
  167. package/src/session/compaction/pruning.ts +0 -91
  168. package/src/session/compaction/utils.ts +0 -184
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Custom tool loader - loads TypeScript tool modules using native Bun import.
3
3
  *
4
- * Dependencies (@sinclair/typebox and pi-coding-agent) are injected via the CustomToolAPI
5
- * to avoid import resolution issues with custom tools loaded from user directories.
4
+ * Dependencies (the zod-backed typebox shim and pi-coding-agent) are injected via the
5
+ * CustomToolAPI to avoid import resolution issues with custom tools loaded from user directories.
6
6
  */
7
7
  import * as path from "node:path";
8
8
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
9
9
  import { logger } from "@oh-my-pi/pi-utils";
10
- import * as typebox from "@sinclair/typebox";
10
+ import * as z from "zod/v4";
11
11
  import { toolCapability } from "../../capability/tool";
12
12
  import { type CustomTool, loadCapability } from "../../discovery";
13
13
  import type { ExecOptions } from "../../exec/exec";
14
14
  import { execCommand } from "../../exec/exec";
15
15
  import type { HookUIContext } from "../../extensibility/hooks/types";
16
16
  import { getAllPluginToolPaths } from "../../extensibility/plugins/loader";
17
+ import * as typebox from "../typebox";
17
18
  import { createNoOpUIContext, resolvePath } from "../utils";
18
19
  import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
19
20
 
@@ -103,6 +104,7 @@ export class CustomToolLoader {
103
104
  hasUI: false,
104
105
  logger,
105
106
  typebox,
107
+ zod: z,
106
108
  pi,
107
109
  pushPendingAction: action => {
108
110
  if (!pushPendingAction) {
@@ -5,16 +5,15 @@
5
5
  * They can provide custom rendering for tool calls and results in the TUI.
6
6
  */
7
7
  import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
8
- import type { Model } from "@oh-my-pi/pi-ai";
8
+ import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
9
+ import type { Model, Static, TSchema } from "@oh-my-pi/pi-ai";
9
10
  import type { Component } from "@oh-my-pi/pi-tui";
10
- import type { Static, TSchema } from "@sinclair/typebox";
11
11
  import type { Rule } from "../../capability/rule";
12
12
  import type { ModelRegistry } from "../../config/model-registry";
13
13
  import type { Settings } from "../../config/settings";
14
14
  import type { ExecOptions, ExecResult } from "../../exec/exec";
15
15
  import type { HookUIContext } from "../../extensibility/hooks/types";
16
16
  import type { Theme } from "../../modes/theme/theme";
17
- import type { CompactionResult } from "../../session/compaction";
18
17
  import type { ReadonlySessionManager } from "../../session/session-manager";
19
18
  import type { TodoItem } from "../../tools/todo-write";
20
19
 
@@ -52,8 +51,10 @@ export interface CustomToolAPI {
52
51
  hasUI: boolean;
53
52
  /** File logger for error/warning/debug messages */
54
53
  logger: typeof import("@oh-my-pi/pi-utils").logger;
55
- /** Injected @sinclair/typebox module */
56
- typebox: typeof import("@sinclair/typebox");
54
+ /** Injected zod-backed typebox shim (legacy/compat — Zod-authored tools are preferred). */
55
+ typebox: typeof import("../typebox");
56
+ /** Injected zod module for Zod-authored custom tools. */
57
+ zod: typeof import("zod/v4");
57
58
  /** Injected pi-coding-agent exports */
58
59
  pi: typeof import("../..");
59
60
  /** Push a preview action that can later be resolved with the hidden resolve tool */
@@ -180,7 +181,7 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
180
181
  strict?: boolean;
181
182
  /** Description for LLM */
182
183
  description: string;
183
- /** Parameter schema (TypeBox) */
184
+ /** Parameter schema (Zod or TypeBox; TypeBox is auto-lifted to Zod at registration). */
184
185
  parameters: TParams;
185
186
  /** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
186
187
  hidden?: boolean;
@@ -2,7 +2,7 @@
2
2
  * CustomToolAdapter wraps CustomTool instances into AgentTool for use with the agent.
3
3
  */
4
4
  import type { AgentTool, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
- import type { Static, TSchema } from "@sinclair/typebox";
5
+ import type { Static, TSchema } from "@oh-my-pi/pi-ai";
6
6
  import type { Theme } from "../../modes/theme/theme";
7
7
  import { applyToolProxy } from "../tool-proxy";
8
8
  import type { CustomTool, CustomToolContext } from "./types";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Helper for wiring the `getCommands` action of {@link ExtensionAPI}.
3
+ *
4
+ * Centralizes the union over the three slash-command sources the runtime
5
+ * exposes so the five wiring sites (interactive UI, ACP, RPC, print, child
6
+ * task executor) cannot drift:
7
+ * - extension-registered hook commands (`source: "extension"`)
8
+ * - prompt commands loaded as `LoadedCustomCommand` — user/project/bundled
9
+ * custom commands and MCP prompts (`source: "prompt"`)
10
+ * - skill commands derived from `session.skills`, gated on
11
+ * `skillsSettings.enableSkillCommands` (`source: "skill"`)
12
+ *
13
+ * Built-in slash commands are intentionally excluded; `getCommands()` is the
14
+ * surface extensions use to discover dynamic commands they did not register
15
+ * themselves. Each frontend (interactive-mode, ACP) prepends its own builtins.
16
+ */
17
+ import type { SkillsSettings } from "../../config/settings";
18
+ import type { CustomCommandSource, LoadedCustomCommand } from "../custom-commands";
19
+ import { getSkillSlashCommandName, type Skill } from "../skills";
20
+ import type { SlashCommandInfo, SlashCommandLocation } from "../slash-commands";
21
+ import type { ExtensionRunner } from "./runner";
22
+
23
+ interface CommandsCapableSession {
24
+ readonly extensionRunner?: ExtensionRunner;
25
+ readonly customCommands: ReadonlyArray<LoadedCustomCommand>;
26
+ readonly skills: ReadonlyArray<Skill>;
27
+ readonly skillsSettings?: SkillsSettings;
28
+ }
29
+
30
+ export function getSessionSlashCommands(session: CommandsCapableSession): SlashCommandInfo[] {
31
+ const out: SlashCommandInfo[] = [];
32
+
33
+ const runner = session.extensionRunner;
34
+ if (runner) {
35
+ for (const cmd of runner.getRegisteredCommands()) {
36
+ out.push({
37
+ name: cmd.name,
38
+ description: cmd.description,
39
+ source: "extension",
40
+ });
41
+ }
42
+ }
43
+
44
+ for (const cmd of session.customCommands) {
45
+ out.push({
46
+ name: cmd.command.name,
47
+ description: cmd.command.description,
48
+ source: "prompt",
49
+ location: customCommandLocation(cmd.source),
50
+ path: cmd.resolvedPath,
51
+ });
52
+ }
53
+
54
+ if (session.skillsSettings?.enableSkillCommands) {
55
+ for (const skill of session.skills) {
56
+ out.push({
57
+ name: getSkillSlashCommandName(skill),
58
+ description: skill.description || undefined,
59
+ source: "skill",
60
+ path: skill.filePath,
61
+ });
62
+ }
63
+ }
64
+
65
+ return out;
66
+ }
67
+
68
+ function customCommandLocation(source: CustomCommandSource): SlashCommandLocation | undefined {
69
+ switch (source) {
70
+ case "user":
71
+ return "user";
72
+ case "project":
73
+ return "project";
74
+ case "bundled":
75
+ return undefined;
76
+ }
77
+ }
@@ -8,8 +8,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
8
  import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
9
9
  import type { KeyId } from "@oh-my-pi/pi-tui";
10
10
  import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
11
- import type { TSchema } from "@sinclair/typebox";
12
- import * as TypeBox from "@sinclair/typebox";
11
+ import * as Zod from "zod/v4";
13
12
  import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
14
13
  import { loadCapability } from "../../discovery";
15
14
  import { getExtensionNameFromPath } from "../../discovery/helpers";
@@ -19,6 +18,7 @@ import type { CustomMessage } from "../../session/messages";
19
18
  import { EventBus } from "../../utils/event-bus";
20
19
  import { installLegacyPiSpecifierShim, loadLegacyPiModule } from "../plugins/legacy-pi-compat";
21
20
  import { getAllPluginExtensionPaths } from "../plugins/loader";
21
+ import * as TypeBox from "../typebox";
22
22
 
23
23
  import { resolvePath } from "../utils";
24
24
  import type {
@@ -119,6 +119,7 @@ export class ExtensionRuntime implements IExtensionRuntime {
119
119
  class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
120
120
  readonly logger = logger;
121
121
  readonly typebox = TypeBox;
122
+ readonly zod = Zod;
122
123
  readonly flagValues = new Map<string, boolean | string>();
123
124
  readonly pendingProviderRegistrations: Array<{
124
125
  name: string;
@@ -140,7 +141,10 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
140
141
  this.extension.handlers.set(event, list);
141
142
  }
142
143
 
143
- registerTool<TParams extends TSchema = TSchema, TDetails = unknown>(tool: ToolDefinition<TParams, TDetails>): void {
144
+ registerTool<
145
+ TParams extends import("@oh-my-pi/pi-ai").TSchema = import("@oh-my-pi/pi-ai").TSchema,
146
+ TDetails = unknown,
147
+ >(tool: ToolDefinition<TParams, TDetails>): void {
144
148
  this.extension.tools.set(tool.name, {
145
149
  definition: tool,
146
150
  extensionPath: this.extension.path,
@@ -8,6 +8,7 @@
8
8
  * - Interact with the user via UI primitives
9
9
  */
10
10
  import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
11
+ import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
11
12
  import type {
12
13
  Api,
13
14
  AssistantMessageEvent,
@@ -17,12 +18,13 @@ import type {
17
18
  Model,
18
19
  ProviderResponseMetadata,
19
20
  SimpleStreamOptions,
21
+ Static,
20
22
  TextContent,
23
+ TSchema,
21
24
  } from "@oh-my-pi/pi-ai";
22
25
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/utils/oauth/types";
23
26
  import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
24
27
  import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
25
- import type { Static, TSchema } from "@sinclair/typebox";
26
28
  import type { KeybindingsManager } from "../../config/keybindings";
27
29
  import type { ModelRegistry } from "../../config/model-registry";
28
30
  import type { EditToolDetails } from "../../edit";
@@ -31,7 +33,6 @@ import type { BashResult } from "../../exec/bash-executor";
31
33
  import type { ExecOptions, ExecResult } from "../../exec/exec";
32
34
  import type { CustomEditor } from "../../modes/components/custom-editor";
33
35
  import type { Theme } from "../../modes/theme/theme";
34
- import type { CompactionResult } from "../../session/compaction";
35
36
  import type { CustomMessage } from "../../session/messages";
36
37
  import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager";
37
38
  import type {
@@ -355,7 +356,7 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
355
356
  label: string;
356
357
  /** Description for LLM */
357
358
  description: string;
358
- /** Parameter schema (TypeBox) */
359
+ /** Parameter schema (Zod, or TypeBox for legacy/extension compat). */
359
360
  parameters: TParams;
360
361
  /** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
361
362
  hidden?: boolean;
@@ -833,8 +834,11 @@ export interface ExtensionAPI {
833
834
  /** File logger for error/warning/debug messages */
834
835
  logger: typeof import("@oh-my-pi/pi-utils").logger;
835
836
 
836
- /** Injected @sinclair/typebox module for defining tool parameters */
837
- typebox: typeof import("@sinclair/typebox");
837
+ /** Injected zod-backed typebox shim for legacy `Type.Object(...)` parameter authoring. */
838
+ typebox: typeof import("../typebox");
839
+
840
+ /** Injected zod module for Zod-authored extension tools (canonical going forward). */
841
+ zod: typeof import("zod/v4");
838
842
 
839
843
  /** Injected pi-coding-agent exports for accessing SDK utilities */
840
844
  pi: typeof piCodingAgent;
@@ -2,8 +2,7 @@
2
2
  * Tool wrappers for extensions.
3
3
  */
4
4
  import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
- import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
6
- import type { Static, TSchema } from "@sinclair/typebox";
5
+ import type { ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
7
6
  import type { Theme } from "../../modes/theme/theme";
8
7
  import { applyToolProxy } from "../tool-proxy";
9
8
  import type { ExtensionRunner } from "./runner";
@@ -3,12 +3,13 @@
3
3
  */
4
4
  import * as path from "node:path";
5
5
  import { logger } from "@oh-my-pi/pi-utils";
6
- import * as typebox from "@sinclair/typebox";
6
+ import * as zod from "zod/v4";
7
7
  import { hookCapability } from "../../capability/hook";
8
8
  import type { Hook } from "../../discovery";
9
9
  import { loadCapability } from "../../discovery";
10
10
  import type { HookMessage } from "../../session/messages";
11
11
  import type { SessionManager } from "../../session/session-manager";
12
+ import * as typebox from "../typebox";
12
13
  import { resolvePath } from "../utils";
13
14
  import { execCommand } from "./runner";
14
15
  import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
@@ -136,6 +137,7 @@ async function createHookAPI(
136
137
  },
137
138
  logger,
138
139
  typebox,
140
+ zod,
139
141
  pi: await import("@oh-my-pi/pi-coding-agent"),
140
142
  } as HookAPI;
141
143
 
@@ -2,7 +2,7 @@
2
2
  * Tool wrapper - wraps tools with hook callbacks for interception.
3
3
  */
4
4
  import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
- import type { Static, TSchema } from "@sinclair/typebox";
5
+ import type { Static, TSchema } from "@oh-my-pi/pi-ai";
6
6
  import { applyToolProxy } from "../tool-proxy";
7
7
  import type { HookRunner } from "./runner";
8
8
  import type { ToolCallEventResult, ToolResultEventResult } from "./types";
@@ -571,8 +571,10 @@ export interface HookAPI {
571
571
 
572
572
  /** File logger for error/warning/debug messages */
573
573
  logger: typeof import("@oh-my-pi/pi-utils").logger;
574
- /** Injected @sinclair/typebox module */
575
- typebox: typeof import("@sinclair/typebox");
574
+ /** Injected zod-backed typebox shim (legacy/compat — prefer `zod`). */
575
+ typebox: typeof import("../typebox");
576
+ /** Injected zod module for Zod-authored hooks. */
577
+ zod: typeof import("zod/v4");
576
578
  /** Injected pi-coding-agent exports */
577
579
  pi: typeof import("../..");
578
580
  }
@@ -3,47 +3,75 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import * as url from "node:url";
5
5
 
6
- const LEGACY_PI_PACKAGE_MAP = {
7
- "@mariozechner/pi-agent-core": "@oh-my-pi/pi-agent-core",
8
- "@mariozechner/pi-ai": "@oh-my-pi/pi-ai",
9
- "@mariozechner/pi-coding-agent": "@oh-my-pi/pi-coding-agent",
10
- "@mariozechner/pi-tui": "@oh-my-pi/pi-tui",
11
- } as const;
12
-
13
- const LEGACY_PI_CODING_AGENT_SUBPATH_MAP = {
14
- "extensibility/extensions": "@oh-my-pi/pi-coding-agent/extensibility/extensions",
15
- "extensibility/hooks": "@oh-my-pi/pi-coding-agent/extensibility/hooks",
16
- } as const;
17
-
18
- const LEGACY_PI_SPECIFIER_FILTER = /^@mariozechner\/pi-(agent-core|ai|coding-agent|tui)(\/.*)?$/;
19
- const LEGACY_PI_IMPORT_SPECIFIER_REGEX =
20
- /((?:from\s+|import\s*\(\s*)["'])(@mariozechner\/pi-(?:agent-core|ai|coding-agent|tui)(?:\/[^"'()\s]+)?)(["'])/g;
6
+ // Canonical scope for in-process pi packages. Plugins published against any of
7
+ // the aliased scopes below (mariozechner's original publish, earendil-works'
8
+ // fork, or the canonical @oh-my-pi scope itself) are remapped to this scope and
9
+ // resolved against the bundled copy that ships inside the omp binary. This
10
+ // keeps plugins running against the exact runtime state of the host (single
11
+ // module registry, single tool registry, etc.) regardless of which historical
12
+ // scope name they happened to declare in their peerDependencies.
13
+ const CANONICAL_PI_SCOPE = "@oh-my-pi";
14
+
15
+ // Scopes that have historically been used to publish (or alias) the same set
16
+ // of internal pi-* packages. `@oh-my-pi` is intentionally included so that
17
+ // direct imports of the canonical name still flow through `Bun.resolveSync`
18
+ // against the host binary, avoiding a duplicate copy being pulled in from a
19
+ // plugin's own node_modules tree at install time.
20
+ const PI_SCOPE_ALIASES = ["oh-my-pi", "mariozechner", "earendil-works"] as const;
21
+
22
+ // Internal pi-* package basenames bundled inside the omp binary.
23
+ const PI_PACKAGE_NAMES = ["pi-agent-core", "pi-ai", "pi-coding-agent", "pi-natives", "pi-tui", "pi-utils"] as const;
24
+
25
+ const PI_SCOPE_ALTERNATION = PI_SCOPE_ALIASES.join("|");
26
+ const PI_PACKAGE_ALTERNATION = PI_PACKAGE_NAMES.join("|");
27
+
28
+ // Upstream `@mariozechner/*` packages exposed a few subpaths at the package
29
+ // root that we relocated under a different folder. Each entry rewrites
30
+ // `<pkg>/<from>` → `<pkg>/<to>` after the scope has been canonicalised, so
31
+ // plugins importing the upstream layout still resolve to a real file in our
32
+ // bundled copy. Add new entries as `pkg/from -> pkg/to` whenever a plugin
33
+ // surfaces another upstream-only subpath that breaks resolution.
34
+ const PI_SUBPATH_REMAPS: ReadonlyMap<string, string> = new Map<string, string>([
35
+ // `@mariozechner/pi-ai/oauth` re-exported `./utils/oauth/index.js`.
36
+ // Our pi-ai keeps the implementation under `utils/oauth` but never added a
37
+ // root-level re-export, so map the upstream subpath onto it directly.
38
+ ["pi-ai/oauth", "pi-ai/utils/oauth"],
39
+ ]);
40
+
41
+ const LEGACY_PI_SPECIFIER_FILTER = new RegExp(`^@(?:${PI_SCOPE_ALTERNATION})/(?:${PI_PACKAGE_ALTERNATION})(?:/.*)?$`);
42
+ const LEGACY_PI_IMPORT_SPECIFIER_REGEX = new RegExp(
43
+ `((?:from\\s+|import\\s*\\(\\s*)["'])(@(?:${PI_SCOPE_ALTERNATION})/(?:${PI_PACKAGE_ALTERNATION})(?:/[^"'()\\s]+)?)(["'])`,
44
+ "g",
45
+ );
21
46
  const LEGACY_PI_FILE_PREFIX = "omp-legacy-pi-file:";
22
47
  const LEGACY_PI_FILE_NAMESPACE = "omp-legacy-pi-file";
23
48
  const resolvedSpecifierFallbacks = new Map<string, string>();
24
49
 
50
+ // Extensions that imported `@sinclair/typebox` directly used to resolve against a
51
+ // real `@sinclair/typebox` install. The runtime dep was replaced with the Zod-backed
52
+ // shim under `extensibility/typebox.ts`; plugins still importing the public name
53
+ // are redirected to that shim so existing extensions keep working without code
54
+ // changes. Submodules like `@sinclair/typebox/compiler` are intentionally not
55
+ // remapped — those expose TypeBox-only APIs the shim does not provide and plugins
56
+ // relying on them must vendor `@sinclair/typebox` directly.
57
+ const TYPEBOX_SPECIFIER = "@sinclair/typebox";
58
+ const TYPEBOX_SPECIFIER_FILTER = /^@sinclair\/typebox$/;
59
+ const TYPEBOX_SHIM_PATH = path.resolve(import.meta.dir, "../typebox.ts");
60
+
25
61
  let isLegacyPiSpecifierShimInstalled = false;
26
62
 
27
63
  function remapLegacyPiSpecifier(specifier: string): string | null {
28
- const [legacyScope, packageName, ...subpathParts] = specifier.split("/");
29
- const legacyPackageName = `${legacyScope}/${packageName}`;
30
- const mappedPackageName = LEGACY_PI_PACKAGE_MAP[legacyPackageName as keyof typeof LEGACY_PI_PACKAGE_MAP];
31
- if (!mappedPackageName) {
64
+ if (!LEGACY_PI_SPECIFIER_FILTER.test(specifier)) {
32
65
  return null;
33
66
  }
34
- if (subpathParts.length === 0) {
35
- return mappedPackageName;
36
- }
37
-
38
- const subpath = subpathParts.join("/");
39
- if (legacyPackageName === "@mariozechner/pi-coding-agent") {
40
- return (
41
- LEGACY_PI_CODING_AGENT_SUBPATH_MAP[subpath as keyof typeof LEGACY_PI_CODING_AGENT_SUBPATH_MAP] ??
42
- `${mappedPackageName}/${subpath}`
43
- );
67
+ const slashIdx = specifier.indexOf("/", 1);
68
+ // Filter guarantees a slash exists, but guard anyway to keep the type narrow.
69
+ if (slashIdx === -1) {
70
+ return null;
44
71
  }
45
-
46
- return `${mappedPackageName}/${subpath}`;
72
+ const rest = specifier.slice(slashIdx + 1);
73
+ const remappedSubpath = PI_SUBPATH_REMAPS.get(rest) ?? rest;
74
+ return `${CANONICAL_PI_SCOPE}/${remappedSubpath}`;
47
75
  }
48
76
 
49
77
  function getResolvedSpecifier(specifier: string): string {
@@ -82,6 +110,12 @@ const ANY_IMPORT_SPECIFIER_REGEX = /((?:from\s+|import\s*\(\s*)["'])([^"']+)(["'
82
110
 
83
111
  /** Resolve bare imports against the extension directory before loading mirrored legacy Pi files. */
84
112
  function isUrlLikeSpecifier(specifier: string): boolean {
113
+ // Windows drive-letter paths (e.g. `C:\foo` or `C:/foo`) also match the URL
114
+ // scheme shape `[A-Za-z][A-Za-z\d+.-]*:`. Treat them as filesystem paths so
115
+ // `toRewrittenImportSpecifier` converts them to `file://` URLs instead of
116
+ // emitting raw paths whose `\n`, `\U`, ... get eaten by TS string-literal
117
+ // escapes inside the mirrored extension file.
118
+ if (/^[a-zA-Z]:[\\/]/.test(specifier)) return false;
85
119
  return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(specifier);
86
120
  }
87
121
 
@@ -100,6 +134,9 @@ function rewriteBareImportsForLegacyExtension(source: string, importerPath: stri
100
134
  if (shouldPreserveImportSpecifier(specifier)) {
101
135
  return match;
102
136
  }
137
+ if (specifier === TYPEBOX_SPECIFIER) {
138
+ return `${prefix}${toRewrittenImportSpecifier(TYPEBOX_SHIM_PATH)}${suffix}`;
139
+ }
103
140
  try {
104
141
  const resolved = Bun.resolveSync(specifier, importerDir);
105
142
  return `${prefix}${toRewrittenImportSpecifier(resolved)}${suffix}`;
@@ -206,6 +243,10 @@ function resolveLegacyPiSpecifier(args: { path: string }): { path: string } | un
206
243
  };
207
244
  }
208
245
 
246
+ function resolveTypeBoxSpecifier(): { path: string } {
247
+ return { path: TYPEBOX_SHIM_PATH };
248
+ }
249
+
209
250
  export function installLegacyPiSpecifierShim(): void {
210
251
  if (isLegacyPiSpecifierShimInstalled) {
211
252
  return;
@@ -221,6 +262,12 @@ export function installLegacyPiSpecifierShim(): void {
221
262
  resolveLegacyPiSpecifier,
222
263
  );
223
264
 
265
+ build.onResolve({ filter: TYPEBOX_SPECIFIER_FILTER, namespace: "file" }, resolveTypeBoxSpecifier);
266
+ build.onResolve(
267
+ { filter: TYPEBOX_SPECIFIER_FILTER, namespace: LEGACY_PI_FILE_NAMESPACE },
268
+ resolveTypeBoxSpecifier,
269
+ );
270
+
224
271
  build.onResolve({ filter: /^omp-legacy-pi-file:/, namespace: "file" }, args => ({
225
272
  path: args.path.slice(LEGACY_PI_FILE_PREFIX.length),
226
273
  namespace: LEGACY_PI_FILE_NAMESPACE,
@@ -13,10 +13,10 @@
13
13
  * `types.ts` files and is documented there.
14
14
  */
15
15
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
16
+ import type { CompactionPreparation, CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
16
17
  import type { ImageContent, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
17
18
  import type { Rule } from "../capability/rule";
18
19
  import type { Goal, GoalModeState } from "../goals/state";
19
- import type { CompactionPreparation, CompactionResult } from "../session/compaction";
20
20
  import type { BranchSummaryEntry, CompactionEntry, SessionEntry } from "../session/session-manager";
21
21
  import type { TodoItem } from "../tools/todo-write";
22
22