@oh-my-pi/pi-coding-agent 15.7.3 → 15.7.6

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 (119) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/dist/types/config/settings-schema.d.ts +12 -22
  3. package/dist/types/eval/__tests__/heartbeat.test.d.ts +1 -0
  4. package/dist/types/eval/heartbeat.d.ts +45 -0
  5. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  6. package/dist/types/extensibility/extensions/loader.d.ts +2 -2
  7. package/dist/types/extensibility/extensions/runner.d.ts +2 -1
  8. package/dist/types/extensibility/extensions/types.d.ts +24 -5
  9. package/dist/types/extensibility/shared-events.d.ts +2 -2
  10. package/dist/types/internal-urls/local-protocol.d.ts +19 -9
  11. package/dist/types/internal-urls/types.d.ts +14 -0
  12. package/dist/types/lsp/client.d.ts +3 -0
  13. package/dist/types/lsp/diagnostics-ledger.d.ts +10 -0
  14. package/dist/types/lsp/index.d.ts +2 -0
  15. package/dist/types/lsp/utils.d.ts +4 -0
  16. package/dist/types/mcp/manager.d.ts +14 -5
  17. package/dist/types/modes/acp/acp-agent.d.ts +1 -1
  18. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  19. package/dist/types/modes/components/custom-editor.d.ts +0 -1
  20. package/dist/types/modes/components/hook-selector.d.ts +7 -1
  21. package/dist/types/modes/controllers/command-controller.d.ts +2 -3
  22. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -2
  23. package/dist/types/modes/interactive-mode.d.ts +2 -2
  24. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
  25. package/dist/types/modes/theme/theme.d.ts +1 -1
  26. package/dist/types/modes/types.d.ts +2 -2
  27. package/dist/types/session/agent-session.d.ts +10 -7
  28. package/dist/types/session/shake-types.d.ts +3 -3
  29. package/dist/types/task/repair-args.d.ts +52 -0
  30. package/dist/types/tiny/models.d.ts +0 -14
  31. package/dist/types/tiny/title-client.d.ts +28 -2
  32. package/dist/types/tiny/title-protocol.d.ts +8 -9
  33. package/dist/types/tools/ask.d.ts +8 -6
  34. package/dist/types/tools/eval-backends.d.ts +12 -0
  35. package/dist/types/tools/eval-render.d.ts +52 -0
  36. package/dist/types/tools/eval.d.ts +2 -35
  37. package/dist/types/tools/find.d.ts +1 -1
  38. package/dist/types/tools/index.d.ts +4 -11
  39. package/dist/types/tools/path-utils.d.ts +7 -0
  40. package/dist/types/tui/output-block.d.ts +11 -10
  41. package/examples/extensions/README.md +1 -0
  42. package/examples/extensions/thinking-note.ts +13 -0
  43. package/package.json +9 -9
  44. package/scripts/build-binary.ts +0 -1
  45. package/src/cli.ts +59 -0
  46. package/src/config/model-registry.ts +33 -4
  47. package/src/config/settings-schema.ts +13 -24
  48. package/src/config/settings.ts +10 -0
  49. package/src/discovery/claude.ts +41 -22
  50. package/src/edit/index.ts +23 -3
  51. package/src/eval/__tests__/agent-bridge.test.ts +90 -0
  52. package/src/eval/__tests__/heartbeat.test.ts +84 -0
  53. package/src/eval/__tests__/llm-bridge.test.ts +30 -0
  54. package/src/eval/agent-bridge.ts +44 -38
  55. package/src/eval/heartbeat.ts +74 -0
  56. package/src/eval/js/executor.ts +13 -9
  57. package/src/eval/llm-bridge.ts +20 -14
  58. package/src/eval/py/executor.ts +14 -18
  59. package/src/exec/bash-executor.ts +31 -5
  60. package/src/extensibility/custom-tools/types.ts +2 -2
  61. package/src/extensibility/extensions/loader.ts +16 -18
  62. package/src/extensibility/extensions/runner.ts +22 -17
  63. package/src/extensibility/extensions/types.ts +39 -5
  64. package/src/extensibility/shared-events.ts +2 -2
  65. package/src/internal-urls/docs-index.generated.ts +5 -5
  66. package/src/internal-urls/local-protocol.ts +23 -11
  67. package/src/internal-urls/types.ts +15 -0
  68. package/src/lsp/client.ts +28 -5
  69. package/src/lsp/diagnostics-ledger.ts +51 -0
  70. package/src/lsp/index.ts +9 -22
  71. package/src/lsp/utils.ts +21 -0
  72. package/src/mcp/manager.ts +87 -4
  73. package/src/modes/acp/acp-agent.ts +8 -4
  74. package/src/modes/components/assistant-message.ts +28 -1
  75. package/src/modes/components/custom-editor.ts +9 -7
  76. package/src/modes/components/hook-selector.ts +159 -32
  77. package/src/modes/components/tool-execution.ts +20 -4
  78. package/src/modes/controllers/command-controller.ts +7 -39
  79. package/src/modes/controllers/event-controller.ts +38 -28
  80. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  81. package/src/modes/controllers/input-controller.ts +0 -15
  82. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  83. package/src/modes/interactive-mode.ts +2 -1
  84. package/src/modes/rpc/rpc-mode.ts +17 -6
  85. package/src/modes/theme/theme-schema.json +30 -0
  86. package/src/modes/theme/theme.ts +39 -2
  87. package/src/modes/types.ts +2 -1
  88. package/src/modes/utils/ui-helpers.ts +5 -2
  89. package/src/prompts/system/project-prompt.md +3 -2
  90. package/src/prompts/system/subagent-system-prompt.md +12 -8
  91. package/src/prompts/system/system-prompt.md +8 -6
  92. package/src/prompts/tools/ask.md +2 -1
  93. package/src/prompts/tools/eval.md +1 -1
  94. package/src/session/agent-session.ts +75 -103
  95. package/src/session/shake-types.ts +4 -5
  96. package/src/slash-commands/builtin-registry.ts +2 -4
  97. package/src/task/executor.ts +14 -4
  98. package/src/task/index.ts +3 -2
  99. package/src/task/repair-args.ts +117 -0
  100. package/src/tiny/models.ts +0 -28
  101. package/src/tiny/title-client.ts +133 -43
  102. package/src/tiny/title-protocol.ts +11 -16
  103. package/src/tiny/worker.ts +6 -61
  104. package/src/tools/ask.ts +74 -32
  105. package/src/tools/ast-edit.ts +3 -0
  106. package/src/tools/ast-grep.ts +3 -0
  107. package/src/tools/eval-backends.ts +38 -0
  108. package/src/tools/eval-render.ts +750 -0
  109. package/src/tools/eval.ts +27 -754
  110. package/src/tools/find.ts +20 -6
  111. package/src/tools/gh.ts +1 -0
  112. package/src/tools/index.ts +7 -37
  113. package/src/tools/path-utils.ts +13 -2
  114. package/src/tools/read.ts +1 -0
  115. package/src/tools/renderers.ts +1 -1
  116. package/src/tools/search.ts +12 -1
  117. package/src/tools/write.ts +9 -1
  118. package/src/tui/output-block.ts +42 -79
  119. package/src/utils/git.ts +9 -3
