@oh-my-pi/pi-coding-agent 15.9.1 → 15.9.5

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 (109) hide show
  1. package/CHANGELOG.md +68 -2
  2. package/dist/types/cli/classify-install-target.d.ts +5 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +104 -0
  4. package/dist/types/commands/dry-balance.d.ts +31 -0
  5. package/dist/types/config/model-registry.d.ts +2 -0
  6. package/dist/types/config/models-config-schema.d.ts +3 -0
  7. package/dist/types/config/settings-schema.d.ts +13 -4
  8. package/dist/types/config/settings.d.ts +11 -0
  9. package/dist/types/discovery/helpers.d.ts +1 -0
  10. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
  11. package/dist/types/hindsight/bank.d.ts +17 -9
  12. package/dist/types/hindsight/mental-models.d.ts +1 -1
  13. package/dist/types/hindsight/state.d.ts +9 -3
  14. package/dist/types/mcp/manager.d.ts +1 -1
  15. package/dist/types/modes/components/assistant-message.d.ts +11 -0
  16. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  17. package/dist/types/modes/components/error-banner.d.ts +11 -0
  18. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  19. package/dist/types/modes/components/transcript-container.d.ts +4 -2
  20. package/dist/types/modes/components/user-message.d.ts +1 -1
  21. package/dist/types/modes/image-references.d.ts +17 -0
  22. package/dist/types/modes/interactive-mode.d.ts +7 -0
  23. package/dist/types/modes/types.d.ts +7 -0
  24. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  25. package/dist/types/session/agent-session.d.ts +9 -0
  26. package/dist/types/session/auth-storage.d.ts +2 -2
  27. package/dist/types/session/blob-store.d.ts +12 -11
  28. package/dist/types/session/session-manager.d.ts +5 -3
  29. package/dist/types/system-prompt.d.ts +2 -0
  30. package/dist/types/task/types.d.ts +2 -0
  31. package/dist/types/tiny/title-client.d.ts +16 -1
  32. package/dist/types/tool-discovery/mode.d.ts +8 -0
  33. package/dist/types/tools/archive-reader.d.ts +5 -1
  34. package/dist/types/tools/index.d.ts +16 -0
  35. package/dist/types/tools/path-utils.d.ts +11 -0
  36. package/dist/types/tui/hyperlink.d.ts +12 -0
  37. package/dist/types/web/search/render.d.ts +1 -2
  38. package/package.json +9 -9
  39. package/src/cli/classify-install-target.ts +31 -5
  40. package/src/cli/dry-balance-cli.ts +823 -0
  41. package/src/cli/plugin-cli.ts +45 -0
  42. package/src/cli/web-search-cli.ts +0 -1
  43. package/src/cli-commands.ts +1 -0
  44. package/src/commands/dry-balance.ts +43 -0
  45. package/src/config/model-registry.ts +60 -4
  46. package/src/config/models-config-schema.ts +2 -0
  47. package/src/config/settings-schema.ts +14 -4
  48. package/src/config/settings.ts +38 -0
  49. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
  50. package/src/discovery/github.ts +37 -1
  51. package/src/discovery/helpers.ts +3 -1
  52. package/src/eval/__tests__/agent-bridge.test.ts +72 -0
  53. package/src/eval/py/tool-bridge.ts +43 -5
  54. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +31 -2
  55. package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
  56. package/src/hindsight/backend.ts +184 -35
  57. package/src/hindsight/bank.ts +32 -22
  58. package/src/hindsight/mental-models.ts +1 -1
  59. package/src/hindsight/state.ts +21 -7
  60. package/src/internal-urls/docs-index.generated.ts +6 -6
  61. package/src/internal-urls/omp-protocol.ts +8 -2
  62. package/src/main.ts +7 -1
  63. package/src/mcp/manager.ts +40 -21
  64. package/src/modes/components/assistant-message.ts +22 -0
  65. package/src/modes/components/custom-editor.ts +14 -2
  66. package/src/modes/components/error-banner.ts +33 -0
  67. package/src/modes/components/tool-execution.ts +44 -0
  68. package/src/modes/components/transcript-container.ts +102 -30
  69. package/src/modes/components/tree-selector.ts +29 -2
  70. package/src/modes/components/user-message.ts +9 -2
  71. package/src/modes/controllers/event-controller.ts +42 -3
  72. package/src/modes/controllers/input-controller.ts +41 -3
  73. package/src/modes/image-references.ts +111 -0
  74. package/src/modes/interactive-mode.ts +48 -13
  75. package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
  76. package/src/modes/types.ts +10 -1
  77. package/src/modes/utils/ui-helpers.ts +23 -2
  78. package/src/prompts/agents/explore.md +1 -0
  79. package/src/prompts/agents/librarian.md +1 -0
  80. package/src/prompts/ci-green-request.md +5 -3
  81. package/src/prompts/dry-balance-bench.md +8 -0
  82. package/src/prompts/system/project-prompt.md +1 -0
  83. package/src/sdk.ts +99 -18
  84. package/src/session/agent-session.ts +103 -19
  85. package/src/session/auth-storage.ts +4 -0
  86. package/src/session/blob-store.ts +96 -9
  87. package/src/session/session-manager.ts +19 -10
  88. package/src/system-prompt.ts +4 -0
  89. package/src/task/executor.ts +6 -2
  90. package/src/task/index.ts +8 -7
  91. package/src/task/types.ts +2 -0
  92. package/src/tiny/title-client.ts +7 -1
  93. package/src/tool-discovery/mode.ts +24 -0
  94. package/src/tools/archive-reader.ts +339 -31
  95. package/src/tools/bash.ts +3 -4
  96. package/src/tools/fetch.ts +29 -9
  97. package/src/tools/gh.ts +65 -11
  98. package/src/tools/index.ts +22 -8
  99. package/src/tools/job.ts +3 -3
  100. package/src/tools/memory-reflect.ts +2 -2
  101. package/src/tools/path-utils.ts +21 -0
  102. package/src/tools/read.ts +58 -12
  103. package/src/tools/search-tool-bm25.ts +4 -6
  104. package/src/tools/search.ts +78 -12
  105. package/src/tui/hyperlink.ts +42 -7
  106. package/src/utils/file-mentions.ts +7 -107
  107. package/src/utils/title-generator.ts +58 -37
  108. package/src/web/search/index.ts +2 -2
  109. package/src/web/search/render.ts +20 -52
