@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/config/settings-schema.d.ts +3 -22
  3. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  4. package/dist/types/extensibility/shared-events.d.ts +2 -2
  5. package/dist/types/internal-urls/local-protocol.d.ts +19 -9
  6. package/dist/types/internal-urls/types.d.ts +14 -0
  7. package/dist/types/lsp/client.d.ts +3 -0
  8. package/dist/types/mcp/manager.d.ts +14 -5
  9. package/dist/types/modes/controllers/command-controller.d.ts +2 -3
  10. package/dist/types/session/agent-session.d.ts +2 -6
  11. package/dist/types/session/shake-types.d.ts +3 -3
  12. package/dist/types/task/repair-args.d.ts +52 -0
  13. package/dist/types/tiny/models.d.ts +0 -14
  14. package/dist/types/tiny/title-client.d.ts +28 -2
  15. package/dist/types/tiny/title-protocol.d.ts +8 -9
  16. package/dist/types/tools/find.d.ts +1 -1
  17. package/dist/types/tools/path-utils.d.ts +7 -0
  18. package/dist/types/tui/output-block.d.ts +7 -7
  19. package/package.json +9 -9
  20. package/scripts/build-binary.ts +0 -1
  21. package/src/cli.ts +59 -0
  22. package/src/config/settings-schema.ts +3 -24
  23. package/src/config/settings.ts +10 -0
  24. package/src/extensibility/custom-tools/types.ts +2 -2
  25. package/src/extensibility/shared-events.ts +2 -2
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/internal-urls/local-protocol.ts +23 -11
  28. package/src/internal-urls/types.ts +15 -0
  29. package/src/lsp/client.ts +28 -5
  30. package/src/mcp/manager.ts +87 -4
  31. package/src/modes/controllers/command-controller.ts +7 -39
  32. package/src/modes/controllers/event-controller.ts +33 -26
  33. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  34. package/src/prompts/system/project-prompt.md +3 -2
  35. package/src/prompts/system/subagent-system-prompt.md +12 -8
  36. package/src/prompts/system/system-prompt.md +8 -6
  37. package/src/session/agent-session.ts +11 -93
  38. package/src/session/shake-types.ts +4 -5
  39. package/src/slash-commands/builtin-registry.ts +2 -4
  40. package/src/task/executor.ts +14 -4
  41. package/src/task/index.ts +3 -2
  42. package/src/task/repair-args.ts +117 -0
  43. package/src/tiny/models.ts +0 -28
  44. package/src/tiny/title-client.ts +133 -43
  45. package/src/tiny/title-protocol.ts +11 -16
  46. package/src/tiny/worker.ts +6 -61
  47. package/src/tools/ast-edit.ts +3 -0
  48. package/src/tools/ast-grep.ts +3 -0
  49. package/src/tools/find.ts +20 -6
  50. package/src/tools/gh.ts +1 -0
  51. package/src/tools/path-utils.ts +13 -2
  52. package/src/tools/read.ts +1 -0
  53. package/src/tools/search.ts +12 -1
  54. package/src/tui/output-block.ts +37 -75
  55. 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", "shake-summary", "off"];
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), shake with local-model summaries, or disable auto maintenance (off)";
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" | "shake-summary" | "off";
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" | "shake-summary";
89
+ action: "context-full" | "handoff" | "shake";
90
90
  } | {
91
91
  reason: "auto_compaction_end";
92
- action: "context-full" | "handoff" | "shake" | "shake-summary";
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" | "shake-summary";
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" | "shake-summary";
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. Explicit override installed via {@link setOverride} (used by subagents
32
- * that share their parent's root and by SDK consumers with a custom
33
- * artifacts/session id mapping).
34
- * 2. The main session in `AgentRegistry.global()`. Its `SessionManager`
35
- * supplies both `getArtifactsDir` and `getSessionId`.
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
- * Concurrent calls for the same server share one reconnection attempt.
136
- * Returns the new connection, or null if reconnection failed.
137
- */
138
- reconnectServer(name: string): Promise<MCPServerConnection | null>;
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`/`images` are instant structural drops;
35
- * `summary` runs the local on-device compressor behind a cancelable loader
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" | "shake-summary";
57
+ action: "context-full" | "handoff" | "shake";
58
58
  } | {
59
59
  type: "auto_compaction_end";
60
- action: "context-full" | "handoff" | "shake" | "shake-summary";
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" | "summary" | "images";
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/compressed. */
12
+ /** Whole tool-call results dropped. */
13
13
  toolResultsDropped: number;
14
- /** Large fenced/XML blocks dropped/compressed. */
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 { TinyTitleProgressEvent } from "./title-protocol";
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
- export declare function createTinyTitleWorker(): Worker;
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
- * Perimeter index of the moving segment's head for a box of inner width `W` and
24
- * height `H` at time `now`. The lap is split across the four edges in proportion
25
- * to their length (so the average speed is uniform) and each edge is eased, for a
26
- * deliberate, non-linear glide that slows at every corner. Position is derived
27
- * from the wall clock against a fixed lap duration, so a perimeter change (new
28
- * row / resize) shifts the head by at most a cell or two — no reset.
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 borderSegmentHead(W: number, H: number, now: number): number;
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.3",
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.3",
51
- "@oh-my-pi/omp-stats": "15.7.3",
52
- "@oh-my-pi/pi-agent-core": "15.7.3",
53
- "@oh-my-pi/pi-ai": "15.7.3",
54
- "@oh-my-pi/pi-mnemopi": "15.7.3",
55
- "@oh-my-pi/pi-natives": "15.7.3",
56
- "@oh-my-pi/pi-tui": "15.7.3",
57
- "@oh-my-pi/pi-utils": "15.7.3",
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",
@@ -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];