@@ -17,7 +17,7 @@ export declare class Theme {
17
17
  #private;
18
18
  private readonly mode;
19
19
  private readonly symbolPreset;
20
- constructor(fgColors: Record<ThemeColor, string | number>, bgColors: Record<ThemeBg, string | number>, mode: ColorMode, symbolPreset: SymbolPreset, symbolOverrides: Partial<Record<SymbolKey, string>>);
20
+ constructor(fgColors: Record<ThemeColor, string | number>, bgColors: Record<ThemeBg, string | number>, mode: ColorMode, symbolPreset: SymbolPreset, symbolOverrides: Partial<Record<SymbolKey, string>>, spinnerFramesOverrides?: Partial<Record<SpinnerType, string[]>>);
21
21
  fg(color: ThemeColor, text: string): string;
22
22
  bg(color: ThemeBg, text: string): string;
23
23
  bold(text: string): string;
@@ -4,7 +4,7 @@ import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-m
4
4
  import type { Component, Container, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
5
5
  import type { KeybindingsManager } from "../config/keybindings";
6
6
  import type { Settings } from "../config/settings";
7
- import type { ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetContent, ExtensionWidgetOptions } from "../extensibility/extensions";
7
+ import type { ExtensionUIContext, ExtensionUIDialogOptions, ExtensionUISelectItem, ExtensionWidgetContent, ExtensionWidgetOptions } from "../extensibility/extensions";
8
8
  import type { CompactOptions } from "../extensibility/extensions/types";
9
9
  import type { MCPManager } from "../mcp";
10
10
  import type { PlanApprovalDetails } from "../plan-mode/approved-plan";
@@ -264,7 +264,7 @@ export interface InteractiveModeContext {
264
264
  emitCustomToolSessionEvent(reason: "start" | "switch" | "branch" | "tree" | "shutdown", previousSessionFile?: string): Promise<void>;
265
265
  setHookWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void;
266
266
  setHookStatus(key: string, text: string | undefined): void;
267
- showHookSelector(title: string, options: string[], dialogOptions?: ExtensionUIDialogOptions): Promise<string | undefined>;
267
+ showHookSelector(title: string, options: ExtensionUISelectItem[], dialogOptions?: ExtensionUIDialogOptions): Promise<string | undefined>;
268
268
  hideHookSelector(): void;
269
269
  showHookInput(title: string, placeholder?: string): Promise<string | undefined>;
270
270
  hideHookInput(): 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;
@@ -438,8 +438,15 @@ export declare class AgentSession {
438
438
  /**
439
439
  * Replace MCP tools in the registry and recompute the visible MCP tool set immediately.
440
440
  * This allows /mcp add/remove/reauth to take effect without restarting the session.
441
+ *
442
+ * @param mcpTools The new MCP tools to register.
443
+ * @param options.activateAll When true, force-activates every newly registered MCP tool
444
+ * regardless of prior selection state. Used when an ACP client provisions MCP servers
445
+ * for a session where MCP discovery is disabled.
441
446
  */
442
- refreshMCPTools(mcpTools: CustomTool[]): Promise<void>;
447
+ refreshMCPTools(mcpTools: CustomTool[], options?: {
448
+ activateAll?: boolean;
449
+ }): Promise<void>;
443
450
  /**
444
451
  * Replace RPC host-owned tools and refresh the active tool set before the next model call.
445
452
  */
@@ -737,10 +744,6 @@ export declare class AgentSession {
737
744
  * - `images` delegates to {@link dropImages}.
738
745
  * - `elide` replaces whole tool-call results and large fenced/XML blocks
739
746
  * 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
747
  *
745
748
  * Mutates the branch in place, persists via `rewriteEntries`, replays the
746
749
  * 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
  }
@@ -26,6 +26,7 @@ declare const askSchema: z.ZodObject<{
26
26
  question: z.ZodString;
27
27
  options: z.ZodArray<z.ZodObject<{
28
28
  label: z.ZodString;
29
+ description: z.ZodOptional<z.ZodString>;
29
30
  }, z.core.$strip>>;
30
31
  multi: z.ZodOptional<z.ZodBoolean>;
31
32
  recommended: z.ZodOptional<z.ZodNumber>;
@@ -71,6 +72,7 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
71
72
  question: z.ZodString;
72
73
  options: z.ZodArray<z.ZodObject<{
73
74
  label: z.ZodString;
75
+ description: z.ZodOptional<z.ZodString>;
74
76
  }, z.core.$strip>>;
75
77
  multi: z.ZodOptional<z.ZodBoolean>;
76
78
  recommended: z.ZodOptional<z.ZodNumber>;
@@ -82,18 +84,18 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
82
84
  static createIf(session: ToolSession): AskTool | null;
83
85
  execute(_toolCallId: string, params: AskParams, signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<AskToolDetails>, context?: AgentToolContext): Promise<AgentToolResult<AskToolDetails>>;
84
86
  }
87
+ interface AskRenderOption {
88
+ label: string;
89
+ description?: string;
90
+ }
85
91
  interface AskRenderArgs {
86
92
  question?: string;
87
- options?: Array<{
88
- label: string;
89
- }>;
93
+ options?: AskRenderOption[];
90
94
  multi?: boolean;
91
95
  questions?: Array<{
92
96
  id: string;
93
97
  question: string;
94
- options: Array<{
95
- label: string;
96
- }>;
98
+ options: AskRenderOption[];
97
99
  multi?: boolean;
98
100
  }>;
99
101
  }
@@ -0,0 +1,12 @@
1
+ import type { ToolSession } from ".";
2
+ export interface EvalBackendsAllowance {
3
+ python: boolean;
4
+ js: boolean;
5
+ }
6
+ /** Read per-backend allowance from settings (defaults true). */
7
+ export declare function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAllowance;
8
+ /**
9
+ * Materialize the active eval backend allowance: PI_PY / PI_JS env flags
10
+ * override the per-key settings; otherwise settings (defaults true) win.
11
+ */
12
+ export declare function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * TUI rendering for the eval tool.
3
+ *
4
+ * Split out from `eval.ts` so the renderer can be imported by `renderers.ts`
5
+ * without dragging the eval *runtime* (JS/Python backends -> agent bridge ->
6
+ * task executor -> sdk -> extension loader -> root barrel) into the renderer
7
+ * module graph. That transitive chain re-enters `renderers.ts` while `eval.ts`
8
+ * is still initializing, which previously crashed module load with a TDZ
9
+ * `Cannot access 'evalToolRenderer' before initialization`.
10
+ */
11
+ import type { Component } from "@oh-my-pi/pi-tui";
12
+ import type { EvalStatusEvent, EvalToolDetails } from "../eval/types";
13
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
+ import { type Theme } from "../modes/theme/theme";
15
+ export declare const EVAL_DEFAULT_PREVIEW_LINES = 10;
16
+ interface EvalRenderCellArg {
17
+ language?: string;
18
+ code?: string;
19
+ title?: string;
20
+ }
21
+ interface EvalRenderArgs {
22
+ cells?: EvalRenderCellArg[];
23
+ __partialJson?: string;
24
+ }
25
+ interface EvalRenderContext {
26
+ output?: string;
27
+ expanded?: boolean;
28
+ previewLines?: number;
29
+ timeout?: number;
30
+ }
31
+ /**
32
+ * Append or replace a status event. `agent` events are progress snapshots keyed
33
+ * by `id`, so they coalesce in place (preserving first-seen order); every other
34
+ * op is a discrete action and simply appends. Keeps the persisted event list
35
+ * bounded even when a subagent emits hundreds of throttled progress ticks.
36
+ */
37
+ export declare function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void;
38
+ export declare const evalToolRenderer: {
39
+ renderCall(args: EvalRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component;
40
+ renderResult(result: {
41
+ content: Array<{
42
+ type: string;
43
+ text?: string;
44
+ }>;
45
+ details?: EvalToolDetails;
46
+ }, options: RenderResultOptions & {
47
+ renderContext?: EvalRenderContext;
48
+ }, uiTheme: Theme, _args?: EvalRenderArgs): Component;
49
+ mergeCallAndResult: boolean;
50
+ inline: boolean;
51
+ };
52
+ export {};
@@ -1,11 +1,8 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import type { Component } from "@oh-my-pi/pi-tui";
3
2
  import * as z from "zod/v4";
4
3
  import type { EvalToolDetails } from "../eval/types";
5
- import type { RenderResultOptions } from "../extensibility/custom-tools/types";
6
- import { type Theme } from "../modes/theme/theme";
7
- import { type ToolSession } from ".";
8
- export declare const EVAL_DEFAULT_PREVIEW_LINES = 10;
4
+ import type { ToolSession } from ".";
5
+ export { EVAL_DEFAULT_PREVIEW_LINES, evalToolRenderer } from "./eval-render";
9
6
  /**
10
7
  * Per-cell input. Each cell runs in order; state persists within a language
11
8
  * across cells and across tool calls.
@@ -78,33 +75,3 @@ export declare class EvalTool implements AgentTool<typeof evalSchema> {
78
75
  constructor(session: ToolSession | null, options?: EvalToolOptions);
79
76
  execute(_toolCallId: string, params: z.infer<typeof evalSchema>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, _ctx?: AgentToolContext): Promise<AgentToolResult<EvalToolDetails | undefined>>;
80
77
  }
81
- interface EvalRenderCellArg {
82
- language?: string;
83
- code?: string;
84
- title?: string;
85
- }
86
- interface EvalRenderArgs {
87
- cells?: EvalRenderCellArg[];
88
- __partialJson?: string;
89
- }
90
- interface EvalRenderContext {
91
- output?: string;
92
- expanded?: boolean;
93
- previewLines?: number;
94
- timeout?: number;
95
- }
96
- export declare const evalToolRenderer: {
97
- renderCall(args: EvalRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component;
98
- renderResult(result: {
99
- content: Array<{
100
- type: string;
101
- text?: string;
102
- }>;
103
- details?: EvalToolDetails;
104
- }, options: RenderResultOptions & {
105
- renderContext?: EvalRenderContext;
106
- }, uiTheme: Theme, _args?: EvalRenderArgs): Component;
107
- mergeCallAndResult: boolean;
108
- inline: boolean;
109
- };
110
- export {};
@@ -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: {
@@ -37,6 +37,7 @@ export * from "./browser";
37
37
  export * from "./checkpoint";
38
38
  export * from "./debug";
39
39
  export * from "./eval";
40
+ export * from "./eval-backends";
40
41
  export * from "./find";
41
42
  export * from "./gh";
42
43
  export * from "./image-gen";
@@ -218,6 +219,9 @@ export interface ToolSession {
218
219
  * to splice the recorded region with replacement content. Lazily initialized
219
220
  * by `getConflictHistory`. */
220
221
  conflictHistory?: import("./conflict-detect").ConflictHistory;
222
+ /** Per-session ledger of post-edit LSP diagnostics already surfaced to the
223
+ * model for each file. Lazily initialized by `getDiagnosticsLedger`. */
224
+ diagnosticsLedger?: import("../lsp/diagnostics-ledger").DiagnosticsLedger;
221
225
  /** Queue a hidden message to be injected at the next agent turn. */
222
226
  queueDeferredMessage?(message: CustomMessage): void;
223
227
  /** Get the active OpenTelemetry config so subagent dispatch can forward
@@ -241,17 +245,6 @@ export declare function computeEssentialBuiltinNames(settings: Settings): string
241
245
  export declare const BUILTIN_TOOLS: Record<string, ToolFactory>;
242
246
  export declare const HIDDEN_TOOLS: Record<string, ToolFactory>;
243
247
  export type ToolName = keyof typeof BUILTIN_TOOLS;
244
- export interface EvalBackendsAllowance {
245
- python: boolean;
246
- js: boolean;
247
- }
248
- /** Read per-backend allowance from settings (defaults true). */
249
- export declare function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAllowance;
250
- /**
251
- * Materialize the active eval backend allowance: PI_PY / PI_JS env flags
252
- * override the per-key settings; otherwise settings (defaults true) win.
253
- */
254
- export declare function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance;
255
248
  /**
256
249
  * Create tools from BUILTIN_TOOLS registry.
257
250
  */
@@ -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;
@@ -14,20 +14,21 @@ export interface OutputBlockOptions {
14
14
  animate?: boolean;
15
15
  }
16
16
  /**
17
- * Monotonic frame counter for animated borders. Quantized coarse enough to
18
- * coalesce multiple render passes inside one frame, fine enough to advance on
19
- * every spinner interval so cached blocks re-render while the segment travels.
17
+ * Monotonic frame counter for animated borders, quantized to the TUI's ~16ms
18
+ * render cap so the cache key advances once per ~60fps frame fine enough for a
19
+ * smooth segment sweep, coarse enough to coalesce multiple render passes that
20
+ * land inside the same frame.
20
21
  */
21
22
  export declare function borderShimmerTick(): number;
22
23
  /**
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.
24
+ * Column of the travelling segment's center on the bottom edge for a box of
25
+ * inner width `W` at time `now`. The segment bounces left right left across
26
+ * the bottom border: a triangle wave over one full there-and-back cycle, eased
27
+ * per leg so it slows as it nears each wall before reversing. Position is
28
+ * derived from the wall clock against a fixed cycle, so a resize shifts the
29
+ * center proportionally — no reset.
29
30
  */
30
- export declare function borderSegmentHead(W: number, H: number, now: number): number;
31
+ export declare function borderSegmentHeadCol(W: number, now: number): number;
31
32
  export declare function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[];
32
33
  /**
33
34
  * Cached wrapper around `renderOutputBlock`.
@@ -41,6 +41,7 @@ cp permission-gate.ts ~/.omp/agent/extensions/
41
41
  | `handoff.ts` | Transfer context to a new focused session via `/handoff <goal>` |
42
42
  | `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
43
43
  | `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
44
+ | `thinking-note.ts` | Adds display-only supplemental UI below assistant thinking blocks |
44
45
  | `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
45
46
 
46
47
  ### Git Integration
@@ -0,0 +1,13 @@
1
+ import type { ExtensionFactory } from "@oh-my-pi/pi-coding-agent";
2
+ import { Container, Text } from "@oh-my-pi/pi-tui";
3
+
4
+ const extension: ExtensionFactory = pi => {
5
+ pi.setLabel("Thinking note");
6
+ pi.registerAssistantThinkingRenderer((context, theme) => {
7
+ const container = new Container();
8
+ container.addChild(new Text(theme.fg("dim", `thinking chars: ${context.text.length}`), 1, 0));
9
+ return container;
10
+ });
11
+ };
12
+
13
+ export default extension;
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.6",
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.6",
51
+ "@oh-my-pi/omp-stats": "15.7.6",
52
+ "@oh-my-pi/pi-agent-core": "15.7.6",
53
+ "@oh-my-pi/pi-ai": "15.7.6",
54
+ "@oh-my-pi/pi-mnemopi": "15.7.6",
55
+ "@oh-my-pi/pi-natives": "15.7.6",
56
+ "@oh-my-pi/pi-tui": "15.7.6",
57
+ "@oh-my-pi/pi-utils": "15.7.6",
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];