@oh-my-pi/pi-coding-agent 15.7.3 → 15.7.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.
- package/CHANGELOG.md +39 -0
- package/dist/types/config/settings-schema.d.ts +3 -22
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/local-protocol.d.ts +19 -9
- package/dist/types/internal-urls/types.d.ts +14 -0
- package/dist/types/lsp/client.d.ts +3 -0
- package/dist/types/mcp/manager.d.ts +14 -5
- package/dist/types/modes/controllers/command-controller.d.ts +2 -3
- package/dist/types/session/agent-session.d.ts +2 -6
- package/dist/types/session/shake-types.d.ts +3 -3
- package/dist/types/task/repair-args.d.ts +52 -0
- package/dist/types/tiny/models.d.ts +0 -14
- package/dist/types/tiny/title-client.d.ts +28 -2
- package/dist/types/tiny/title-protocol.d.ts +8 -9
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tui/output-block.d.ts +7 -7
- package/package.json +9 -9
- package/scripts/build-binary.ts +0 -1
- package/src/cli.ts +59 -0
- package/src/config/settings-schema.ts +3 -24
- package/src/config/settings.ts +10 -0
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/internal-urls/local-protocol.ts +23 -11
- package/src/internal-urls/types.ts +15 -0
- package/src/lsp/client.ts +28 -5
- package/src/mcp/manager.ts +87 -4
- package/src/modes/controllers/command-controller.ts +7 -39
- package/src/modes/controllers/event-controller.ts +33 -26
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/prompts/system/project-prompt.md +3 -2
- package/src/prompts/system/subagent-system-prompt.md +12 -8
- package/src/prompts/system/system-prompt.md +8 -6
- package/src/session/agent-session.ts +11 -93
- package/src/session/shake-types.ts +4 -5
- package/src/slash-commands/builtin-registry.ts +2 -4
- package/src/task/executor.ts +14 -4
- package/src/task/index.ts +3 -2
- package/src/task/repair-args.ts +117 -0
- package/src/tiny/models.ts +0 -28
- package/src/tiny/title-client.ts +133 -43
- package/src/tiny/title-protocol.ts +11 -16
- package/src/tiny/worker.ts +6 -61
- package/src/tools/ast-edit.ts +3 -0
- package/src/tools/ast-grep.ts +3 -0
- package/src/tools/find.ts +20 -6
- package/src/tools/gh.ts +1 -0
- package/src/tools/path-utils.ts +13 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +12 -1
- package/src/tui/output-block.ts +37 -75
- package/src/utils/git.ts +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.7.5] - 2026-06-01
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- Fixed streaming assistant responses leaving duplicated tail rows in WSL/Windows Terminal scrollback by enabling eager native-scrollback rebuilds while assistant text is actively streaming ([#1615](https://github.com/can1357/oh-my-pi/issues/1615)).
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fixed the `task` tool mangling subagent prompts when a model double-JSON-encodes a string argument: `context` and each task's `assignment`/`description` are now repaired when they arrive uniformly double-escaped (literal `\n`, `\"`, `\uXXXX`), so the subagent receives the intended prose and the call preview renders real newlines. The repair is guarded by a JSON-string round-trip and a double-encode signature, so legitimate backslashes/quotes (Windows paths, regexes, embedded quotes) are left untouched, and it is scoped to these natural-language fields only (never code-bearing tools).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed `pr://` PR views omitting formal review submissions and approvals when comments are enabled ([#1600](https://github.com/can1357/oh-my-pi/issues/1600)).
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fixed subagent yield-reminder loop logging benign user/compaction aborts as `ERROR`. The catch around `session.prompt`/`waitForIdle` in `task/executor.ts` now demotes `ToolAbortError` and signal-aborted exits to `debug` and keeps `ERROR` for genuine prompt failures only ([#1623](https://github.com/can1357/oh-my-pi/issues/1623)).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed `read local://<file>` resolving to the wrong session's artifacts directory in multi-session ACP hosts (e.g. cmux). `LocalProtocolHandler.resolve` now honors `context.localProtocolOptions` supplied by the calling tool before falling back to the process-wide override or the first `main`-kind session in the global `AgentRegistry`; `read`, `find`, `search`, `ast_grep`, and `ast_edit` thread their session's options through so a `local://PLAN.md` lookup hits the calling session's `local` root instead of a sibling session's ([#1608](https://github.com/can1357/oh-my-pi/issues/1608)).
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Fixed `omp` segfaulting on exit on Windows after the tiny title/memory model loaded `onnxruntime-node` (issue [#1606](https://github.com/can1357/oh-my-pi/issues/1606)). The tiny model now runs in a Bun subprocess instead of a Worker thread, so the NAPI finalizer that crashes during shutdown never executes in the agent's address space; the subprocess is `SIGKILL`'d on dispose to skip every native destructor on every platform.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Fixed unbounded MCP reconnect loop that could fork-bomb the host when a stdio MCP server completes the `initialize`/`tools/list` handshake and then exits. `MCPManager` now enforces a per-server crash circuit breaker (5 reconnects per 30 s window) on the automatic `transport.onClose` path; manual `/mcp reconnect` resets the window so users can recover after fixing the misconfiguration ([#1592](https://github.com/can1357/oh-my-pi/issues/1592)).
|
|
33
|
+
|
|
34
|
+
## [15.7.4] - 2026-05-31
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
|
|
38
|
+
- Removed `/shake summary`, the `shake-summary` auto-compaction strategy, and the `providers.shakeSummaryModel` setting. Use `/shake` or `compaction.strategy: shake` for mechanical artifact-backed elision without local-model CPU.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Fixed plugin install failing for sources pinned to a SHA: `git.clone()` no longer adds `--depth 1` when `options.sha` is set, so the checkout of arbitrary commits succeeds instead of bailing out with "shallow clone may not contain this commit" ([#1589](https://github.com/can1357/oh-my-pi/issues/1589)).
|
|
43
|
+
|
|
5
44
|
## [15.7.3] - 2026-05-31
|
|
6
45
|
### Added
|
|
7
46
|
|
|
@@ -1298,12 +1298,12 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1298
1298
|
};
|
|
1299
1299
|
readonly "compaction.strategy": {
|
|
1300
1300
|
readonly type: "enum";
|
|
1301
|
-
readonly values: readonly ["context-full", "handoff", "shake", "
|
|
1301
|
+
readonly values: readonly ["context-full", "handoff", "shake", "off"];
|
|
1302
1302
|
readonly default: "context-full";
|
|
1303
1303
|
readonly ui: {
|
|
1304
1304
|
readonly tab: "context";
|
|
1305
1305
|
readonly label: "Compaction Strategy";
|
|
1306
|
-
readonly description: "Choose in-place context-full maintenance, auto-handoff, surgical shake (drop heavy content),
|
|
1306
|
+
readonly description: "Choose in-place context-full maintenance, auto-handoff, surgical shake (drop heavy content), or disable auto maintenance (off)";
|
|
1307
1307
|
readonly options: readonly [{
|
|
1308
1308
|
readonly value: "context-full";
|
|
1309
1309
|
readonly label: "Context-full";
|
|
@@ -1316,10 +1316,6 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1316
1316
|
readonly value: "shake";
|
|
1317
1317
|
readonly label: "Shake";
|
|
1318
1318
|
readonly description: "Drop heavy content (tool results + large blocks) in place; recover via artifact";
|
|
1319
|
-
}, {
|
|
1320
|
-
readonly value: "shake-summary";
|
|
1321
|
-
readonly label: "Shake (summary)";
|
|
1322
|
-
readonly description: "Shake, but compress heavy regions with a local on-device model instead of dropping";
|
|
1323
1319
|
}, {
|
|
1324
1320
|
readonly value: "off";
|
|
1325
1321
|
readonly label: "Off";
|
|
@@ -3560,21 +3556,6 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
3560
3556
|
})[];
|
|
3561
3557
|
};
|
|
3562
3558
|
};
|
|
3563
|
-
readonly "providers.shakeSummaryModel": {
|
|
3564
|
-
readonly type: "enum";
|
|
3565
|
-
readonly values: readonly ["qwen3-1.7b", "gemma-3-1b", "qwen2.5-1.5b", "lfm2-1.2b"];
|
|
3566
|
-
readonly default: "gemma-3-1b" | "lfm2-1.2b" | "qwen2.5-1.5b" | "qwen3-1.7b";
|
|
3567
|
-
readonly ui: {
|
|
3568
|
-
readonly tab: "context";
|
|
3569
|
-
readonly label: "Shake Summary Model";
|
|
3570
|
-
readonly description: "Local on-device model used by /shake summary and the shake-summary compaction strategy to compress heavy regions. Runs entirely on-device; downloads on first use. Falls back to plain elide when unavailable.";
|
|
3571
|
-
readonly options: {
|
|
3572
|
-
value: "gemma-3-1b" | "lfm2-1.2b" | "qwen2.5-1.5b" | "qwen3-1.7b";
|
|
3573
|
-
label: "Gemma 3 1B" | "LFM2 1.2B" | "Qwen2.5 1.5B" | "Qwen3 1.7B";
|
|
3574
|
-
description: "Best consolidation/dedup; lighter footprint, but leaks small talk during extraction." | "Best extraction granularity (atomic facts); weaker consolidation." | "Fastest load; solid all-rounder, slightly noisier extraction labels." | "Recommended; most disciplined extraction (ignores chit-chat), good consolidation, about 1.1 GB cached.";
|
|
3575
|
-
}[];
|
|
3576
|
-
};
|
|
3577
|
-
};
|
|
3578
3559
|
readonly "providers.kimiApiFormat": {
|
|
3579
3560
|
readonly type: "enum";
|
|
3580
3561
|
readonly values: readonly ["openai", "anthropic"];
|
|
@@ -3868,7 +3849,7 @@ export type StatusLineSeparatorStyle = SettingValue<"statusLine.separator">;
|
|
|
3868
3849
|
export type TreeFilterMode = SettingValue<"treeFilterMode">;
|
|
3869
3850
|
export interface CompactionSettings {
|
|
3870
3851
|
enabled: boolean;
|
|
3871
|
-
strategy: "context-full" | "handoff" | "shake" | "
|
|
3852
|
+
strategy: "context-full" | "handoff" | "shake" | "off";
|
|
3872
3853
|
thresholdPercent: number;
|
|
3873
3854
|
thresholdTokens: number;
|
|
3874
3855
|
reserveTokens: number;
|
|
@@ -86,10 +86,10 @@ export type CustomToolSessionEvent = {
|
|
|
86
86
|
} | {
|
|
87
87
|
reason: "auto_compaction_start";
|
|
88
88
|
trigger: "threshold" | "overflow" | "idle" | "incomplete";
|
|
89
|
-
action: "context-full" | "handoff" | "shake"
|
|
89
|
+
action: "context-full" | "handoff" | "shake";
|
|
90
90
|
} | {
|
|
91
91
|
reason: "auto_compaction_end";
|
|
92
|
-
action: "context-full" | "handoff" | "shake"
|
|
92
|
+
action: "context-full" | "handoff" | "shake";
|
|
93
93
|
result: CompactionResult | undefined;
|
|
94
94
|
aborted: boolean;
|
|
95
95
|
willRetry: boolean;
|
|
@@ -160,12 +160,12 @@ export interface TurnEndEvent {
|
|
|
160
160
|
export interface AutoCompactionStartEvent {
|
|
161
161
|
type: "auto_compaction_start";
|
|
162
162
|
reason: "threshold" | "overflow" | "idle" | "incomplete";
|
|
163
|
-
action: "context-full" | "handoff" | "shake"
|
|
163
|
+
action: "context-full" | "handoff" | "shake";
|
|
164
164
|
}
|
|
165
165
|
/** Fired when auto-compaction ends */
|
|
166
166
|
export interface AutoCompactionEndEvent {
|
|
167
167
|
type: "auto_compaction_end";
|
|
168
|
-
action: "context-full" | "handoff" | "shake"
|
|
168
|
+
action: "context-full" | "handoff" | "shake";
|
|
169
169
|
result: CompactionResult | undefined;
|
|
170
170
|
aborted: boolean;
|
|
171
171
|
willRetry: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InternalResource, InternalUrl, ProtocolHandler, UrlCompletion } from "./types";
|
|
1
|
+
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
|
|
2
2
|
export interface LocalProtocolOptions {
|
|
3
3
|
getArtifactsDir?: () => string | null;
|
|
4
4
|
getSessionId?: () => string | null;
|
|
@@ -28,13 +28,23 @@ export declare class LocalProtocolHandler implements ProtocolHandler {
|
|
|
28
28
|
* Returns the active local-protocol options.
|
|
29
29
|
*
|
|
30
30
|
* Resolution order:
|
|
31
|
-
* 1.
|
|
32
|
-
* that
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
31
|
+
* 1. **Caller-supplied** `context.localProtocolOptions` (the actual session
|
|
32
|
+
* that initiated the `read`/`find`/`search`/`router.resolve` call). This
|
|
33
|
+
* is what keeps `local://` reads pinned to the calling session in
|
|
34
|
+
* multi-session hosts (cmux/ACP, embedded SDK consumers) where every
|
|
35
|
+
* session registers as `kind: "main"` and "first one wins" would route
|
|
36
|
+
* to the wrong artifacts directory.
|
|
37
|
+
* 2. Explicit process-global override installed via {@link setOverride}
|
|
38
|
+
* (used by SDK consumers with a custom artifacts/session-id mapping and
|
|
39
|
+
* by code paths that do not have a calling session, e.g. TUI hyperlink
|
|
40
|
+
* resolution).
|
|
41
|
+
* 3. The first `main`-kind session in `AgentRegistry.global()`. Its
|
|
42
|
+
* `SessionManager` supplies both `getArtifactsDir` and `getSessionId`.
|
|
43
|
+
* Last-resort fallback — every caller that has a session reference
|
|
44
|
+
* SHOULD thread it through `context` so this branch is never taken in
|
|
45
|
+
* multi-session setups.
|
|
36
46
|
*/
|
|
37
|
-
static resolveOptions(): LocalProtocolOptions | undefined;
|
|
38
|
-
resolve(url: InternalUrl): Promise<InternalResource>;
|
|
39
|
-
complete(): Promise<UrlCompletion[]>;
|
|
47
|
+
static resolveOptions(context?: ResolveContext): LocalProtocolOptions | undefined;
|
|
48
|
+
resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource>;
|
|
49
|
+
complete(_query?: string, context?: ResolveContext): Promise<UrlCompletion[]>;
|
|
40
50
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Internal URLs (agent://, artifact://, memory://, skill://, rule://, mcp://, omp://, local://) are resolved by tools like read,
|
|
5
5
|
* providing access to agent outputs and server resources without exposing filesystem paths.
|
|
6
6
|
*/
|
|
7
|
+
import type { LocalProtocolOptions } from "./local-protocol";
|
|
7
8
|
/**
|
|
8
9
|
* Raw resource payload returned by protocol handlers. The `immutable` flag is
|
|
9
10
|
* applied by the router from {@link ProtocolHandler.immutable}, so handlers do
|
|
@@ -73,6 +74,17 @@ export interface ResolveContext {
|
|
|
73
74
|
settings?: unknown;
|
|
74
75
|
/** Caller's abort signal. */
|
|
75
76
|
signal?: AbortSignal;
|
|
77
|
+
/**
|
|
78
|
+
* Calling session's `local://` root mapping. When present, the local-protocol
|
|
79
|
+
* handler resolves the URL against THIS session's artifacts dir instead of
|
|
80
|
+
* picking the first `main`-kind session from the global `AgentRegistry`.
|
|
81
|
+
*
|
|
82
|
+
* Required for correctness in multi-session hosts (cmux/ACP, embedded SDK
|
|
83
|
+
* consumers) where multiple sessions are registered as `main` and the
|
|
84
|
+
* "first one wins" lookup picks the wrong artifacts directory — see
|
|
85
|
+
* [#1608](https://github.com/can1357/oh-my-pi/issues/1608).
|
|
86
|
+
*/
|
|
87
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
76
88
|
}
|
|
77
89
|
/**
|
|
78
90
|
* Caller context for write operations dispatched to host-owned URI handlers.
|
|
@@ -84,6 +96,8 @@ export interface WriteContext {
|
|
|
84
96
|
cwd?: string;
|
|
85
97
|
/** Caller's abort signal. */
|
|
86
98
|
signal?: AbortSignal;
|
|
99
|
+
/** Calling session's `local://` root mapping — see {@link ResolveContext.localProtocolOptions}. */
|
|
100
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
87
101
|
}
|
|
88
102
|
/**
|
|
89
103
|
* Handler for a specific internal URL scheme (e.g., agent://, memory://, skill://, mcp://).
|
|
@@ -39,6 +39,9 @@ export declare function notifySaved(client: LspClient, filePath: string, signal?
|
|
|
39
39
|
* Increments version, sends didChange and didSave notifications.
|
|
40
40
|
*/
|
|
41
41
|
export declare function refreshFile(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Shutdown a specific client by key.
|
|
44
|
+
*/
|
|
42
45
|
export declare function shutdownClient(key: string): Promise<void>;
|
|
43
46
|
/**
|
|
44
47
|
* Send an LSP request and wait for response.
|
|
@@ -130,12 +130,21 @@ export declare class MCPManager {
|
|
|
130
130
|
disconnectAll(): Promise<void>;
|
|
131
131
|
/**
|
|
132
132
|
* Reconnect to a server after a connection failure.
|
|
133
|
+
*
|
|
133
134
|
* Tears down the stale connection, re-resolves auth, establishes a new
|
|
134
|
-
* connection, reloads tools, and notifies consumers.
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
* connection, reloads tools, and notifies consumers. Concurrent calls for
|
|
136
|
+
* the same server share one reconnection attempt. Returns the new
|
|
137
|
+
* connection, or `null` if reconnection failed or the per-server crash
|
|
138
|
+
* burst limit (see {@link RECONNECT_BURST_LIMIT}) is exceeded.
|
|
139
|
+
*
|
|
140
|
+
* @param options.manual - When `true`, resets the crash-burst window so a
|
|
141
|
+
* user-driven retry (e.g. `/mcp reconnect`) is never blocked by an
|
|
142
|
+
* earlier storm. Defaults to `false`; the transport `onClose` callback
|
|
143
|
+
* and the per-tool-call retry path in `tool-bridge` MUST NOT set it.
|
|
144
|
+
*/
|
|
145
|
+
reconnectServer(name: string, options?: {
|
|
146
|
+
manual?: boolean;
|
|
147
|
+
}): Promise<MCPServerConnection | null>;
|
|
139
148
|
/**
|
|
140
149
|
* Refresh tools from a specific server.
|
|
141
150
|
*/
|
|
@@ -31,9 +31,8 @@ export declare class CommandController {
|
|
|
31
31
|
handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
|
|
32
32
|
handleCompactCommand(customInstructions?: string): Promise<CompactionOutcome>;
|
|
33
33
|
/**
|
|
34
|
-
* TUI handler for `/shake`. `elide
|
|
35
|
-
* `
|
|
36
|
-
* (Esc aborts via `abortCompaction`). Rebuilds the chat and reports counts.
|
|
34
|
+
* TUI handler for `/shake`. `elide` drops heavy structural content and
|
|
35
|
+
* `images` strips image blocks. Rebuilds the chat and reports counts.
|
|
37
36
|
*/
|
|
38
37
|
handleShakeCommand(mode: ShakeMode): Promise<void>;
|
|
39
38
|
handleSkillCommand(skillPath: string, args: string): Promise<void>;
|
|
@@ -54,10 +54,10 @@ import { YieldQueue } from "./yield-queue";
|
|
|
54
54
|
export type AgentSessionEvent = AgentEvent | {
|
|
55
55
|
type: "auto_compaction_start";
|
|
56
56
|
reason: "threshold" | "overflow" | "idle" | "incomplete";
|
|
57
|
-
action: "context-full" | "handoff" | "shake"
|
|
57
|
+
action: "context-full" | "handoff" | "shake";
|
|
58
58
|
} | {
|
|
59
59
|
type: "auto_compaction_end";
|
|
60
|
-
action: "context-full" | "handoff" | "shake"
|
|
60
|
+
action: "context-full" | "handoff" | "shake";
|
|
61
61
|
result: CompactionResult | undefined;
|
|
62
62
|
aborted: boolean;
|
|
63
63
|
willRetry: boolean;
|
|
@@ -737,10 +737,6 @@ export declare class AgentSession {
|
|
|
737
737
|
* - `images` delegates to {@link dropImages}.
|
|
738
738
|
* - `elide` replaces whole tool-call results and large fenced/XML blocks
|
|
739
739
|
* with short placeholders that embed an `artifact://` recovery link.
|
|
740
|
-
* - `summary` extractively compresses the same regions with the configured
|
|
741
|
-
* local on-device model (`providers.shakeSummaryModel`), falling back to
|
|
742
|
-
* the elide placeholder per region (or wholesale when the local model is
|
|
743
|
-
* unavailable). Never calls a remote/cloud LLM.
|
|
744
740
|
*
|
|
745
741
|
* Mutates the branch in place, persists via `rewriteEntries`, replays the
|
|
746
742
|
* rebuilt context through the agent, and tears down provider sessions that
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* an import cycle through the slash-command registry).
|
|
6
6
|
*/
|
|
7
7
|
/** Mode selector for `AgentSession.shake`. */
|
|
8
|
-
export type ShakeMode = "elide" | "
|
|
8
|
+
export type ShakeMode = "elide" | "images";
|
|
9
9
|
/** Outcome of an `AgentSession.shake` run. */
|
|
10
10
|
export interface ShakeResult {
|
|
11
11
|
mode: ShakeMode;
|
|
12
|
-
/** Whole tool-call results dropped
|
|
12
|
+
/** Whole tool-call results dropped. */
|
|
13
13
|
toolResultsDropped: number;
|
|
14
|
-
/** Large fenced/XML blocks dropped
|
|
14
|
+
/** Large fenced/XML blocks dropped. */
|
|
15
15
|
blocksDropped: number;
|
|
16
16
|
/** Image blocks removed (images mode only). */
|
|
17
17
|
imagesDropped?: number;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair double-encoded JSON string arguments for the task tool.
|
|
3
|
+
*
|
|
4
|
+
* Models occasionally JSON-escape a string value twice when emitting a
|
|
5
|
+
* `task` tool call, so a `context`/`assignment` that should read
|
|
6
|
+
*
|
|
7
|
+
* # Role
|
|
8
|
+
* You are a judge … "describe this" … return —
|
|
9
|
+
*
|
|
10
|
+
* arrives — after the one JSON decode the provider already applied — as the
|
|
11
|
+
* literal text
|
|
12
|
+
*
|
|
13
|
+
* # Role\nYou are a judge … \"describe this\" … return \u2014
|
|
14
|
+
*
|
|
15
|
+
* i.e. every newline, quote, and unicode character is still backslash-escaped.
|
|
16
|
+
* The subagent then receives that garbled prompt, and the call preview renders
|
|
17
|
+
* one long blob with visible `\n` / `\"` / `\uXXXX`.
|
|
18
|
+
*
|
|
19
|
+
* The *whole-arguments* form of this quirk (the entire `arguments` blob is a
|
|
20
|
+
* JSON string) is already auto-corrected by the validator's JSON-string
|
|
21
|
+
* coercion. This module handles the *per-field* form, where the object parses
|
|
22
|
+
* fine but an individual string value is double-encoded — the validator never
|
|
23
|
+
* fires there because a double-encoded string is still a structurally valid
|
|
24
|
+
* string.
|
|
25
|
+
*
|
|
26
|
+
* This is deliberately scoped to the task tool's natural-language fields
|
|
27
|
+
* (`context`, `assignment`, `description`). It is NOT applied to code-bearing
|
|
28
|
+
* tools (write/edit/bash/search), where a backslash or quote is load-bearing
|
|
29
|
+
* and a false-positive unescape would silently corrupt a file or command.
|
|
30
|
+
*/
|
|
31
|
+
import type { TaskParams } from "./types";
|
|
32
|
+
/**
|
|
33
|
+
* Return the once-unescaped string when `value` is uniformly double-encoded
|
|
34
|
+
* JSON (a well-formed JSON string body that decodes to a different string);
|
|
35
|
+
* otherwise return `value` unchanged.
|
|
36
|
+
*
|
|
37
|
+
* The `JSON.parse(\`"${value}"\`)` round-trip is the safety net: it only
|
|
38
|
+
* succeeds when *every* backslash begins a valid JSON escape and no bare
|
|
39
|
+
* double-quote exists — exactly the signature of double-encoding. Genuine
|
|
40
|
+
* prose with a Windows path (`C:\Users`), a regex (`\d+`), an embedded quote,
|
|
41
|
+
* or a real (already-decoded) newline makes the parse throw, so the value is
|
|
42
|
+
* returned untouched.
|
|
43
|
+
*/
|
|
44
|
+
export declare function repairDoubleEncodedJsonString(value: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Repair double-encoded prose in task-tool params (`context` and each task's
|
|
47
|
+
* `assignment`/`description`). Returns the same reference when nothing changed
|
|
48
|
+
* so callers can cheaply skip work. Defensive against partially-streamed args
|
|
49
|
+
* (missing/undefined fields, partial task arrays) so it is safe on the render
|
|
50
|
+
* path as well as on execution.
|
|
51
|
+
*/
|
|
52
|
+
export declare function repairTaskParams(params: TaskParams): TaskParams;
|
|
@@ -113,20 +113,6 @@ export declare const TINY_MEMORY_MODEL_OPTIONS: ({
|
|
|
113
113
|
})[];
|
|
114
114
|
export declare function isTinyMemoryLocalModelKey(value: string): value is TinyMemoryLocalModelKey;
|
|
115
115
|
export declare function getTinyMemoryModelSpec(key: TinyMemoryLocalModelKey): (typeof TINY_MEMORY_LOCAL_MODELS)[number];
|
|
116
|
-
/**
|
|
117
|
-
* Shake-summary models. Shake's `summary` mode (and the `shake-summary`
|
|
118
|
-
* compaction strategy) compress heavy regions strictly on-device — there is no
|
|
119
|
-
* online/remote option, so this registry reuses the local memory models only.
|
|
120
|
-
*/
|
|
121
|
-
export declare const SHAKE_SUMMARY_MODEL_VALUES: readonly ["qwen3-1.7b", "gemma-3-1b", "qwen2.5-1.5b", "lfm2-1.2b"];
|
|
122
|
-
export type ShakeSummaryModelKey = (typeof SHAKE_SUMMARY_MODEL_VALUES)[number];
|
|
123
|
-
export declare const SHAKE_SUMMARY_MODEL_OPTIONS: {
|
|
124
|
-
value: "gemma-3-1b" | "lfm2-1.2b" | "qwen2.5-1.5b" | "qwen3-1.7b";
|
|
125
|
-
label: "Gemma 3 1B" | "LFM2 1.2B" | "Qwen2.5 1.5B" | "Qwen3 1.7B";
|
|
126
|
-
description: "Best consolidation/dedup; lighter footprint, but leaks small talk during extraction." | "Best extraction granularity (atomic facts); weaker consolidation." | "Fastest load; solid all-rounder, slightly noisier extraction labels." | "Recommended; most disciplined extraction (ignores chit-chat), good consolidation, about 1.1 GB cached.";
|
|
127
|
-
}[];
|
|
128
|
-
/** Default shake-summary local model when none is named. */
|
|
129
|
-
export declare const DEFAULT_SHAKE_SUMMARY_MODEL_KEY: ShakeSummaryModelKey;
|
|
130
116
|
/** Any local model key (title or memory), used by the shared inference worker. */
|
|
131
117
|
export type TinyLocalModelKey = TinyTitleLocalModelKey | TinyMemoryLocalModelKey;
|
|
132
118
|
/** Resolve a local model spec by key across both the title and memory registries. */
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Subprocess } from "bun";
|
|
2
|
+
import type { TinyTitleProgressEvent, TinyTitleWorkerOutbound } from "./title-protocol";
|
|
2
3
|
export interface TinyTitleDownloadOptions {
|
|
3
4
|
signal?: AbortSignal;
|
|
4
5
|
onProgress?: (event: TinyTitleProgressEvent) => void;
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Hidden subcommand on the main CLI that boots the tiny-model worker in the
|
|
9
|
+
* spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TINY_WORKER_ARG = "--tiny-worker";
|
|
6
12
|
/**
|
|
7
13
|
* Decide which `PI_TINY_DEVICE` / `PI_TINY_DTYPE` vars to overlay onto the worker
|
|
8
14
|
* env. A present env var wins (left untouched); otherwise the mapped persisted
|
|
@@ -11,7 +17,26 @@ export interface TinyTitleDownloadOptions {
|
|
|
11
17
|
* @internal
|
|
12
18
|
*/
|
|
13
19
|
export declare function tinyWorkerEnvOverlay(env: Record<string, string | undefined>, deviceSetting: string | undefined, dtypeSetting: string | undefined): Record<string, string>;
|
|
14
|
-
|
|
20
|
+
interface SpawnedSubprocess {
|
|
21
|
+
proc: Subprocess<"ignore", "inherit", "inherit">;
|
|
22
|
+
inbound: Set<(message: TinyTitleWorkerOutbound) => void>;
|
|
23
|
+
errors: Set<(error: Error) => void>;
|
|
24
|
+
/**
|
|
25
|
+
* Flipped to `true` by {@link wrapSubprocess}'s `terminate()` right
|
|
26
|
+
* before it SIGKILLs the child so `onExit` can distinguish the
|
|
27
|
+
* expected hard-kill from a crash/OOM/external signal. Only the
|
|
28
|
+
* latter is surfaced as a worker error.
|
|
29
|
+
*/
|
|
30
|
+
intentionalExit: {
|
|
31
|
+
value: boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Spawn the tiny-model worker as a subprocess. Exported for tests and the
|
|
36
|
+
* smoke probe; production callers go through {@link spawnTinyTitleWorker}
|
|
37
|
+
* which wraps the result in a {@link WorkerHandle}.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createTinyTitleSubprocess(): SpawnedSubprocess;
|
|
15
40
|
export declare class TinyTitleClient {
|
|
16
41
|
#private;
|
|
17
42
|
onProgress(listener: (event: TinyTitleProgressEvent) => void): () => void;
|
|
@@ -30,3 +55,4 @@ export declare function shutdownTinyTitleClient(): Promise<void>;
|
|
|
30
55
|
export declare function smokeTestTinyTitleWorker({ timeoutMs, }?: {
|
|
31
56
|
timeoutMs?: number;
|
|
32
57
|
}): Promise<void>;
|
|
58
|
+
export {};
|
|
@@ -30,16 +30,10 @@ export type TinyTitleWorkerInbound = {
|
|
|
30
30
|
modelKey: TinyLocalModelKey;
|
|
31
31
|
prompt: string;
|
|
32
32
|
maxTokens?: number;
|
|
33
|
-
/** Optional assistant-turn prefix appended after the generation prompt to pin output format. */
|
|
34
|
-
prefill?: string;
|
|
35
|
-
/** Optional literal stop string; generation halts once it appears in the decoded tail. */
|
|
36
|
-
stop?: string;
|
|
37
33
|
} | {
|
|
38
34
|
type: "download";
|
|
39
35
|
id: string;
|
|
40
36
|
modelKey: TinyLocalModelKey;
|
|
41
|
-
} | {
|
|
42
|
-
type: "close";
|
|
43
37
|
};
|
|
44
38
|
export type TinyTitleWorkerOutbound = {
|
|
45
39
|
type: "pong";
|
|
@@ -68,11 +62,16 @@ export type TinyTitleWorkerOutbound = {
|
|
|
68
62
|
level: "debug" | "warn" | "error";
|
|
69
63
|
msg: string;
|
|
70
64
|
meta?: Record<string, unknown>;
|
|
71
|
-
} | {
|
|
72
|
-
type: "closed";
|
|
73
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* Wire transport between the parent (`TinyTitleClient`) and the tiny-model
|
|
68
|
+
* subprocess. The parent owns the subprocess lifecycle (graceful work, hard
|
|
69
|
+
* kill on shutdown); the protocol therefore carries no explicit close
|
|
70
|
+
* handshake — once the parent decides to terminate, it signals the OS to
|
|
71
|
+
* reap the child so `onnxruntime-node`'s NAPI finalizer never runs in any
|
|
72
|
+
* shared address space. See `title-client.ts` for the spawn/kill glue.
|
|
73
|
+
*/
|
|
74
74
|
export interface TinyTitleTransport {
|
|
75
75
|
send(message: TinyTitleWorkerOutbound): void;
|
|
76
76
|
onMessage(handler: (message: TinyTitleWorkerInbound) => void): () => void;
|
|
77
|
-
close(): void;
|
|
78
77
|
}
|
|
@@ -93,7 +93,7 @@ export declare class FindTool implements AgentTool<typeof findSchema, FindToolDe
|
|
|
93
93
|
execute(_toolCallId: string, params: z.infer<typeof findSchema>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<FindToolDetails>, _context?: AgentToolContext): Promise<AgentToolResult<FindToolDetails>>;
|
|
94
94
|
}
|
|
95
95
|
interface FindRenderArgs {
|
|
96
|
-
paths?: string[];
|
|
96
|
+
paths?: string | string[];
|
|
97
97
|
limit?: number;
|
|
98
98
|
}
|
|
99
99
|
export declare const findToolRenderer: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LocalProtocolOptions } from "../internal-urls";
|
|
1
2
|
export declare function expandTilde(filePath: string, home?: string): string;
|
|
2
3
|
export declare function expandPath(filePath: string): string;
|
|
3
4
|
/**
|
|
@@ -143,6 +144,12 @@ export interface ToolScopeOptions {
|
|
|
143
144
|
surfaceExactFilePaths?: boolean;
|
|
144
145
|
/** Extra hint appended to "Path not found" when stat fails and the user supplied multiple paths. */
|
|
145
146
|
multipathStatHint?: string;
|
|
147
|
+
/** Calling session's settings — forwarded to the internal-URL router so caller-aware handlers (issue://, pr://) honor it. */
|
|
148
|
+
settings?: unknown;
|
|
149
|
+
/** Caller's abort signal — forwarded to the internal-URL router. */
|
|
150
|
+
signal?: AbortSignal;
|
|
151
|
+
/** Calling session's `local://` root mapping — pins resolutions to the calling session. */
|
|
152
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
146
153
|
}
|
|
147
154
|
export interface ToolScopeResolution {
|
|
148
155
|
searchPath: string;
|
|
@@ -20,14 +20,14 @@ export interface OutputBlockOptions {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function borderShimmerTick(): number;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* from the wall clock against a fixed
|
|
28
|
-
*
|
|
23
|
+
* Column of the travelling segment's center on the bottom edge for a box of
|
|
24
|
+
* inner width `W` at time `now`. The segment bounces left → right → left across
|
|
25
|
+
* the bottom border: a triangle wave over one full there-and-back cycle, eased
|
|
26
|
+
* per leg so it slows as it nears each wall before reversing. Position is
|
|
27
|
+
* derived from the wall clock against a fixed cycle, so a resize shifts the
|
|
28
|
+
* center proportionally — no reset.
|
|
29
29
|
*/
|
|
30
|
-
export declare function
|
|
30
|
+
export declare function borderSegmentHeadCol(W: number, now: number): number;
|
|
31
31
|
export declare function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[];
|
|
32
32
|
/**
|
|
33
33
|
* Cached wrapper around `renderOutputBlock`.
|
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.7.
|
|
4
|
+
"version": "15.7.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.7.
|
|
51
|
-
"@oh-my-pi/omp-stats": "15.7.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "15.7.
|
|
53
|
-
"@oh-my-pi/pi-ai": "15.7.
|
|
54
|
-
"@oh-my-pi/pi-mnemopi": "15.7.
|
|
55
|
-
"@oh-my-pi/pi-natives": "15.7.
|
|
56
|
-
"@oh-my-pi/pi-tui": "15.7.
|
|
57
|
-
"@oh-my-pi/pi-utils": "15.7.
|
|
50
|
+
"@oh-my-pi/hashline": "15.7.5",
|
|
51
|
+
"@oh-my-pi/omp-stats": "15.7.5",
|
|
52
|
+
"@oh-my-pi/pi-agent-core": "15.7.5",
|
|
53
|
+
"@oh-my-pi/pi-ai": "15.7.5",
|
|
54
|
+
"@oh-my-pi/pi-mnemopi": "15.7.5",
|
|
55
|
+
"@oh-my-pi/pi-natives": "15.7.5",
|
|
56
|
+
"@oh-my-pi/pi-tui": "15.7.5",
|
|
57
|
+
"@oh-my-pi/pi-utils": "15.7.5",
|
|
58
58
|
"@puppeteer/browsers": "^3.0.4",
|
|
59
59
|
"@types/turndown": "5.0.6",
|
|
60
60
|
"@xterm/headless": "^6.0.0",
|
package/scripts/build-binary.ts
CHANGED
|
@@ -56,7 +56,6 @@ async function main(): Promise<void> {
|
|
|
56
56
|
"../stats/src/sync-worker.ts",
|
|
57
57
|
"./src/tools/browser/tab-worker-entry.ts",
|
|
58
58
|
"./src/eval/js/worker-entry.ts",
|
|
59
|
-
"./src/tiny/worker.ts",
|
|
60
59
|
// Legacy pi-* extension compat entrypoints served by
|
|
61
60
|
// `legacy-pi-compat.ts`. These are reached via computed bunfs paths
|
|
62
61
|
// (which `--compile`'s static analyzer cannot trace), so each must be
|
package/src/cli.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
// Strip macOS malloc-stack-logging vars in the parent entrypoint, before any
|
|
3
|
+
// subprocess/worker spawn. libmalloc reads MallocStackLogging /
|
|
4
|
+
// MallocStackLoggingNoCompact during malloc bootstrap (pre-main) in every child
|
|
5
|
+
// and warns when they're present but set to "off"; a child cannot suppress its
|
|
6
|
+
// own warning, so the only fix is to keep them out of the inherited env here.
|
|
7
|
+
// (They must be unset, not set — presence is the trigger.)
|
|
8
|
+
try {
|
|
9
|
+
delete process.env.MallocStackLogging;
|
|
10
|
+
delete process.env.MallocStackLoggingNoCompact;
|
|
11
|
+
} catch {}
|
|
12
|
+
|
|
2
13
|
/**
|
|
3
14
|
* CLI entry point — registers all commands explicitly and delegates to the
|
|
4
15
|
* lightweight CLI runner from pi-utils.
|
|
@@ -43,12 +54,60 @@ async function runSmokeTest(): Promise<void> {
|
|
|
43
54
|
process.stdout.write("smoke-test: ok\n");
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Hidden subcommand that boots the tiny-model worker inside this process
|
|
59
|
+
* over the parent's IPC channel. The agent's main process spawns the same
|
|
60
|
+
* binary with this flag so `onnxruntime-node` (loaded transitively by
|
|
61
|
+
* `@huggingface/transformers`) lives in a child address space. The parent
|
|
62
|
+
* `SIGKILL`s the child on shutdown so the NAPI finalizer never runs in
|
|
63
|
+
* either process — that finalizer segfaults Bun on Windows (issue #1606).
|
|
64
|
+
*/
|
|
65
|
+
async function runTinyWorker(): Promise<void> {
|
|
66
|
+
const { startTinyTitleWorker } = await import("./tiny/worker");
|
|
67
|
+
const { promise: shuttingDown, resolve: shutdown } = Promise.withResolvers<void>();
|
|
68
|
+
const send = (message: unknown): void => {
|
|
69
|
+
// `process.send` only exists when spawned with an IPC channel; the
|
|
70
|
+
// parent always spawns us that way. If it's missing, the parent
|
|
71
|
+
// vanished and there's no one to talk to.
|
|
72
|
+
const sender = (process as NodeJS.Process & { send?: (m: unknown) => boolean }).send;
|
|
73
|
+
if (!sender) {
|
|
74
|
+
shutdown();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
sender.call(process, message);
|
|
79
|
+
} catch {
|
|
80
|
+
shutdown();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
startTinyTitleWorker({
|
|
84
|
+
send,
|
|
85
|
+
onMessage(handler) {
|
|
86
|
+
const wrap = (data: unknown): void => handler(data as never);
|
|
87
|
+
process.on("message", wrap);
|
|
88
|
+
return () => {
|
|
89
|
+
process.off("message", wrap);
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
// Parent went away (crashed, SIGKILL, etc.) — commit suicide so we don't
|
|
94
|
+
// linger as an orphan. SIGKILL via `process.kill` keeps us symmetrical
|
|
95
|
+
// with the parent's hard-kill on shutdown: skip every JS/native finalizer.
|
|
96
|
+
process.on("disconnect", () => shutdown());
|
|
97
|
+
await shuttingDown;
|
|
98
|
+
process.kill(process.pid, "SIGKILL");
|
|
99
|
+
}
|
|
100
|
+
|
|
46
101
|
/** Run the CLI with the given argv (no `process.argv` prefix). */
|
|
47
102
|
export async function runCli(argv: string[]): Promise<void> {
|
|
48
103
|
if (argv[0] === "--smoke-test") {
|
|
49
104
|
await runSmokeTest();
|
|
50
105
|
return;
|
|
51
106
|
}
|
|
107
|
+
if (argv[0] === "--tiny-worker") {
|
|
108
|
+
await runTinyWorker();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
52
111
|
// --help and --version are handled by run() directly, don't rewrite those.
|
|
53
112
|
// Everything else that isn't a known subcommand routes to "launch".
|
|
54
113
|
const first = argv[0];
|