@@ -56,6 +56,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
56
56
  todoContainer: Container;
57
57
  btwContainer: Container;
58
58
  omfgContainer: Container;
59
+ errorBannerContainer: Container;
59
60
  editor: CustomEditor;
60
61
  editorContainer: Container;
61
62
  hookWidgetContainerAbove: Container;
@@ -77,6 +78,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
77
78
  todoPhases: TodoPhase[];
78
79
  hideThinkingBlock: boolean;
79
80
  pendingImages: ImageContent[];
81
+ pendingImageLinks: (string | undefined)[];
80
82
  compactionQueuedMessages: CompactionQueuedMessage[];
81
83
  pendingTools: Map<string, ToolExecutionHandle>;
82
84
  pendingBashComponents: BashExecutionComponent[];
@@ -136,6 +138,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
136
138
  startPendingSubmission(input: {
137
139
  text: string;
138
140
  images?: ImageContent[];
141
+ imageLinks?: (string | undefined)[];
139
142
  customType?: string;
140
143
  display?: boolean;
141
144
  }): SubmittedUserInput;
@@ -162,6 +165,8 @@ export declare class InteractiveMode implements InteractiveModeContext {
162
165
  dim?: boolean;
163
166
  }): void;
164
167
  showError(message: string): void;
168
+ showPinnedError(message: string): void;
169
+ clearPinnedError(): void;
165
170
  showWarning(message: string): void;
166
171
  ensureLoadingAnimation(): void;
167
172
  setWorkingMessage(message?: string): void;
@@ -177,6 +182,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
177
182
  isKnownSlashCommand(text: string): boolean;
