@oh-my-pi/pi-coding-agent 15.10.8 → 15.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/dist/types/extensibility/custom-tools/loader.d.ts +22 -3
- package/dist/types/extensibility/extensions/index.d.ts +1 -1
- package/dist/types/extensibility/extensions/loader.d.ts +17 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
- package/dist/types/mcp/transports/stdio.d.ts +12 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -2
- package/dist/types/sdk.d.ts +42 -2
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tools/index.d.ts +17 -0
- package/dist/types/tui/hyperlink.d.ts +8 -0
- package/package.json +9 -9
- package/src/extensibility/custom-tools/loader.ts +43 -19
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/loader.ts +29 -6
- package/src/extensibility/plugins/legacy-pi-compat.ts +30 -6
- package/src/mcp/transports/stdio.ts +139 -3
- package/src/modes/components/custom-editor.ts +69 -9
- package/src/modes/components/transcript-container.ts +77 -25
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/mcp-command-controller.ts +2 -2
- package/src/sdk.ts +138 -56
- package/src/ssh/ssh-executor.ts +60 -4
- package/src/task/executor.ts +19 -0
- package/src/task/index.ts +4 -0
- package/src/tools/index.ts +17 -0
- package/src/tui/hyperlink.ts +27 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.10.9] - 2026-06-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed streaming thinking (and other styled assistant content) vanishing from native scrollback once it scrolled past the viewport top during a foreground turn. The transcript's append-only commit detector compared raw row bytes, so a styled paragraph wrapping onto a new row (the span-closing SGR and width padding move while the visible cells stay identical) or a streamed token pushing the last word down a line flagged the block as permanently volatile — the commit boundary froze and every later row that crossed the viewport top was committed nowhere. Rows are now compared by visible content, a wrap-shrink of the in-flight bottom line counts as append-only, and a genuine one-off interior rewrite only suspends commits until the block re-earns append-only (30 clean frames), after which the pinned emitter backfills the stalled gap contiguously. Periodically rewriting blocks (spinners, collapsing tool previews) never re-earn and stay deferred.
|
|
10
|
+
|
|
11
|
+
- Fixed bracketed pastes containing multiple image file paths so each image is attached in order instead of treating the whole paste as one unreadable path.
|
|
12
|
+
|
|
13
|
+
- Fixed MCP OAuth fallback prompts so the "Click here to authorize" label emits an auth-safe terminal hyperlink even when hyperlink auto-detection is unavailable, keeping non-browser MCP setup usable ([#2196](https://github.com/can1357/oh-my-pi/issues/2196)).
|
|
14
|
+
- Fixed `task`-spawned subagents repeating filesystem scans the parent had already completed. `ExecutorOptions` and the `createAgentSession()` call inside `runSubprocess()` did not forward `rules`, the discovered extension paths, or the discovered `.omp/tools/` paths, so each subagent re-ran `loadCapability<Rule>()`, `discoverAndLoadExtensions()`, and the full `.omp/tools/` walk. The toolsession now caches `session.rules`, `session.extensionPaths`, and `session.customToolPaths`; `runSubprocess()` threads them through; and `createAgentSession()` accepts new `preloadedExtensionPaths` and `preloadedCustomToolPaths` options backed by new exported `discoverExtensionPaths()` and `discoverCustomToolPaths()` helpers. Crucially, only path lists are forwarded — never loaded instances. Each session rebuilds its own `Extension` and `LoadedCustomTool` objects so the per-session `ExtensionAPI`/`CustomToolAPI` (cwd, eventBus, runtime, exec, pushPendingAction, UI) targets the right session; forwarding loaded instances would have routed extension handlers and custom-tool execution back through the parent. The CLI's `preloadedExtensions` short-circuit is preserved for same-process reuse and now shallow-clones the caller's `extensions` array so inline-extension augmentation (autoresearch + custom-tools wrapper) cannot bleed back into it ([#2190](https://github.com/can1357/oh-my-pi/issues/2190)).
|
|
15
|
+
- Fixed SSH tool cancellation hanging behind OpenSSH ControlMaster streams that stayed open after an Esc/user interrupt ([#2180](https://github.com/can1357/oh-my-pi/issues/2180)).
|
|
16
|
+
- Fixed Windows stdio MCP servers launched through PATH shims such as `codegraph.cmd` so bare commands like `codegraph` resolve via `PATHEXT` before spawn ([#2174](https://github.com/can1357/oh-my-pi/issues/2174)).
|
|
17
|
+
- Fixed compiled-binary extensions failing to load `@oh-my-pi/pi-*` packages when `bun --compile` quietly dropped one of the extra entrypoints (observed on macOS arm64 release builds): the legacy-pi compat shim's package-root override branch returned the bunfs path without checking the target was present, so the rewrite emitted a `file://` URL to a missing module and the #1216 fallback (scoped to the throwing `getResolvedSpecifier` path) never ran. Override targets are now validated against the on-disk filesystem at module init, missing entries are dropped, and resolution falls through to canonical lookup so Bun resolves the import from the extension's own `node_modules` ([#2168](https://github.com/can1357/oh-my-pi/issues/2168)).
|
|
18
|
+
|
|
5
19
|
## [15.10.8] - 2026-06-09
|
|
6
20
|
|
|
7
21
|
### Added
|
|
@@ -9796,4 +9810,4 @@ Initial public release.
|
|
|
9796
9810
|
- Git branch display in footer
|
|
9797
9811
|
- Message queueing during streaming responses
|
|
9798
9812
|
- OAuth integration for Gmail and Google Calendar access
|
|
9799
|
-
- HTML export with syntax highlighting and collapsible sections
|
|
9813
|
+
- HTML export with syntax highlighting and collapsible sections
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { HookUIContext } from "../../extensibility/hooks/types";
|
|
3
3
|
import type { LoadedCustomTool, ToolLoadError } from "./types";
|
|
4
|
-
/** Tool path with optional source metadata
|
|
5
|
-
|
|
4
|
+
/** Tool path with optional source metadata, suitable for forwarding from a
|
|
5
|
+
* parent session to a subagent so the subagent can re-bind tools to its own
|
|
6
|
+
* `CustomToolAPI` without redoing the filesystem scan. */
|
|
7
|
+
export interface ToolPathWithSource {
|
|
6
8
|
path: string;
|
|
7
9
|
source?: {
|
|
8
10
|
provider: string;
|
|
@@ -46,12 +48,30 @@ export declare function loadCustomTools(pathsWithSources: ToolPathWithSource[],
|
|
|
46
48
|
errors: ToolLoadError[];
|
|
47
49
|
setUIContext: (uiContext: HookUIContext, hasUI: boolean) => void;
|
|
48
50
|
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Collect the absolute tool-source paths to load, without importing or
|
|
53
|
+
* binding factories. Hot path on session startup — the scan walks
|
|
54
|
+
* `.omp/tools/`, `.claude/tools/`, the plugin tree, and any configured paths.
|
|
55
|
+
*
|
|
56
|
+
* Subagents reuse the parent's collected paths via the SDK's
|
|
57
|
+
* `preloadedCustomToolPaths` option, then call `loadCustomTools` themselves
|
|
58
|
+
* so each session re-binds factories with its own session-scoped
|
|
59
|
+
* `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
60
|
+
*
|
|
61
|
+
* @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
|
|
62
|
+
* @param cwd - Current working directory
|
|
63
|
+
*/
|
|
64
|
+
export declare function discoverCustomToolPaths(configuredPaths: string[], cwd: string): Promise<ToolPathWithSource[]>;
|
|
49
65
|
/**
|
|
50
66
|
* Discover and load tools from standard locations via capability system:
|
|
51
67
|
* 1. User and project tools discovered by capability providers
|
|
52
68
|
* 2. Installed plugins (~/.omp/plugins/node_modules/*)
|
|
53
69
|
* 3. Explicitly configured paths from settings or CLI
|
|
54
70
|
*
|
|
71
|
+
* Composed of {@link discoverCustomToolPaths} (FS scan) + {@link loadCustomTools}
|
|
72
|
+
* (per-session binding). Subagents skip the first step and just call
|
|
73
|
+
* `loadCustomTools` against the parent's collected paths.
|
|
74
|
+
*
|
|
55
75
|
* @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
|
|
56
76
|
* @param cwd - Current working directory
|
|
57
77
|
* @param builtInToolNames - Names of built-in tools to check for conflicts
|
|
@@ -66,4 +86,3 @@ export declare function discoverAndLoadCustomTools(configuredPaths: string[], cw
|
|
|
66
86
|
errors: ToolLoadError[];
|
|
67
87
|
setUIContext: (uiContext: HookUIContext, hasUI: boolean) => void;
|
|
68
88
|
}>;
|
|
69
|
-
export {};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Extension system for lifecycle events and custom tools.
|
|
3
3
|
*/
|
|
4
4
|
export type { SlashCommandInfo, SlashCommandLocation, SlashCommandSource } from "../slash-commands";
|
|
5
|
-
export { discoverAndLoadExtensions, ExtensionRuntimeNotInitializedError, loadExtensionFromFactory, loadExtensions, } from "./loader";
|
|
5
|
+
export { discoverAndLoadExtensions, discoverExtensionPaths, ExtensionRuntimeNotInitializedError, loadExtensionFromFactory, loadExtensions, } from "./loader";
|
|
6
6
|
export * from "./runner";
|
|
7
7
|
export * from "./types";
|
|
8
8
|
export * from "./wrapper";
|
|
@@ -38,6 +38,22 @@ export declare function loadExtensionFromFactory(factory: ExtensionFactory, cwd:
|
|
|
38
38
|
*/
|
|
39
39
|
export declare function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult>;
|
|
40
40
|
/**
|
|
41
|
-
* Discover
|
|
41
|
+
* Discover absolute paths of extensions to load, without importing or
|
|
42
|
+
* binding factories. Hot path on session startup — the scan walks native
|
|
43
|
+
* `.omp`/`.pi` extension capabilities, the installed-plugin tree, and any
|
|
44
|
+
* configured paths.
|
|
45
|
+
*
|
|
46
|
+
* Subagents reuse the parent's collected paths via the SDK's
|
|
47
|
+
* `preloadedExtensionPaths` option, then call {@link loadExtensions} themselves
|
|
48
|
+
* so each session rebuilds Extension instances bound to its OWN
|
|
49
|
+
* `ExtensionAPI` (cwd, eventBus, runtime). Forwarding the parent's
|
|
50
|
+
* `LoadExtensionsResult` directly would reuse handlers/tools/commands that
|
|
51
|
+
* closed over the parent's `cwd` and event bus.
|
|
52
|
+
*/
|
|
53
|
+
export declare function discoverExtensionPaths(configuredPaths: string[], cwd: string, disabledExtensionIds?: string[]): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Discover and load extensions from standard locations. Composed of
|
|
56
|
+
* {@link discoverExtensionPaths} (FS scan) + {@link loadExtensions}
|
|
57
|
+
* (per-session binding).
|
|
42
58
|
*/
|
|
43
59
|
export declare function discoverAndLoadExtensions(configuredPaths: string[], cwd: string, eventBus?: EventBus, disabledExtensionIds?: string[]): Promise<LoadExtensionsResult>;
|
|
@@ -13,6 +13,14 @@ import * as path from "node:path";
|
|
|
13
13
|
* Exported for tests; production callers use `BUNFS_PACKAGE_ROOT` below.
|
|
14
14
|
*/
|
|
15
15
|
export declare function __computeBunfsPackageRoot(metaDir: string, pathImpl?: typeof path): string;
|
|
16
|
+
/**
|
|
17
|
+
* Drop overrides whose targets are missing on disk so they can fall through to
|
|
18
|
+
* the canonical-resolution path. Exported for the test seam in #2168.
|
|
19
|
+
*
|
|
20
|
+
* `pathExistsSync` defaults to `fs.existsSync`; the tests inject a stub to
|
|
21
|
+
* simulate the missing-entrypoint failure mode without touching the real FS.
|
|
22
|
+
*/
|
|
23
|
+
export declare function __validateLegacyPiPackageRootOverrides(candidates: Record<string, string>, pathExistsSync?: (p: string) => boolean): Record<string, string>;
|
|
16
24
|
/**
|
|
17
25
|
* Load a legacy Pi extension module from its real on-disk location.
|
|
18
26
|
*
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
* Messages are newline-delimited JSON.
|
|
6
6
|
*/
|
|
7
7
|
import type { MCPRequestOptions, MCPStdioServerConfig, MCPTransport } from "../../mcp/types";
|
|
8
|
+
/** Subprocess argv for launching an MCP stdio server. */
|
|
9
|
+
export interface StdioSpawnCommand {
|
|
10
|
+
cmd: string[];
|
|
11
|
+
}
|
|
12
|
+
/** Inputs used to resolve platform-specific stdio spawn behavior. */
|
|
13
|
+
export interface ResolveStdioSpawnOptions {
|
|
14
|
+
cwd: string;
|
|
15
|
+
env: Record<string, string | undefined>;
|
|
16
|
+
platform?: NodeJS.Platform;
|
|
17
|
+
}
|
|
18
|
+
/** Resolve the subprocess argv used to launch an MCP stdio server. */
|
|
19
|
+
export declare function resolveStdioSpawnCommand(config: MCPStdioServerConfig, options: ResolveStdioSpawnOptions): Promise<StdioSpawnCommand>;
|
|
8
20
|
/** Minimal write surface of `Subprocess.stdin` we need for framed sends. */
|
|
9
21
|
interface FrameSink {
|
|
10
22
|
write(chunk: string): unknown;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Editor, type KeyId } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import type { AppKeybinding } from "../../config/keybindings";
|
|
3
3
|
type ConfigurableEditorAction = Extract<AppKeybinding, "app.interrupt" | "app.clear" | "app.exit" | "app.suspend" | "app.display.reset" | "app.thinking.cycle" | "app.model.cycleForward" | "app.model.cycleBackward" | "app.model.select" | "app.model.selectTemporary" | "app.tools.expand" | "app.thinking.toggle" | "app.editor.external" | "app.history.search" | "app.message.dequeue" | "app.clipboard.pasteImage" | "app.clipboard.pasteTextRaw" | "app.clipboard.copyPrompt">;
|
|
4
|
+
export declare function extractBracketedImagePastePaths(data: string): string[] | undefined;
|
|
4
5
|
export declare function extractBracketedImagePastePath(data: string): string | undefined;
|
|
5
6
|
/**
|
|
6
7
|
* Custom editor that handles configurable app-level shortcuts for coding-agent.
|
|
@@ -33,8 +34,8 @@ export declare class CustomEditor extends Editor {
|
|
|
33
34
|
onCopyPrompt?: () => void;
|
|
34
35
|
/** Called when the configured image-paste shortcut is pressed. */
|
|
35
36
|
onPasteImage?: () => Promise<boolean>;
|
|
36
|
-
/** Called when a bracketed paste contains
|
|
37
|
-
onPasteImagePath?: (path: string) => void
|
|
37
|
+
/** Called when a bracketed paste contains one or more image-file paths. */
|
|
38
|
+
onPasteImagePath?: (path: string) => void | Promise<void>;
|
|
38
39
|
/** Called when the configured raw text-paste shortcut is pressed. */
|
|
39
40
|
onPasteTextRaw?: () => void;
|
|
40
41
|
/** Called when the configured dequeue shortcut is pressed. */
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type PromptTemplate } from "./config/prompt-templates";
|
|
|
6
6
|
import { Settings, type SkillsSettings } from "./config/settings";
|
|
7
7
|
import "./discovery";
|
|
8
8
|
import { type CustomCommandsLoadResult } from "./extensibility/custom-commands";
|
|
9
|
+
import { type ToolPathWithSource } from "./extensibility/custom-tools";
|
|
9
10
|
import type { CustomTool } from "./extensibility/custom-tools/types";
|
|
10
11
|
import { type ExtensionFactory, type ExtensionUIContext, type LoadExtensionsResult, type ToolDefinition } from "./extensibility/extensions";
|
|
11
12
|
import { type Skill, type SkillWarning } from "./extensibility/skills";
|
|
@@ -62,10 +63,41 @@ export interface CreateAgentSessionOptions {
|
|
|
62
63
|
/** Disable extension discovery (explicit paths still load). */
|
|
63
64
|
disableExtensionDiscovery?: boolean;
|
|
64
65
|
/**
|
|
65
|
-
* Pre-loaded extensions (skips file discovery
|
|
66
|
-
*
|
|
66
|
+
* Pre-loaded extensions (skips file discovery and the per-session factory
|
|
67
|
+
* call). Used by the CLI when extensions are loaded early to parse custom
|
|
68
|
+
* flags — the same process owns the returned instances, so reusing them is
|
|
69
|
+
* safe.
|
|
70
|
+
*
|
|
71
|
+
* NEVER pass this across session boundaries (e.g. parent → subagent).
|
|
72
|
+
* `Extension` instances close over a parent-bound `ExtensionAPI` (cwd,
|
|
73
|
+
* eventBus, runtime), and reusing them would route tools/handlers/commands
|
|
74
|
+
* back through the parent. For subagents, forward
|
|
75
|
+
* {@link preloadedExtensionPaths} instead.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
67
78
|
*/
|
|
68
79
|
preloadedExtensions?: LoadExtensionsResult;
|
|
80
|
+
/**
|
|
81
|
+
* Pre-discovered extension source paths. When provided, the filesystem-scan
|
|
82
|
+
* inside `discoverExtensionPaths()` is skipped — the session still calls
|
|
83
|
+
* `loadExtensions()` itself so each `Extension` is bound to THIS session's
|
|
84
|
+
* `ExtensionAPI` (cwd, eventBus, runtime).
|
|
85
|
+
*
|
|
86
|
+
* This is the safe pass-through for parent → subagent forwarding.
|
|
87
|
+
*/
|
|
88
|
+
preloadedExtensionPaths?: string[];
|
|
89
|
+
/**
|
|
90
|
+
* Pre-discovered custom-tool source paths from `.omp/tools/`, `.claude/tools/`,
|
|
91
|
+
* plugins, etc. When provided, the filesystem-scan inside
|
|
92
|
+
* `discoverCustomToolPaths()` is skipped — subagents inherit the parent's
|
|
93
|
+
* scan result and call `loadCustomTools()` themselves so each session binds
|
|
94
|
+
* tools to its OWN `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
95
|
+
*
|
|
96
|
+
* Forwarding the loaded `LoadedCustomTool[]` instances directly would reuse
|
|
97
|
+
* the parent's session-bound API and route tool execution back through the
|
|
98
|
+
* parent — wrong for isolated tasks and for pending-action routing.
|
|
99
|
+
*/
|
|
100
|
+
preloadedCustomToolPaths?: ToolPathWithSource[];
|
|
69
101
|
/** Shared event bus for tool/extension communication. Default: creates new bus. */
|
|
70
102
|
eventBus?: EventBus;
|
|
71
103
|
/** Skills. Default: discovered from multiple locations */
|
|
@@ -178,6 +210,14 @@ export declare function discoverAuthStorage(agentDir?: string): Promise<AuthStor
|
|
|
178
210
|
* Discover extensions from cwd.
|
|
179
211
|
*/
|
|
180
212
|
export declare function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult>;
|
|
213
|
+
/**
|
|
214
|
+
* Path-only counterpart of {@link loadSessionExtensions}: the FS-heavy scan
|
|
215
|
+
* without the per-session module load. Subagents reuse the parent's path list
|
|
216
|
+
* (cached on {@link ToolSession.extensionPaths}) and rebuild Extension
|
|
217
|
+
* instances themselves so each session's `ExtensionAPI` (cwd, eventBus,
|
|
218
|
+
* runtime) is its own.
|
|
219
|
+
*/
|
|
220
|
+
export declare function discoverSessionExtensionPaths(options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">, cwd: string, settings: Settings): Promise<string[]>;
|
|
181
221
|
/**
|
|
182
222
|
* Load the discovered/configured extensions for a session — everything {@link
|
|
183
223
|
* createAgentSession} would load except the inline factory extensions it appends
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import type { Rule } from "../capability/rule";
|
|
7
8
|
import { ModelRegistry } from "../config/model-registry";
|
|
8
9
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
9
10
|
import { Settings } from "../config/settings";
|
|
10
11
|
import { type SettingPath } from "../config/settings-schema";
|
|
12
|
+
import type { ToolPathWithSource } from "../extensibility/custom-tools";
|
|
11
13
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
12
14
|
import { type Skill } from "../extensibility/skills";
|
|
13
15
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
@@ -70,6 +72,20 @@ export interface ExecutorOptions {
|
|
|
70
72
|
skills?: Skill[];
|
|
71
73
|
promptTemplates?: PromptTemplate[];
|
|
72
74
|
workspaceTree?: WorkspaceTree;
|
|
75
|
+
/** Parent-discovered rules, forwarded to skip rule discovery in the subagent. */
|
|
76
|
+
rules?: Rule[];
|
|
77
|
+
/**
|
|
78
|
+
* Parent's discovered extension source paths. Forwarded to skip the
|
|
79
|
+
* extension FS scan in the subagent; the subagent then re-binds each
|
|
80
|
+
* extension against its own `ExtensionAPI` (cwd, eventBus, runtime).
|
|
81
|
+
*/
|
|
82
|
+
preloadedExtensionPaths?: string[];
|
|
83
|
+
/**
|
|
84
|
+
* Parent's discovered custom-tool source paths. Forwarded to skip the
|
|
85
|
+
* `.omp/tools/` FS scan in the subagent; the subagent then re-binds each
|
|
86
|
+
* tool against its own `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
87
|
+
*/
|
|
88
|
+
preloadedCustomToolPaths?: ToolPathWithSource[];
|
|
73
89
|
mcpManager?: MCPManager;
|
|
74
90
|
authStorage?: AuthStorage;
|
|
75
91
|
modelRegistry?: ModelRegistry;
|
|
@@ -2,8 +2,10 @@ import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
|
2
2
|
import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import type { FetchImpl, ToolChoice } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import type { AsyncJobManager } from "../async/job-manager";
|
|
5
|
+
import type { Rule } from "../capability/rule";
|
|
5
6
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
6
7
|
import type { Settings } from "../config/settings";
|
|
8
|
+
import type { ToolPathWithSource } from "../extensibility/custom-tools";
|
|
7
9
|
import type { Skill } from "../extensibility/skills";
|
|
8
10
|
import type { GoalModeState, GoalRuntime } from "../goals";
|
|
9
11
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
@@ -107,6 +109,21 @@ export interface ToolSession {
|
|
|
107
109
|
skills?: Skill[];
|
|
108
110
|
/** Pre-loaded prompt templates */
|
|
109
111
|
promptTemplates?: PromptTemplate[];
|
|
112
|
+
/** Pre-loaded rules (forwarded to subagents to skip re-discovery). */
|
|
113
|
+
rules?: Rule[];
|
|
114
|
+
/**
|
|
115
|
+
* Pre-discovered extension source paths. Forwarded to subagents so they
|
|
116
|
+
* skip the FS scan but still re-bind extensions to their own session-scoped
|
|
117
|
+
* `ExtensionAPI` (cwd, eventBus, runtime). Inline extension factories
|
|
118
|
+
* (`<inline-N>`) are NOT included — those are session-local.
|
|
119
|
+
*/
|
|
120
|
+
extensionPaths?: string[];
|
|
121
|
+
/**
|
|
122
|
+
* Pre-discovered custom-tool source paths from `.omp/tools/`, `.claude/tools/`,
|
|
123
|
+
* plugins, etc. Forwarded to subagents so they skip the FS scan but still
|
|
124
|
+
* re-bind tools to their own session-scoped `CustomToolAPI`.
|
|
125
|
+
*/
|
|
126
|
+
customToolPaths?: ToolPathWithSource[];
|
|
110
127
|
/** Whether LSP integrations are enabled */
|
|
111
128
|
enableLsp?: boolean;
|
|
112
129
|
/** Whether an edit-capable tool is available in this session (controls hashline output) */
|
|
@@ -19,6 +19,14 @@ export declare function uriHyperlink(uri: string, displayText: string): string;
|
|
|
19
19
|
* `www.example.com` inputs are linked as `https://www.example.com`.
|
|
20
20
|
*/
|
|
21
21
|
export declare function urlHyperlink(url: string, displayText: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Wrap `displayText` in an OSC 8 hyperlink pointing at an HTTP(S) URL,
|
|
24
|
+
* bypassing terminal capability auto-detection. Used for auth prompts where
|
|
25
|
+
* an inert "click" label blocks login on terminals whose capabilities are
|
|
26
|
+
* not advertised. Still returns plain text when the user has explicitly
|
|
27
|
+
* opted out via `tui.hyperlinks=off`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function urlHyperlinkAlways(url: string, displayText: string): string;
|
|
22
30
|
/**
|
|
23
31
|
* Wrap `displayText` in an OSC 8 hyperlink pointing at a filesystem path.
|
|
24
32
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "15.10.
|
|
4
|
+
"version": "15.10.9",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.22.1",
|
|
48
48
|
"@babel/parser": "^7.29.7",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/hashline": "15.10.
|
|
51
|
-
"@oh-my-pi/omp-stats": "15.10.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "15.10.
|
|
53
|
-
"@oh-my-pi/pi-ai": "15.10.
|
|
54
|
-
"@oh-my-pi/pi-mnemopi": "15.10.
|
|
55
|
-
"@oh-my-pi/pi-natives": "15.10.
|
|
56
|
-
"@oh-my-pi/pi-tui": "15.10.
|
|
57
|
-
"@oh-my-pi/pi-utils": "15.10.
|
|
50
|
+
"@oh-my-pi/hashline": "15.10.9",
|
|
51
|
+
"@oh-my-pi/omp-stats": "15.10.9",
|
|
52
|
+
"@oh-my-pi/pi-agent-core": "15.10.9",
|
|
53
|
+
"@oh-my-pi/pi-ai": "15.10.9",
|
|
54
|
+
"@oh-my-pi/pi-mnemopi": "15.10.9",
|
|
55
|
+
"@oh-my-pi/pi-natives": "15.10.9",
|
|
56
|
+
"@oh-my-pi/pi-tui": "15.10.9",
|
|
57
|
+
"@oh-my-pi/pi-utils": "15.10.9",
|
|
58
58
|
"@opentelemetry/api": "^1.9.1",
|
|
59
59
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
60
60
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -66,8 +66,10 @@ async function loadTool(
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** Tool path with optional source metadata
|
|
70
|
-
|
|
69
|
+
/** Tool path with optional source metadata, suitable for forwarding from a
|
|
70
|
+
* parent session to a subagent so the subagent can re-bind tools to its own
|
|
71
|
+
* `CustomToolAPI` without redoing the filesystem scan. */
|
|
72
|
+
export interface ToolPathWithSource {
|
|
71
73
|
path: string;
|
|
72
74
|
source?: { provider: string; providerName: string; level: "user" | "project" };
|
|
73
75
|
}
|
|
@@ -189,26 +191,19 @@ export async function loadCustomTools(
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
194
|
+
* Collect the absolute tool-source paths to load, without importing or
|
|
195
|
+
* binding factories. Hot path on session startup — the scan walks
|
|
196
|
+
* `.omp/tools/`, `.claude/tools/`, the plugin tree, and any configured paths.
|
|
197
|
+
*
|
|
198
|
+
* Subagents reuse the parent's collected paths via the SDK's
|
|
199
|
+
* `preloadedCustomToolPaths` option, then call `loadCustomTools` themselves
|
|
200
|
+
* so each session re-binds factories with its own session-scoped
|
|
201
|
+
* `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
196
202
|
*
|
|
197
203
|
* @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
|
|
198
204
|
* @param cwd - Current working directory
|
|
199
|
-
* @param builtInToolNames - Names of built-in tools to check for conflicts
|
|
200
205
|
*/
|
|
201
|
-
export async function
|
|
202
|
-
configuredPaths: string[],
|
|
203
|
-
cwd: string,
|
|
204
|
-
builtInToolNames: string[],
|
|
205
|
-
pushPendingAction?: (action: {
|
|
206
|
-
label: string;
|
|
207
|
-
sourceToolName: string;
|
|
208
|
-
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
209
|
-
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
210
|
-
}) => void,
|
|
211
|
-
) {
|
|
206
|
+
export async function discoverCustomToolPaths(configuredPaths: string[], cwd: string): Promise<ToolPathWithSource[]> {
|
|
212
207
|
const allPathsWithSources: ToolPathWithSource[] = [];
|
|
213
208
|
const seen = new Set<string>();
|
|
214
209
|
|
|
@@ -241,5 +236,34 @@ export async function discoverAndLoadCustomTools(
|
|
|
241
236
|
addPath(resolvePath(configPath, cwd), { provider: "config", providerName: "Config", level: "project" });
|
|
242
237
|
}
|
|
243
238
|
|
|
244
|
-
return
|
|
239
|
+
return allPathsWithSources;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Discover and load tools from standard locations via capability system:
|
|
244
|
+
* 1. User and project tools discovered by capability providers
|
|
245
|
+
* 2. Installed plugins (~/.omp/plugins/node_modules/*)
|
|
246
|
+
* 3. Explicitly configured paths from settings or CLI
|
|
247
|
+
*
|
|
248
|
+
* Composed of {@link discoverCustomToolPaths} (FS scan) + {@link loadCustomTools}
|
|
249
|
+
* (per-session binding). Subagents skip the first step and just call
|
|
250
|
+
* `loadCustomTools` against the parent's collected paths.
|
|
251
|
+
*
|
|
252
|
+
* @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
|
|
253
|
+
* @param cwd - Current working directory
|
|
254
|
+
* @param builtInToolNames - Names of built-in tools to check for conflicts
|
|
255
|
+
*/
|
|
256
|
+
export async function discoverAndLoadCustomTools(
|
|
257
|
+
configuredPaths: string[],
|
|
258
|
+
cwd: string,
|
|
259
|
+
builtInToolNames: string[],
|
|
260
|
+
pushPendingAction?: (action: {
|
|
261
|
+
label: string;
|
|
262
|
+
sourceToolName: string;
|
|
263
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
264
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
265
|
+
}) => void,
|
|
266
|
+
) {
|
|
267
|
+
const pathsWithSources = await discoverCustomToolPaths(configuredPaths, cwd);
|
|
268
|
+
return loadCustomTools(pathsWithSources, cwd, builtInToolNames, pushPendingAction);
|
|
245
269
|
}
|
|
@@ -475,16 +475,24 @@ async function discoverExtensionsInDir(dir: string): Promise<string[]> {
|
|
|
475
475
|
|
|
476
476
|
return discovered;
|
|
477
477
|
}
|
|
478
|
-
|
|
479
478
|
/**
|
|
480
|
-
* Discover
|
|
479
|
+
* Discover absolute paths of extensions to load, without importing or
|
|
480
|
+
* binding factories. Hot path on session startup — the scan walks native
|
|
481
|
+
* `.omp`/`.pi` extension capabilities, the installed-plugin tree, and any
|
|
482
|
+
* configured paths.
|
|
483
|
+
*
|
|
484
|
+
* Subagents reuse the parent's collected paths via the SDK's
|
|
485
|
+
* `preloadedExtensionPaths` option, then call {@link loadExtensions} themselves
|
|
486
|
+
* so each session rebuilds Extension instances bound to its OWN
|
|
487
|
+
* `ExtensionAPI` (cwd, eventBus, runtime). Forwarding the parent's
|
|
488
|
+
* `LoadExtensionsResult` directly would reuse handlers/tools/commands that
|
|
489
|
+
* closed over the parent's `cwd` and event bus.
|
|
481
490
|
*/
|
|
482
|
-
export async function
|
|
491
|
+
export async function discoverExtensionPaths(
|
|
483
492
|
configuredPaths: string[],
|
|
484
493
|
cwd: string,
|
|
485
|
-
eventBus?: EventBus,
|
|
486
494
|
disabledExtensionIds: string[] = [],
|
|
487
|
-
): Promise<
|
|
495
|
+
): Promise<string[]> {
|
|
488
496
|
const allPaths: string[] = [];
|
|
489
497
|
const seen = new Set<string>();
|
|
490
498
|
const disabled = new Set(disabledExtensionIds);
|
|
@@ -545,5 +553,20 @@ export async function discoverAndLoadExtensions(
|
|
|
545
553
|
addPath(resolved);
|
|
546
554
|
}
|
|
547
555
|
|
|
548
|
-
return
|
|
556
|
+
return allPaths;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Discover and load extensions from standard locations. Composed of
|
|
561
|
+
* {@link discoverExtensionPaths} (FS scan) + {@link loadExtensions}
|
|
562
|
+
* (per-session binding).
|
|
563
|
+
*/
|
|
564
|
+
export async function discoverAndLoadExtensions(
|
|
565
|
+
configuredPaths: string[],
|
|
566
|
+
cwd: string,
|
|
567
|
+
eventBus?: EventBus,
|
|
568
|
+
disabledExtensionIds: string[] = [],
|
|
569
|
+
): Promise<LoadExtensionsResult> {
|
|
570
|
+
const paths = await discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds);
|
|
571
|
+
return loadExtensions(paths, cwd, eventBus);
|
|
549
572
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as fs from "node:fs
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as url from "node:url";
|
|
4
4
|
import { isCompiledBinary } from "@oh-my-pi/pi-utils";
|
|
@@ -142,7 +142,31 @@ const LEGACY_PI_CODING_AGENT_SHIM_PATH = BUNFS_PACKAGE_ROOT
|
|
|
142
142
|
// `Bun.resolveSync`, and hardcoding a relative source-tree path would break
|
|
143
143
|
// installs where the bundled packages live at `node_modules/@oh-my-pi/pi-*`
|
|
144
144
|
// rather than `packages/*`.
|
|
145
|
-
|
|
145
|
+
//
|
|
146
|
+
// Every override target is validated against the on-disk filesystem at module
|
|
147
|
+
// init: any entry whose file is missing (e.g. a compiled binary where Bun's
|
|
148
|
+
// `--compile` quietly dropped an additional entrypoint — issue #2168) is left
|
|
149
|
+
// out so `resolveCanonicalPiSpecifier` falls through to `getResolvedSpecifier`,
|
|
150
|
+
// which throws under bunfs and triggers the catch in `rewriteLegacyPiImports`.
|
|
151
|
+
// That catch leaves the specifier untouched so Bun resolves the canonical
|
|
152
|
+
// `@oh-my-pi/pi-*` import from the extension's own `node_modules` instead of
|
|
153
|
+
// emitting a bunfs `file://` URL to a module that isn't actually present.
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Drop overrides whose targets are missing on disk so they can fall through to
|
|
157
|
+
* the canonical-resolution path. Exported for the test seam in #2168.
|
|
158
|
+
*
|
|
159
|
+
* `pathExistsSync` defaults to `fs.existsSync`; the tests inject a stub to
|
|
160
|
+
* simulate the missing-entrypoint failure mode without touching the real FS.
|
|
161
|
+
*/
|
|
162
|
+
export function __validateLegacyPiPackageRootOverrides(
|
|
163
|
+
candidates: Record<string, string>,
|
|
164
|
+
pathExistsSync: (p: string) => boolean = fs.existsSync,
|
|
165
|
+
): Record<string, string> {
|
|
166
|
+
return Object.fromEntries(Object.entries(candidates).filter(([, candidate]) => pathExistsSync(candidate)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const LEGACY_PI_PACKAGE_ROOT_OVERRIDES = __validateLegacyPiPackageRootOverrides({
|
|
146
170
|
[`${CANONICAL_PI_SCOPE}/pi-ai`]: LEGACY_PI_AI_SHIM_PATH,
|
|
147
171
|
[`${CANONICAL_PI_SCOPE}/pi-coding-agent`]: LEGACY_PI_CODING_AGENT_SHIM_PATH,
|
|
148
172
|
...(BUNFS_PACKAGE_ROOT
|
|
@@ -153,7 +177,7 @@ const LEGACY_PI_PACKAGE_ROOT_OVERRIDES: Record<string, string> = {
|
|
|
153
177
|
[`${CANONICAL_PI_SCOPE}/pi-utils`]: bunfsPath("utils", "src", "index.js"),
|
|
154
178
|
}
|
|
155
179
|
: {}),
|
|
156
|
-
};
|
|
180
|
+
});
|
|
157
181
|
|
|
158
182
|
let isLegacyPiSpecifierShimInstalled = false;
|
|
159
183
|
|
|
@@ -253,7 +277,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
253
277
|
|
|
254
278
|
async function pathExists(p: string): Promise<boolean> {
|
|
255
279
|
try {
|
|
256
|
-
await fs.stat(p);
|
|
280
|
+
await fs.promises.stat(p);
|
|
257
281
|
return true;
|
|
258
282
|
} catch {
|
|
259
283
|
return false;
|
|
@@ -267,7 +291,7 @@ function hasSourceModuleExtension(p: string): boolean {
|
|
|
267
291
|
|
|
268
292
|
async function resolveSourceModuleFile(basePath: string): Promise<string | null> {
|
|
269
293
|
try {
|
|
270
|
-
const stats = await fs.stat(basePath);
|
|
294
|
+
const stats = await fs.promises.stat(basePath);
|
|
271
295
|
if (stats.isFile()) {
|
|
272
296
|
// Non-source files (JSON, WASM, text assets, etc.) bypass the on-load
|
|
273
297
|
// rewrite hook so Bun's native loaders handle them; our hook would
|
|
@@ -475,7 +499,7 @@ const hookedExtensionEntries = new Set<string>();
|
|
|
475
499
|
/** Resolve symlinks in a path, falling back to the input if realpath fails. */
|
|
476
500
|
async function realpathOrSelf(p: string): Promise<string> {
|
|
477
501
|
try {
|
|
478
|
-
return await fs.realpath(p);
|
|
502
|
+
return await fs.promises.realpath(p);
|
|
479
503
|
} catch {
|
|
480
504
|
return p;
|
|
481
505
|
}
|