178
183
  addMessageToChat(message: AgentMessage, options?: {
179
184
  populateHistory?: boolean;
185
+ imageLinks?: readonly (string | undefined)[];
180
186
  }): Component[];
181
187
  renderSessionContext(sessionContext: SessionContext, options?: {
182
188
  updateFooter?: boolean;
@@ -184,6 +190,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
184
190
  }): void;
185
191
  renderInitialMessages(prebuiltContext?: SessionContext, options?: {
186
192
  preserveExistingChat?: boolean;
193
+ clearTerminalHistory?: boolean;
187
194
  }): void;
188
195
  getUserMessageText(message: Message): string;
189
196
  findLastAssistantMessage(): AssistantMessage | undefined;
@@ -32,6 +32,7 @@ export type CompactionQueuedMessage = {
32
32
  export type SubmittedUserInput = {
33
33
  text: string;
34
34
  images?: ImageContent[];
35
+ imageLinks?: (string | undefined)[];
35
36
  customType?: string;
36
37
  display?: boolean;
37
38
  cancelled: boolean;
@@ -60,6 +61,7 @@ export interface InteractiveModeContext {
60
61
  todoContainer: Container;
61
62
  btwContainer: Container;
62
63
  omfgContainer: Container;
64
+ errorBannerContainer: Container;
63
65
  editor: CustomEditor;
64
66
  editorContainer: Container;
65
67
  hookWidgetContainerAbove: Container;
@@ -87,6 +89,7 @@ export interface InteractiveModeContext {
87
89
  planModePlanFilePath?: string;
88
90
  hideThinkingBlock: boolean;
89
91
  pendingImages: ImageContent[];
92
+ pendingImageLinks: (string | undefined)[];
90
93
  compactionQueuedMessages: CompactionQueuedMessage[];
91
94
  pendingTools: Map<string, ToolExecutionHandle>;
92
95
  pendingBashComponents: BashExecutionComponent[];
@@ -130,6 +133,8 @@ export interface InteractiveModeContext {
130
133
  dim?: boolean;
131
134
  }): void;
132
135
  showError(message: string): void;
136
+ showPinnedError(message: string): void;
137
+ clearPinnedError(): void;
133
138
  showWarning(message: string): void;
134
139
  showNewVersionNotification(newVersion: string): void;
135
140
  clearEditor(): void;
@@ -146,6 +151,7 @@ export interface InteractiveModeContext {
146
151
  startPendingSubmission(input: {
147
152
  text: string;
148
153
  images?: ImageContent[];
154
+ imageLinks?: (string | undefined)[];
149
155
  customType?: string;
150
156
  display?: boolean;
151
157
  }): SubmittedUserInput;
@@ -170,6 +176,7 @@ export interface InteractiveModeContext {
170
176
  isKnownSlashCommand(text: string): boolean;
171
177
  addMessageToChat(message: AgentMessage, options?: {
172
178
  populateHistory?: boolean;
179
+ imageLinks?: readonly (string | undefined)[];
173
180
  }): Component[];
174
181
  renderSessionContext(sessionContext: SessionContext, options?: {
175
182
  updateFooter?: boolean;
@@ -24,6 +24,7 @@ export declare class UiHelpers {
24
24
  }): void;
25
25
  addMessageToChat(message: AgentMessage, options?: {
26
26
  populateHistory?: boolean;
27
+ imageLinks?: readonly (string | undefined)[];
27
28
  }): Component[];
28
29
  /**
29
30
  * Render session context to chat. Used for initial load and rebuild after compaction.
@@ -201,6 +201,15 @@ export interface AgentSessionConfig {
201
201
  * **MUST NOT** dispose it on their own teardown.
202
202
  */
203
203
  ownedAsyncJobManager?: AsyncJobManager;
204
+ /**
205
+ * AsyncJobManager reachable by this session for scoped job actions.
206
+ *
207
+ * Top-level owners receive their own manager, subagents receive the inherited
208
+ * parent manager, and secondary in-process top-level sessions receive
209
+ * `undefined` so job snapshots and ACP drains cannot observe the primary's
210
+ * state.
211
+ */
212
+ asyncJobManager?: AsyncJobManager;
204
213
  /** Agent identity (registry id like "Main" or "Alice") used for IRC routing. */
205
214
  agentId?: string;
206
215
  /** Shared agent registry (for forwarding IRC observations to the main session UI). */
@@ -2,5 +2,5 @@
2
2
  * Re-exports from @oh-my-pi/pi-ai.
3
3
  * All credential storage types and the AuthStorage class now live in the ai package.
4
4
  */
5
- export type { ApiKeyCredential, AuthCredential, AuthCredentialEntry, AuthCredentialStore, AuthStorageData, AuthStorageOptions, OAuthCredential, SerializedAuthStorage, StoredAuthCredential, } from "@oh-my-pi/pi-ai";
6
- export { AuthBrokerClient, AuthStorage, REMOTE_REFRESH_SENTINEL, RemoteAuthCredentialStore, SqliteAuthCredentialStore, } from "@oh-my-pi/pi-ai";
5
+ export type { ApiKeyCredential, AuthCredential, AuthCredentialEntry, AuthCredentialStore, AuthStorageData, AuthStorageOptions, OAuthCredential, SerializedAuthStorage, SnapshotResponse, StoredAuthCredential, } from "@oh-my-pi/pi-ai";
6
+ export { AuthBrokerClient, AuthStorage, DEFAULT_SNAPSHOT_CACHE_TTL_MS, REMOTE_REFRESH_SENTINEL, RemoteAuthCredentialStore, readAuthBrokerSnapshotCache, SqliteAuthCredentialStore, writeAuthBrokerSnapshotCache, } from "@oh-my-pi/pi-ai";
@@ -1,15 +1,16 @@
1
+ export interface BlobPutOptions {
2
+ /** Optional file extension for a sidecar hardlink/copy that OS openers can type-detect. */
3
+ extension?: string;
4
+ }
1
5
  export interface BlobPutResult {
2
6
  hash: string;
7
+ /** Canonical content-addressed path, always `<dir>/<sha256-hex>`. */
3
8
  path: string;
9
+ /** Path with the requested extension when supplied, otherwise the canonical path. */
10
+ displayPath: string;
4
11
  get ref(): string;
5
12
  }
6
- /**
7
- * Content-addressed blob store for externalizing large binary data (images) from session JSONL files.
8
- *
9
- * Files are stored at `<dir>/<sha256-hex>` with no extension. The SHA-256 hash is computed
10
- * over the raw binary data (not base64). Content-addressing makes writes idempotent and
11
- * provides automatic deduplication across sessions.
12
- */
13
+ export declare function blobExtensionForImageMimeType(mimeType: string | undefined): string | undefined;
13
14
  export declare class BlobStore {
14
15
  readonly dir: string;
15
16
  constructor(dir: string);
@@ -17,13 +18,13 @@ export declare class BlobStore {
17
18
  * Write binary data to the blob store.
18
19
  * @returns SHA-256 hex hash of the data
19
20
  */
20
- put(data: Buffer): Promise<BlobPutResult>;
21
+ put(data: Buffer, options?: BlobPutOptions): Promise<BlobPutResult>;
21
22
  /**
22
23
  * Synchronous variant of {@link put}. Use on persistence hot paths where the caller
23
24
  * cannot afford the microtask hops of the async version (e.g. OOM-safe session writes).
24
25
  * Returns once the bytes are in the kernel page cache.
25
26
  */
26
- putSync(data: Buffer): BlobPutResult;
27
+ putSync(data: Buffer, options?: BlobPutOptions): BlobPutResult;
27
28
  /** Read blob by hash, returns Buffer or null if not found. */
28
29
  get(hash: string): Promise<Buffer | null>;
29
30
  /** Check if a blob exists. */
@@ -46,9 +47,9 @@ export declare function externalizeImageDataUrlSync(blobStore: BlobStore, dataUr
46
47
  * Externalize an image's base64 data to the blob store, returning a blob reference.
47
48
  * If the data is already a blob reference, returns it unchanged.
48
49
  */
49
- export declare function externalizeImageData(blobStore: BlobStore, base64Data: string): Promise<string>;
50
+ export declare function externalizeImageData(blobStore: BlobStore, base64Data: string, mimeType?: string): Promise<string>;
50
51
  /** Synchronous variant of {@link externalizeImageData}. */
51
- export declare function externalizeImageDataSync(blobStore: BlobStore, base64Data: string): string;
52
+ export declare function externalizeImageDataSync(blobStore: BlobStore, base64Data: string, mimeType?: string): string;
52
53
  /**
53
54
  * Resolve an externalized provider image data URL back to its original string.
54
55
  * If the data is not a blob reference, returns it unchanged.
@@ -1,7 +1,7 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { ImageContent, Message, MessageAttribution, ServiceTier, TextContent } from "@oh-my-pi/pi-ai";
3
3
  import { ArtifactManager } from "./artifacts";
4
- import { type BlobPutResult } from "./blob-store";
4
+ import { type BlobPutOptions, type BlobPutResult } from "./blob-store";
5
5
  import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages";
6
6
  import type { SessionStorage } from "./session-storage";
7
7
  export declare const CURRENT_SESSION_VERSION = 3;
@@ -208,7 +208,7 @@ export interface SessionInfo {
208
208
  */
209
209
  status?: SessionStatus;
210
210
  }
211
- export type ReadonlySessionManager = Pick<SessionManager, "getCwd" | "getSessionDir" | "getSessionId" | "getSessionFile" | "getSessionName" | "getArtifactsDir" | "getArtifactManager" | "allocateArtifactPath" | "saveArtifact" | "getArtifactPath" | "getLeafId" | "getLeafEntry" | "getEntry" | "getLabel" | "getBranch" | "getHeader" | "getEntries" | "getTree" | "getUsageStatistics" | "putBlob">;
211
+ export type ReadonlySessionManager = Pick<SessionManager, "getCwd" | "getSessionDir" | "getSessionId" | "getSessionFile" | "getSessionName" | "getArtifactsDir" | "getArtifactManager" | "allocateArtifactPath" | "saveArtifact" | "getArtifactPath" | "getLeafId" | "getLeafEntry" | "getEntry" | "getLabel" | "getBranch" | "getHeader" | "getEntries" | "getTree" | "getUsageStatistics" | "putBlob" | "putBlobSync">;
212
212
  /** Exported for testing */
213
213
  export declare function migrateSessionEntries(entries: FileEntry[]): void;
214
214
  /** Exported for compaction.test.ts */
@@ -294,7 +294,9 @@ export declare class SessionManager {
294
294
  private readonly storage;
295
295
  private constructor();
296
296
  /** Puts a binary blob into the blob store and returns the blob reference */
297
- putBlob(data: Buffer): Promise<BlobPutResult>;
297
+ putBlob(data: Buffer, options?: BlobPutOptions): Promise<BlobPutResult>;
298
+ /** Synchronous variant of {@link putBlob} for rebuild-only render paths. */
299
+ putBlobSync(data: Buffer, options?: BlobPutOptions): BlobPutResult;
298
300
  captureState(): SessionManagerStateSnapshot;
299
301
  restoreState(snapshot: SessionManagerStateSnapshot): void;
300
302
  /** Switch to a different session file (used for resume and branching) */
@@ -84,6 +84,8 @@ export interface BuildSystemPromptOptions {
84
84
  workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
85
85
  /** Whether the local memory://root summary is active. */
86
86
  memoryRootEnabled?: boolean;
87
+ /** Active model identifier (e.g. "anthropic/claude-opus-4") surfaced to the agent. */
88
+ model?: string;
87
89
  }
88
90
  /** Result of building provider-facing system prompt messages. */
89
91
  export interface BuildSystemPromptResult {
@@ -149,6 +149,8 @@ export interface AgentDefinition {
149
149
  output?: unknown;
150
150
  blocking?: boolean;
151
151
  autoloadSkills?: string[];
152
+ /** When `false`, the agent's `read` tool returns verbatim file content instead of structural summaries. */
153
+ readSummarize?: boolean;
152
154
  source: AgentSource;
153
155
  filePath?: string;
154
156
  }
@@ -1,5 +1,19 @@
1
1
  import type { Subprocess } from "bun";
2
- import type { TinyTitleProgressEvent, TinyTitleWorkerOutbound } from "./title-protocol";
2
+ import type { TinyTitleProgressEvent, TinyTitleWorkerInbound, TinyTitleWorkerOutbound } from "./title-protocol";
3
+ /**
4
+ * Abstraction over the tiny-model subprocess. Modelled as a worker interface
5
+ * so existing callers (titles, memory completions, downloads) compose the
6
+ * same way; the runtime implementation is a Bun child process so
7
+ * `onnxruntime-node`'s NAPI finalizer never runs inside the main agent
8
+ * address space — that destructor segfaults Bun on Windows during shutdown
9
+ * (issue #1606).
10
+ */
11
+ interface WorkerHandle {
12
+ send(message: TinyTitleWorkerInbound): void;
13
+ onMessage(handler: (message: TinyTitleWorkerOutbound) => void): () => void;
14
+ onError(handler: (error: Error) => void): () => void;
15
+ terminate(): Promise<void>;
16
+ }
3
17
  export interface TinyTitleDownloadOptions {
4
18
  signal?: AbortSignal;
5
19
  onProgress?: (event: TinyTitleProgressEvent) => void;
@@ -39,6 +53,7 @@ interface SpawnedSubprocess {
39
53
  export declare function createTinyTitleSubprocess(): SpawnedSubprocess;
40
54
  export declare class TinyTitleClient {
41
55
  #private;
56
+ constructor(spawnWorker?: () => WorkerHandle);
42
57
  onProgress(listener: (event: TinyTitleProgressEvent) => void): () => void;
43
58
  generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null>;
44
59
  complete(modelKey: string, prompt: string, options?: {
@@ -0,0 +1,8 @@
1
+ import type { Settings } from "../config/settings";
2
+ import type { SettingValue } from "../config/settings-schema";
3
+ export declare const TOOL_DISCOVERY_AUTO_THRESHOLD = 40;
4
+ export declare const TOOL_DISCOVERY_SEARCH_TOOL_NAME = "search_tool_bm25";
5
+ export type ToolDiscoveryModeSetting = SettingValue<"tools.discoveryMode">;
6
+ export type EffectiveToolDiscoveryMode = Exclude<ToolDiscoveryModeSetting, "auto">;
7
+ export declare function countToolsForAutoDiscovery(toolNames: Iterable<string>): number;
8
+ export declare function resolveEffectiveToolDiscoveryMode(settings: Settings, toolCount: number): EffectiveToolDiscoveryMode;
@@ -21,7 +21,11 @@ interface TarStorage {
21
21
  }
22
22
  interface ZipStorage {
23
23
  type: "zip";
24
- bytes: Uint8Array;
24
+ archivePath: string;
25
+ compressedSize: number;
26
+ compression: number;
27
+ flags: number;
28
+ localHeaderOffset: number;
25
29
  }
26
30
  type EntryStorage = TarStorage | ZipStorage;
27
31
  interface ArchiveIndexEntry extends ArchiveNode {
@@ -1,6 +1,7 @@
1
1
  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 { ToolChoice } from "@oh-my-pi/pi-ai";
4
+ import type { AsyncJobManager } from "../async/job-manager";
4
5
  import type { PromptTemplate } from "../config/prompt-templates";
5
6
  import type { Settings } from "../config/settings";
6
7
  import type { Skill } from "../extensibility/skills";
@@ -139,6 +140,21 @@ export interface ToolSession {
139
140
  modelRegistry?: import("../config/model-registry").ModelRegistry;
140
141
  /** Agent output manager for unique agent:// IDs across task invocations */
141
142
  agentOutputManager?: AgentOutputManager;
143
+ /**
144
+ * Async job manager scoped to this session.
145
+ *
146
+ * - Top-level session that constructed one: its own manager.
147
+ * - Subagent (`parentTaskPrefix` set): the parent's manager, so background
148
+ * bash/task work and `onJobComplete` deliveries flow into the conversation
149
+ * that spawned it.
150
+ * - Secondary in-process top-level session that found a singleton already
151
+ * installed (issue #1923): `undefined`. Tools refuse async work rather
152
+ * than silently route completions into the owning session's `yieldQueue`.
153
+ *
154
+ * Tools MUST use this instead of `AsyncJobManager.instance()` so a secondary
155
+ * session never borrows the owning session's manager by accident.
156
+ */
157
+ asyncJobManager?: AsyncJobManager;
142
158
  /** MCP manager visible to subagents without relying on the process-global singleton. */
143
159
  mcpManager?: MCPManager;
144
160
  /** Local protocol root to propagate to nested subagents and eval-created agents. */
@@ -17,6 +17,17 @@ export declare function parseLineRangeChunk(sel: string): LineRange | null;
17
17
  * downstream consumers can stream the file in a single forward pass per range.
18
18
  */
19
19
  export declare function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null;
20
+ /**
21
+ * Extract the line-range component from a read-tool selector that may also
22
+ * carry a verbatim/index display mode (`raw`, `conflicts`) — alone or compounded
23
+ * with a range (`raw:50-100`, `50-100:raw`). Returns the parsed ranges when the
24
+ * selector names any, otherwise `undefined` (pure `raw`/`conflicts`/none).
25
+ *
26
+ * Used by content search, which honors line ranges as a match filter but has no
27
+ * use for verbatim/conflict display modes — so those selectors are accepted and
28
+ * treated as an unfiltered, whole-resource search rather than rejected.
29
+ */
30
+ export declare function selectorLineRanges(sel: string | undefined): [LineRange, ...LineRange[]] | undefined;
20
31
  /** Return `true` when `lineNumber` (1-indexed) falls in any of the supplied ranges. */
21
32
  export declare function isLineInRanges(lineNumber: number, ranges: readonly LineRange[]): boolean;
22
33
  export declare function splitPathAndSel(rawPath: string): {
@@ -7,6 +7,18 @@
7
7
  * - `"always"`: unconditionally (useful for viewers that support OSC 8 without advertising it)
8
8
  */
9
9
  export declare function isHyperlinkEnabled(): boolean;
10
+ /**
11
+ * Wrap `displayText` in an OSC 8 hyperlink pointing at `uri`.
12
+ *
13
+ * Returns `displayText` unchanged when hyperlinks are disabled, `uri` contains
14
+ * terminal control bytes, or `displayText` already contains an OSC 8 sequence.
15
+ */
16
+ export declare function uriHyperlink(uri: string, displayText: string): string;
17
+ /**
18
+ * Wrap `displayText` in an OSC 8 hyperlink pointing at an HTTP(S) URL.
19
+ * `www.example.com` inputs are linked as `https://www.example.com`.
20
+ */
21
+ export declare function urlHyperlink(url: string, displayText: string): string;
10
22
  /**
11
23
  * Wrap `displayText` in an OSC 8 hyperlink pointing at the given absolute file path.
12
24
  *
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { Component } from "@oh-my-pi/pi-tui";
7
7
  import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
8
- import type { Theme } from "../../modes/theme/theme";
8
+ import { type Theme } from "../../modes/theme/theme";
9
9
  import type { SearchResponse } from "./types";
10
10
  export interface SearchRenderDetails {
11
11
  response: SearchResponse;
@@ -20,7 +20,6 @@ export declare function renderSearchResult(result: {
20
20
  details?: SearchRenderDetails;
21
21
  }, options: RenderResultOptions, theme: Theme, args?: {
22
22
  query?: string;
23
- allowLongAnswer?: boolean;
24
23
  maxAnswerLines?: number;
25
24
  }): Component;
26
25
  /** Render web search call (query preview) */
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.9.1",
4
+ "version": "15.9.5",
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.9.1",
51
- "@oh-my-pi/omp-stats": "15.9.1",
52
- "@oh-my-pi/pi-agent-core": "15.9.1",
53
- "@oh-my-pi/pi-ai": "15.9.1",
54
- "@oh-my-pi/pi-mnemopi": "15.9.1",
55
- "@oh-my-pi/pi-natives": "15.9.1",
56
- "@oh-my-pi/pi-tui": "15.9.1",
57
- "@oh-my-pi/pi-utils": "15.9.1",
50
+ "@oh-my-pi/hashline": "15.9.5",
51
+ "@oh-my-pi/omp-stats": "15.9.5",
52
+ "@oh-my-pi/pi-agent-core": "15.9.5",
53
+ "@oh-my-pi/pi-ai": "15.9.5",
54
+ "@oh-my-pi/pi-mnemopi": "15.9.5",
55
+ "@oh-my-pi/pi-natives": "15.9.5",
56
+ "@oh-my-pi/pi-tui": "15.9.5",
57
+ "@oh-my-pi/pi-utils": "15.9.5",
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",
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Classify an install spec as a marketplace plugin reference or a plain npm package.
2
+ * Classify an install spec as a local path, marketplace plugin reference, or
3
+ * plain npm package.
3
4
  *
4
5
  * Rules (applied in order):
6
+ * 0. Looks like a filesystem path (`.`, `..`, `./…`, `..\…`, `/…`, `~/…`,
7
+ * `C:\…`, `\\unc`) -> local. Routed through `PluginManager.link()` so the
8
+ * `omp plugin install <path>` and `omp plugin link <path>` flows agree.
5
9
  * 1. Starts with `@` (scoped npm) -> always npm.
6
10
  * 2. Contains `@` after the first character -> split on the LAST `@`.
7
11
  * If the right-hand side is a known marketplace name, it's a marketplace ref.
@@ -25,10 +29,32 @@ const NPM_DIST_TAGS = new Set([
25
29
  // Semver-like: starts with digit, or contains version range prefixes
26
30
  const LOOKS_LIKE_VERSION = /^[\d~^>=<]/;
27
31
 
28
- export function classifyInstallTarget(
29
- spec: string,
30
- knownMarketplaces: Set<string>,
31
- ): { type: "marketplace"; name: string; marketplace: string } | { type: "npm"; spec: string } {
32
+ /**
33
+ * Detect specs that name a filesystem path rather than a package: bare `.` /
34
+ * `..`, cwd-relative (`./`, `../`, `.\`, `..\`), absolute (`/`, `C:\`, `C:/`,
35
+ * UNC `\\`), and tilde-prefixed (`~`, `~/`, `~\`). Tilde paths still rely on
36
+ * the shell or the caller for expansion — we only classify them so they reach
37
+ * the link path instead of npm-name validation.
38
+ */
39
+ function isLocalPathSpec(spec: string): boolean {
40
+ if (spec === "." || spec === ".." || spec === "~") return true;
41
+ if (spec.startsWith("./") || spec.startsWith("../")) return true;
42
+ if (spec.startsWith(".\\") || spec.startsWith("..\\")) return true;
43
+ if (spec.startsWith("~/") || spec.startsWith("~\\")) return true;
44
+ if (spec.startsWith("/")) return true;
45
+ if (spec.startsWith("\\\\")) return true;
46
+ if (/^[A-Za-z]:[\\/]/.test(spec)) return true;
47
+ return false;
48
+ }
49
+
50
+ export type ClassifiedInstallTarget =
51
+ | { type: "local"; path: string }
52
+ | { type: "marketplace"; name: string; marketplace: string }
53
+ | { type: "npm"; spec: string };
54
+
55
+ export function classifyInstallTarget(spec: string, knownMarketplaces: Set<string>): ClassifiedInstallTarget {
56
+ // Rule 0: filesystem path — bypass npm/marketplace validation entirely.
57
+ if (isLocalPathSpec(spec)) return { type: "local", path: spec };
32
58
  // Rule 1: scoped npm package — @ at position 0 is never a marketplace separator.
33
59
  if (spec.startsWith("@")) return { type: "npm", spec };
34
60
  // Rule 2: @ somewhere after the first character.