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

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 (160) hide show
  1. package/CHANGELOG.md +75 -6
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/extension-flags.d.ts +36 -0
  4. package/dist/types/config/config-file.d.ts +4 -0
  5. package/dist/types/config/file-lock.d.ts +23 -0
  6. package/dist/types/config/keybindings.d.ts +2 -1
  7. package/dist/types/config/model-registry.d.ts +6 -0
  8. package/dist/types/config/settings-schema.d.ts +88 -65
  9. package/dist/types/edit/hashline/diff.d.ts +3 -3
  10. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
  11. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
  12. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
  13. package/dist/types/eval/agent-bridge.d.ts +25 -0
  14. package/dist/types/eval/backend.d.ts +17 -2
  15. package/dist/types/eval/budget-bridge.d.ts +29 -0
  16. package/dist/types/eval/idle-timeout.d.ts +28 -0
  17. package/dist/types/eval/js/executor.d.ts +8 -0
  18. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  19. package/dist/types/eval/py/executor.d.ts +13 -0
  20. package/dist/types/exec/bash-executor.d.ts +1 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  22. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  23. package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
  24. package/dist/types/extensibility/plugins/manager.d.ts +12 -1
  25. package/dist/types/extensibility/shared-events.d.ts +2 -2
  26. package/dist/types/memory-backend/index.d.ts +1 -1
  27. package/dist/types/memory-backend/resolve.d.ts +1 -1
  28. package/dist/types/memory-backend/types.d.ts +3 -3
  29. package/dist/types/mnemopi/backend.d.ts +4 -0
  30. package/dist/types/mnemopi/config.d.ts +29 -0
  31. package/dist/types/mnemopi/state.d.ts +72 -0
  32. package/dist/types/modes/components/custom-editor.d.ts +2 -2
  33. package/dist/types/modes/components/omfg-panel.d.ts +19 -0
  34. package/dist/types/modes/controllers/command-controller.d.ts +7 -0
  35. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  36. package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
  37. package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
  38. package/dist/types/modes/gradient-highlight.d.ts +5 -1
  39. package/dist/types/modes/interactive-mode.d.ts +7 -3
  40. package/dist/types/modes/magic-keywords.d.ts +14 -0
  41. package/dist/types/modes/markdown-prose.d.ts +27 -0
  42. package/dist/types/modes/orchestrate.d.ts +7 -2
  43. package/dist/types/modes/shared.d.ts +1 -1
  44. package/dist/types/modes/turn-budget.d.ts +18 -0
  45. package/dist/types/modes/types.d.ts +7 -3
  46. package/dist/types/modes/ultrathink.d.ts +7 -2
  47. package/dist/types/modes/workflow.d.ts +15 -0
  48. package/dist/types/sdk.d.ts +13 -3
  49. package/dist/types/session/agent-session.d.ts +40 -17
  50. package/dist/types/session/session-manager.d.ts +18 -0
  51. package/dist/types/session/session-storage.d.ts +6 -0
  52. package/dist/types/session/shake-types.d.ts +24 -0
  53. package/dist/types/task/executor.d.ts +2 -2
  54. package/dist/types/tiny/models.d.ts +15 -1
  55. package/dist/types/tiny/title-protocol.d.ts +4 -0
  56. package/dist/types/tools/index.d.ts +19 -3
  57. package/dist/types/tools/memory-edit.d.ts +1 -1
  58. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  59. package/package.json +10 -10
  60. package/src/autoresearch/tools/run-experiment.ts +45 -113
  61. package/src/cli/args.ts +39 -16
  62. package/src/cli/extension-flags.ts +48 -0
  63. package/src/cli/plugin-cli.ts +11 -2
  64. package/src/config/config-file.ts +98 -13
  65. package/src/config/file-lock.ts +60 -17
  66. package/src/config/keybindings.ts +78 -27
  67. package/src/config/model-registry.ts +7 -1
  68. package/src/config/settings-schema.ts +94 -67
  69. package/src/config/settings.ts +12 -0
  70. package/src/edit/hashline/diff.ts +81 -24
  71. package/src/edit/renderer.ts +16 -12
  72. package/src/eval/__tests__/agent-bridge.test.ts +433 -0
  73. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  74. package/src/eval/__tests__/idle-timeout.test.ts +66 -0
  75. package/src/eval/__tests__/shared-executors.test.ts +21 -0
  76. package/src/eval/agent-bridge.ts +295 -0
  77. package/src/eval/backend.ts +17 -2
  78. package/src/eval/budget-bridge.ts +48 -0
  79. package/src/eval/idle-timeout.ts +80 -0
  80. package/src/eval/js/executor.ts +35 -7
  81. package/src/eval/js/index.ts +2 -1
  82. package/src/eval/js/shared/prelude.txt +85 -1
  83. package/src/eval/js/tool-bridge.ts +9 -0
  84. package/src/eval/py/executor.ts +41 -14
  85. package/src/eval/py/index.ts +2 -1
  86. package/src/eval/py/prelude.py +132 -1
  87. package/src/exec/bash-executor.ts +2 -3
  88. package/src/extensibility/custom-tools/types.ts +2 -2
  89. package/src/extensibility/extensions/runner.ts +12 -2
  90. package/src/extensibility/plugins/git-url.ts +90 -4
  91. package/src/extensibility/plugins/manager.ts +103 -7
  92. package/src/extensibility/shared-events.ts +2 -2
  93. package/src/internal-urls/docs-index.generated.ts +88 -88
  94. package/src/main.ts +44 -55
  95. package/src/memory-backend/index.ts +1 -1
  96. package/src/memory-backend/resolve.ts +3 -3
  97. package/src/memory-backend/types.ts +3 -3
  98. package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
  99. package/src/{mnemosyne → mnemopi}/config.ts +36 -36
  100. package/src/{mnemosyne → mnemopi}/state.ts +67 -67
  101. package/src/modes/components/agent-dashboard.ts +6 -6
  102. package/src/modes/components/custom-editor.ts +4 -11
  103. package/src/modes/components/extensions/state-manager.ts +3 -4
  104. package/src/modes/components/footer.ts +8 -9
  105. package/src/modes/components/hook-selector.ts +86 -20
  106. package/src/modes/components/oauth-selector.ts +93 -21
  107. package/src/modes/components/omfg-panel.ts +141 -0
  108. package/src/modes/components/settings-defs.ts +2 -2
  109. package/src/modes/components/tips.txt +2 -1
  110. package/src/modes/components/tool-execution.ts +38 -19
  111. package/src/modes/components/tree-selector.ts +4 -3
  112. package/src/modes/components/user-message-selector.ts +94 -19
  113. package/src/modes/components/user-message.ts +8 -1
  114. package/src/modes/controllers/command-controller.ts +57 -0
  115. package/src/modes/controllers/event-controller.ts +60 -2
  116. package/src/modes/controllers/input-controller.ts +14 -11
  117. package/src/modes/controllers/omfg-controller.ts +283 -0
  118. package/src/modes/controllers/omfg-rule.ts +647 -0
  119. package/src/modes/controllers/selector-controller.ts +1 -0
  120. package/src/modes/gradient-highlight.ts +23 -6
  121. package/src/modes/interactive-mode.ts +41 -7
  122. package/src/modes/magic-keywords.ts +20 -0
  123. package/src/modes/markdown-prose.ts +247 -0
  124. package/src/modes/orchestrate.ts +17 -11
  125. package/src/modes/shared.ts +3 -11
  126. package/src/modes/turn-budget.ts +31 -0
  127. package/src/modes/types.ts +7 -1
  128. package/src/modes/ultrathink.ts +16 -10
  129. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  130. package/src/modes/workflow.ts +42 -0
  131. package/src/prompts/system/omfg-user.md +51 -0
  132. package/src/prompts/system/system-prompt.md +1 -0
  133. package/src/prompts/system/workflow-notice.md +70 -0
  134. package/src/prompts/tools/eval.md +13 -1
  135. package/src/prompts/tools/memory-edit.md +1 -1
  136. package/src/sdk.ts +63 -33
  137. package/src/session/agent-session.ts +373 -56
  138. package/src/session/session-manager.ts +32 -0
  139. package/src/session/session-storage.ts +68 -8
  140. package/src/session/shake-types.ts +44 -0
  141. package/src/slash-commands/builtin-registry.ts +41 -16
  142. package/src/task/executor.ts +3 -3
  143. package/src/task/index.ts +6 -6
  144. package/src/tiny/models.ts +30 -2
  145. package/src/tiny/title-protocol.ts +11 -1
  146. package/src/tiny/worker.ts +19 -7
  147. package/src/tools/eval.ts +202 -26
  148. package/src/tools/grouped-file-output.ts +9 -2
  149. package/src/tools/index.ts +17 -5
  150. package/src/tools/memory-edit.ts +4 -4
  151. package/src/tools/memory-recall.ts +5 -5
  152. package/src/tools/memory-reflect.ts +5 -5
  153. package/src/tools/memory-retain.ts +4 -4
  154. package/src/tools/render-utils.ts +2 -1
  155. package/src/tools/search.ts +480 -76
  156. package/dist/types/mnemosyne/backend.d.ts +0 -4
  157. package/dist/types/mnemosyne/config.d.ts +0 -29
  158. package/dist/types/mnemosyne/state.d.ts +0 -72
  159. /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
  160. /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
16
16
  import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
17
- import { type CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
17
+ import { type CompactionResult, type ShakeConfig } from "@oh-my-pi/pi-agent-core/compaction";
18
18
  import type { AssistantMessage, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@oh-my-pi/pi-ai";
19
19
  import { Effort } from "@oh-my-pi/pi-ai";
20
20
  import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
@@ -36,7 +36,7 @@ import { type FileSlashCommand } from "../extensibility/slash-commands";
36
36
  import { GoalRuntime } from "../goals/runtime";
37
37
  import type { Goal, GoalModeState } from "../goals/state";
38
38
  import type { HindsightSessionState } from "../hindsight/state";
39
- import { type MnemosyneSessionState } from "../mnemosyne/state";
39
+ import { type MnemopiSessionState } from "../mnemopi/state";
40
40
  import type { PlanModeState } from "../plan-mode/state";
41
41
  import { type AgentRegistry } from "../registry/agent-registry";
42
42
  import { type SecretObfuscator } from "../secrets/obfuscator";
@@ -47,16 +47,17 @@ import { type TodoItem, type TodoPhase } from "../tools/todo-write";
47
47
  import type { ClientBridge } from "./client-bridge";
48
48
  import { type CustomMessage } from "./messages";
49
49
  import type { BranchSummaryEntry, NewSessionOptions, SessionContext, SessionManager } from "./session-manager";
50
+ import type { ShakeMode, ShakeResult } from "./shake-types";
50
51
  import { ToolChoiceQueue } from "./tool-choice-queue";
51
52
  import { YieldQueue } from "./yield-queue";
52
53
  /** Session-specific events that extend the core AgentEvent */
53
54
  export type AgentSessionEvent = AgentEvent | {
54
55
  type: "auto_compaction_start";
55
56
  reason: "threshold" | "overflow" | "idle" | "incomplete";
56
- action: "context-full" | "handoff";
57
+ action: "context-full" | "handoff" | "shake" | "shake-summary";
57
58
  } | {
58
59
  type: "auto_compaction_end";
59
- action: "context-full" | "handoff";
60
+ action: "context-full" | "handoff" | "shake" | "shake-summary";
60
61
  result: CompactionResult | undefined;
61
62
  aborted: boolean;
62
63
  willRetry: boolean;
@@ -121,6 +122,7 @@ export interface AsyncJobSnapshot {
121
122
  recent: AsyncJobSnapshotItem[];
122
123
  delivery: AsyncJobDeliveryState;
123
124
  }
125
+ export type { ShakeMode, ShakeResult };
124
126
  export interface AgentSessionConfig {
125
127
  agent: Agent;
126
128
  sessionManager: SessionManager;
@@ -315,7 +317,7 @@ export declare class AgentSession {
315
317
  get providerSessionState(): Map<string, ProviderSessionState>;
316
318
  getHindsightSessionState(): HindsightSessionState | undefined;
317
319
  setHindsightSessionState(state: HindsightSessionState | undefined): HindsightSessionState | undefined;
318
- getMnemosyneSessionState(): MnemosyneSessionState | undefined;
320
+ getMnemopiSessionState(): MnemopiSessionState | undefined;
319
321
  /** TTSR manager for time-traveling stream rules */
320
322
  get ttsrManager(): TtsrManager | undefined;
321
323
  /** Whether a TTSR abort is pending (stream was aborted to inject rules) */
@@ -615,12 +617,13 @@ export declare class AgentSession {
615
617
  fork(): Promise<boolean>;
616
618
  /**
617
619
  * Set model directly.
618
- * Validates API key, saves to session and settings.
620
+ * Validates API key and saves to the active session. Persists settings only when requested.
619
621
  * @throws Error if no API key available for the model
620
622
  */
621
623
  setModel(model: Model, role?: string, options?: {
622
624
  selector?: string;
623
625
  thinkingLevel?: ThinkingLevel;
626
+ persist?: boolean;
624
627
  }): Promise<void>;
625
628
  /**
626
629
  * Set model temporarily (for this session only).
@@ -647,28 +650,26 @@ export declare class AgentSession {
647
650
  */
648
651
  getRoleModelCycle(roleOrder: readonly string[]): RoleModelCycle | undefined;
649
652
  /**
650
- * Apply a resolved role model as the active model, persisting the choice to
651
- * settings under its role. Mirrors the non-temporary branch of
652
- * {@link cycleRoleModels} and is shared with the plan-approval model slider.
653
+ * Apply a resolved role model as the active model without changing global
654
+ * settings. Shared with role cycling and the plan-approval model slider.
653
655
  */
654
656
  applyRoleModel(entry: ResolvedRoleModel): Promise<void>;
655
657
  /**
656
658
  * Cycle through configured role models in a fixed order.
657
- * Skips missing roles.
659
+ * Skips missing roles and changes only the active session model.
658
660
  * @param roleOrder - Order of roles to cycle through (e.g., ["slow", "default", "smol"])
659
- * @param options - Optional settings: `temporary` to not persist to settings
661
+ * @param direction - "forward" (default) or "backward"
660
662
  */
661
- cycleRoleModels(roleOrder: readonly string[], options?: {
662
- temporary?: boolean;
663
- }): Promise<RoleModelCycleResult | undefined>;
663
+ cycleRoleModels(roleOrder: readonly string[], direction?: "forward" | "backward"): Promise<RoleModelCycleResult | undefined>;
664
664
  /**
665
665
  * Get all available models with valid API keys.
666
666
  */
667
667
  getAvailableModels(): Model[];
668
668
  /**
669
- * Set the thinking level. `auto` enables per-turn classification (session-level,
670
- * never written to the session log); a concrete level clears auto. The effective
671
- * metadata-clamped level is saved to the session/settings only when it changes.
669
+ * Set the thinking level. `auto` enables per-turn classification; the selector
670
+ * itself is never written to the session log, but resolved concrete levels are
671
+ * persisted when real user turns are classified so resumed sessions keep the
672
+ * last resolved effort instead of reverting to pending auto.
672
673
  */
673
674
  setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist?: boolean): void;
674
675
  /**
@@ -730,6 +731,27 @@ export declare class AgentSession {
730
731
  dropImages(): Promise<{
731
732
  removed: number;
732
733
  }>;
734
+ /**
735
+ * Surgically reduce context by dropping heavy content ("shake").
736
+ *
737
+ * - `images` delegates to {@link dropImages}.
738
+ * - `elide` replaces whole tool-call results and large fenced/XML blocks
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
+ *
745
+ * Mutates the branch in place, persists via `rewriteEntries`, replays the
746
+ * rebuilt context through the agent, and tears down provider sessions that
747
+ * cache message identity — same rewrite contract as {@link dropImages}.
748
+ *
749
+ * No-op (zero counts) when nothing is eligible.
750
+ */
751
+ shake(mode: ShakeMode, opts?: {
752
+ config?: ShakeConfig;
753
+ signal?: AbortSignal;
754
+ }): Promise<ShakeResult>;
733
755
  /**
734
756
  * Manually compact the session context.
735
757
  * Aborts current agent operation first.
@@ -880,6 +902,7 @@ export declare class AgentSession {
880
902
  promptText: string;
881
903
  onTextDelta?: (delta: string) => void;
882
904
  signal?: AbortSignal;
905
+ dedupeReply?: boolean;
883
906
  }): Promise<{
884
907
  replyText: string;
885
908
  assistantMessage: AssistantMessage;
@@ -306,6 +306,24 @@ export declare class SessionManager {
306
306
  getCwd(): string;
307
307
  /** Get usage statistics across all assistant messages in the session. */
308
308
  getUsageStatistics(): UsageStatistics;
309
+ /**
310
+ * Open a new per-turn budget window: snapshot the cumulative output baseline,
311
+ * reset the eval-subagent counter, and set the (optional) ceiling. Called once
312
+ * per real user message; `total` is null when no `+Nk` directive was present.
313
+ */
314
+ beginTurnBudget(total: number | null, hard: boolean): void;
315
+ /** Record output tokens consumed by an eval-spawned subagent in the current turn. */
316
+ recordEvalSubagentOutput(output: number): void;
317
+ /**
318
+ * Current turn budget for the eval `budget` helper: the ceiling (null = none),
319
+ * output tokens spent this turn (main loop + eval-spawned subagents, no
320
+ * double-count), and whether the ceiling is hard.
321
+ */
322
+ getTurnBudget(): {
323
+ total: number | null;
324
+ spent: number;
325
+ hard: boolean;
326
+ };
309
327
  getSessionDir(): string;
310
328
  getSessionId(): string;
311
329
  getSessionFile(): string | undefined;
@@ -65,6 +65,12 @@ export declare class MemorySessionStorage implements SessionStorage {
65
65
  ensureDirSync(_dir: string): void;
66
66
  existsSync(path: string): boolean;
67
67
  writeTextSync(path: string, content: string): void;
68
+ /**
69
+ * Internal O(1) append used by {@link MemorySessionStorageWriter}. Lazily
70
+ * creates the entry. External callers should go through `openWriter()`
71
+ * rather than touching the mirror directly.
72
+ */
73
+ appendChunkSync(path: string, chunk: string): void;
68
74
  readTextSync(path: string): string;
69
75
  statSync(path: string): SessionStorageStat;
70
76
  listFilesSync(dir: string, pattern: string): string[];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Public shape of the `shake` operation, kept in a dependency-free leaf module
3
+ * so slash-command registries and controllers can import `formatShakeSummary`
4
+ * without pulling in the heavy `agent-session` module graph (which would form
5
+ * an import cycle through the slash-command registry).
6
+ */
7
+ /** Mode selector for `AgentSession.shake`. */
8
+ export type ShakeMode = "elide" | "summary" | "images";
9
+ /** Outcome of an `AgentSession.shake` run. */
10
+ export interface ShakeResult {
11
+ mode: ShakeMode;
12
+ /** Whole tool-call results dropped/compressed. */
13
+ toolResultsDropped: number;
14
+ /** Large fenced/XML blocks dropped/compressed. */
15
+ blocksDropped: number;
16
+ /** Image blocks removed (images mode only). */
17
+ imagesDropped?: number;
18
+ /** Estimated context tokens reclaimed. */
19
+ tokensFreed: number;
20
+ /** Session artifact holding the dropped originals, when persisted. */
21
+ artifactId?: string;
22
+ }
23
+ /** One-line operator summary of a {@link ShakeResult} (shared by TUI + ACP). */
24
+ export declare function formatShakeSummary(result: ShakeResult): string;
@@ -11,7 +11,7 @@ import { type Skill } from "../extensibility/skills";
11
11
  import type { HindsightSessionState } from "../hindsight/state";
12
12
  import type { LocalProtocolOptions } from "../internal-urls";
13
13
  import type { MCPManager } from "../mcp/manager";
14
- import type { MnemosyneSessionState } from "../mnemosyne/state";
14
+ import type { MnemopiSessionState } from "../mnemopi/state";
15
15
  import type { ArtifactManager } from "../session/artifacts";
16
16
  import type { AuthStorage } from "../session/auth-storage";
17
17
  import type { ContextFileEntry } from "../tools";
@@ -65,7 +65,7 @@ export interface ExecutorOptions {
65
65
  */
66
66
  parentArtifactManager?: ArtifactManager;
67
67
  parentHindsightSessionState?: HindsightSessionState;
68
- parentMnemosyneSessionState?: MnemosyneSessionState;
68
+ parentMnemopiSessionState?: MnemopiSessionState;
69
69
  /** Parent agent's eval executor session id. Subagents reuse it so eval state is shared. */
70
70
  parentEvalSessionId?: string;
71
71
  /**
@@ -65,7 +65,7 @@ export declare const ONLINE_MEMORY_MODEL_KEY = "online";
65
65
  /** Recommended local model for memory tasks when none is named. */
66
66
  export declare const DEFAULT_MEMORY_LOCAL_MODEL_KEY = "qwen3-1.7b";
67
67
  /**
68
- * Local models for Mnemosyne memory tasks (fact extraction + consolidation).
68
+ * Local models for Mnemopi memory tasks (fact extraction + consolidation).
69
69
  * These are larger (1B-1.7B) than the title models: structured extraction and
70
70
  * faithful summarization need more capacity than 3-6 word titles. All q4.
71
71
  * Ranking/recipe rationale lives in docs/local-models.md.
@@ -113,6 +113,20 @@ 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;
116
130
  /** Any local model key (title or memory), used by the shared inference worker. */
117
131
  export type TinyLocalModelKey = TinyTitleLocalModelKey | TinyMemoryLocalModelKey;
118
132
  /** Resolve a local model spec by key across both the title and memory registries. */
@@ -30,6 +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;
33
37
  } | {
34
38
  type: "download";
35
39
  id: string;
@@ -6,7 +6,9 @@ import type { Settings } from "../config/settings";
6
6
  import type { Skill } from "../extensibility/skills";
7
7
  import type { GoalModeState, GoalRuntime } from "../goals";
8
8
  import type { HindsightSessionState } from "../hindsight/state";
9
- import type { MnemosyneSessionState } from "../mnemosyne/state";
9
+ import type { LocalProtocolOptions } from "../internal-urls";
10
+ import type { MCPManager } from "../mcp";
11
+ import type { MnemopiSessionState } from "../mnemopi/state";
10
12
  import type { PlanModeState } from "../plan-mode/state";
11
13
  import { type AgentRegistry } from "../registry/agent-registry";
12
14
  import type { ArtifactManager } from "../session/artifacts";
@@ -107,8 +109,8 @@ export interface ToolSession {
107
109
  getSessionId?: () => string | null;
108
110
  /** Get Hindsight runtime state for this agent session. */
109
111
  getHindsightSessionState?: () => HindsightSessionState | undefined;
110
- /** Get Mnemosyne runtime state for this agent session. */
111
- getMnemosyneSessionState?: () => MnemosyneSessionState | undefined;
112
+ /** Get Mnemopi runtime state for this agent session. */
113
+ getMnemopiSessionState?: () => MnemopiSessionState | undefined;
112
114
  /** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
113
115
  getAgentId?: () => string | null;
114
116
  /** Look up a registered tool by name (used by the eval js backend's tool bridge). */
@@ -136,6 +138,10 @@ export interface ToolSession {
136
138
  modelRegistry?: import("../config/model-registry").ModelRegistry;
137
139
  /** Agent output manager for unique agent:// IDs across task invocations */
138
140
  agentOutputManager?: AgentOutputManager;
141
+ /** MCP manager visible to subagents without relying on the process-global singleton. */
142
+ mcpManager?: MCPManager;
143
+ /** Local protocol root to propagate to nested subagents and eval-created agents. */
144
+ localProtocolOptions?: LocalProtocolOptions;
139
145
  /** Settings instance for passing to subagents */
140
146
  settings: Settings;
141
147
  /** Plan mode state (if active) */
@@ -144,6 +150,16 @@ export interface ToolSession {
144
150
  getGoalModeState?: () => GoalModeState | undefined;
145
151
  /** Goal runtime for the active agent session. */
146
152
  getGoalRuntime?: () => GoalRuntime | undefined;
153
+ /** Get cumulative session usage statistics (input/output tokens, cost). */
154
+ getUsageStatistics?: () => import("../session/session-manager").UsageStatistics;
155
+ /** Current per-turn token budget {total, spent, hard} for the eval `budget` helper. */
156
+ getTurnBudget?: () => {
157
+ total: number | null;
158
+ spent: number;
159
+ hard: boolean;
160
+ };
161
+ /** Record output tokens consumed by an eval-spawned subagent toward the current turn budget. */
162
+ recordEvalSubagentUsage?: (output: number) => void;
147
163
  /** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
148
164
  getClientBridge?: () => ClientBridge | undefined;
149
165
  /** Get compact conversation context for subagents (excludes tool results, system prompts) */
@@ -32,7 +32,7 @@ export declare class MemoryEditTool implements AgentTool<typeof memoryEditSchema
32
32
  }, z.core.$strip>;
33
33
  readonly strict = true;
34
34
  readonly loadMode = "discoverable";
35
- readonly summary = "Update, forget, or invalidate Mnemosyne memories";
35
+ readonly summary = "Update, forget, or invalidate Mnemopi memories";
36
36
  constructor(session: ToolSession);
37
37
  static createIf(session: ToolSession): MemoryEditTool | null;
38
38
  execute(_id: string, params: MemoryEditParams): Promise<AgentToolResult>;
@@ -26,7 +26,7 @@ console.log("Session with default auth storage and model registry");
26
26
 
27
27
  // Custom auth storage location
28
28
  const customAuthStorage = await AuthStorage.create("/tmp/my-app/agent.db");
29
- const customModelRegistry = new ModelRegistry(customAuthStorage, "/tmp/my-app/models.json");
29
+ const customModelRegistry = await ModelRegistry.create(customAuthStorage, "/tmp/my-app/models.json");
30
30
 
31
31
  await createAgentSession({
32
32
  sessionManager: SessionManager.inMemory(),
@@ -45,7 +45,7 @@ await createAgentSession({
45
45
  console.log("Session with runtime API key override");
46
46
 
47
47
  // No models.json - only built-in models
48
- const simpleRegistry = new ModelRegistry(authStorage); // null = no models.json
48
+ const simpleRegistry = await ModelRegistry.create(authStorage); // null = no models.json
49
49
  await createAgentSession({
50
50
  sessionManager: SessionManager.inMemory(),
51
51
  authStorage,
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.2",
4
+ "version": "15.7.3",
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",
@@ -35,7 +35,7 @@
35
35
  "check": "biome check . && bun run check:types",
36
36
  "check:types": "tsgo -p tsconfig.json --noEmit",
37
37
  "lint": "biome lint .",
38
- "test": "bun test",
38
+ "test": "bun test --parallel",
39
39
  "fix": "biome check --write --unsafe . && bun run format-prompts && bun run generate-docs-index",
40
40
  "fmt": "biome format --write . && bun run format-prompts",
41
41
  "format-prompts": "bun scripts/format-prompts.ts",
@@ -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.2",
51
- "@oh-my-pi/omp-stats": "15.7.2",
52
- "@oh-my-pi/pi-agent-core": "15.7.2",
53
- "@oh-my-pi/pi-ai": "15.7.2",
54
- "@oh-my-pi/pi-mnemosyne": "15.7.2",
55
- "@oh-my-pi/pi-natives": "15.7.2",
56
- "@oh-my-pi/pi-tui": "15.7.2",
57
- "@oh-my-pi/pi-utils": "15.7.2",
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",
58
58
  "@puppeteer/browsers": "^3.0.4",
59
59
  "@types/turndown": "5.0.6",
60
60
  "@xterm/headless": "^6.0.0",
@@ -1,12 +1,12 @@
1
- import * as childProcess from "node:child_process";
2
1
  import * as fs from "node:fs";
3
2
  import * as path from "node:path";
4
3
  import { Text } from "@oh-my-pi/pi-tui";
5
4
  import { formatBytes } from "@oh-my-pi/pi-utils";
6
5
  import * as z from "zod/v4";
6
+ import { executeBash } from "../../exec/bash-executor";
7
7
  import type { ToolDefinition } from "../../extensibility/extensions";
8
8
  import type { Theme } from "../../modes/theme/theme";
9
- import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateTail } from "../../session/streaming-output";
9
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, TailBuffer, truncateTail } from "../../session/streaming-output";
10
10
  import { replaceTabs, shortenPath } from "../../tools/render-utils";
11
11
  import * as git from "../../utils/git";
12
12
  import { parseWorkDirDirtyPaths } from "../git";
@@ -15,7 +15,6 @@ import {
15
15
  EXPERIMENT_MAX_LINES,
16
16
  formatElapsed,
17
17
  formatNum,
18
- killTree,
19
18
  parseAsiLines,
20
19
  parseMetricLines,
21
20
  tryGitPrefix,
@@ -117,7 +116,7 @@ export function createRunExperimentTool(
117
116
  let execution: ProcessExecutionResult;
118
117
  try {
119
118
  execution = await executeProcess({
120
- command: ["bash", "-lc", resolvedCommand],
119
+ command: resolvedCommand,
121
120
  cwd: ctx.cwd,
122
121
  logPath: benchmarkLogPath,
123
122
  timeoutMs,
@@ -268,68 +267,18 @@ export function createRunExperimentTool(
268
267
  };
269
268
  }
270
269
  async function executeProcess(opts: {
271
- command: string[];
270
+ command: string;
272
271
  cwd: string;
273
272
  logPath: string;
274
273
  timeoutMs: number;
275
274
  signal?: AbortSignal;
276
275
  onProgress?(details: ProgressSnapshot): void;
277
276
  }): Promise<ProcessExecutionResult> {
278
- const { promise, resolve, reject } = Promise.withResolvers<ProcessExecutionResult>();
279
- const child = childProcess.spawn(opts.command[0] ?? "bash", opts.command.slice(1), {
280
- cwd: opts.cwd,
281
- detached: true,
282
- stdio: ["ignore", "pipe", "pipe"],
283
- });
284
-
285
- const tailChunks: Buffer[] = [];
286
- let chunksBytes = 0;
287
- let killedByTimeout = false;
288
- let resolved = false;
289
- let writeStream: fs.WriteStream | undefined = fs.createWriteStream(opts.logPath);
290
- let forceKillTimeout: NodeJS.Timeout | undefined;
291
-
292
- const closeWriteStream = (): Promise<void> => {
293
- if (!writeStream) return Promise.resolve();
294
- const stream = writeStream;
295
- writeStream = undefined;
296
- return new Promise<void>((resolveClose, rejectClose) => {
297
- stream.end((error?: Error | null) => {
298
- if (error) {
299
- rejectClose(error);
300
- return;
301
- }
302
- resolveClose();
303
- });
304
- });
305
- };
306
-
307
- const cleanup = (): void => {
308
- if (progressTimer) clearInterval(progressTimer);
309
- if (timeoutHandle) clearTimeout(timeoutHandle);
310
- if (forceKillTimeout) clearTimeout(forceKillTimeout);
311
- opts.signal?.removeEventListener("abort", abortHandler);
312
- };
313
-
314
- const finish = (callback: () => void): void => {
315
- if (resolved) return;
316
- resolved = true;
317
- cleanup();
318
- callback();
319
- };
320
-
321
- const appendChunk = (data: Buffer): void => {
322
- writeStream?.write(data);
323
- tailChunks.push(data);
324
- chunksBytes += data.length;
325
- while (chunksBytes > DEFAULT_MAX_BYTES * 2 && tailChunks.length > 1) {
326
- const removed = tailChunks.shift();
327
- if (removed) chunksBytes -= removed.length;
328
- }
329
- };
277
+ const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES * 2);
330
278
 
279
+ const startedAt = Date.now();
331
280
  const snapshot = (): ProgressSnapshot => {
332
- const tail = truncateTail(Buffer.concat(tailChunks).toString("utf8"), {
281
+ const tail = truncateTail(tailBuffer.text(), {
333
282
  maxBytes: DEFAULT_MAX_BYTES,
334
283
  maxLines: DEFAULT_MAX_LINES,
335
284
  });
@@ -342,71 +291,54 @@ async function executeProcess(opts: {
342
291
  };
343
292
  };
344
293
 
345
- const killTreeWithEscalation = (): void => {
346
- if (!child.pid) return;
347
- killTree(child.pid);
348
- forceKillTimeout = setTimeout(() => {
349
- if (child.pid) killTree(child.pid, "SIGKILL");
350
- }, 1_000);
351
- forceKillTimeout.unref?.();
352
- };
353
-
354
- const startedAt = Date.now();
355
294
  const progressTimer = opts.onProgress
356
295
  ? setInterval(() => {
357
296
  opts.onProgress?.(snapshot());
358
297
  }, 1000)
359
298
  : undefined;
360
- const timeoutHandle =
361
- opts.timeoutMs > 0
362
- ? setTimeout(() => {
363
- killedByTimeout = true;
364
- killTreeWithEscalation();
365
- }, opts.timeoutMs)
366
- : undefined;
367
299
 
368
- const abortHandler = (): void => {
369
- killTreeWithEscalation();
300
+ const logSink = Bun.file(opts.logPath).writer();
301
+ let logSinkClosed = false;
302
+ const closeLogSink = async (): Promise<void> => {
303
+ if (logSinkClosed) return;
304
+ logSinkClosed = true;
305
+ await logSink.end();
370
306
  };
371
- if (opts.signal?.aborted) {
372
- abortHandler();
373
- } else {
374
- opts.signal?.addEventListener("abort", abortHandler, { once: true });
375
- }
376
-
377
- child.stdout?.on("data", data => {
378
- appendChunk(data);
379
- });
380
- child.stderr?.on("data", data => {
381
- appendChunk(data);
382
- });
383
- child.on("error", error => {
384
- void closeWriteStream().finally(() => {
385
- finish(() => reject(error));
307
+ try {
308
+ const result = await executeBash(opts.command, {
309
+ cwd: opts.cwd,
310
+ sessionKey: `autoresearch:${opts.cwd}`,
311
+ timeout: opts.timeoutMs > 0 ? opts.timeoutMs : 2_147_000_000,
312
+ signal: opts.signal,
313
+ chunkThrottleMs: 0,
314
+ onChunk: chunk => {
315
+ tailBuffer.append(chunk);
316
+ logSink.write(chunk);
317
+ },
386
318
  });
387
- });
388
- child.on("close", async code => {
389
- try {
390
- await closeWriteStream();
391
- if (opts.signal?.aborted) {
392
- finish(() => reject(new Error("aborted")));
393
- return;
394
- }
395
- const output = await fs.promises.readFile(opts.logPath, "utf8");
396
- finish(() =>
397
- resolve({
398
- exitCode: code,
399
- killed: killedByTimeout,
400
- logPath: opts.logPath,
401
- output,
402
- }),
403
- );
404
- } catch (error) {
405
- finish(() => reject(error));
319
+ await closeLogSink();
320
+ if (opts.signal?.aborted) {
321
+ throw new Error("aborted");
406
322
  }
407
- });
408
323
 
409
- return promise;
324
+ const output = await fs.promises.readFile(opts.logPath, "utf8");
325
+
326
+ return {
327
+ exitCode: result.exitCode ?? null,
328
+ killed: result.cancelled,
329
+ logPath: opts.logPath,
330
+ output,
331
+ };
332
+ } finally {
333
+ if (progressTimer) clearInterval(progressTimer);
334
+ if (!logSinkClosed) {
335
+ try {
336
+ await closeLogSink();
337
+ } catch {
338
+ // Preserve the command failure when cleanup is best-effort.
339
+ }
340
+ }
341
+ }
410
342
  }
411
343
 
412
344
  function buildRunText(details: RunDetails, outputPreview: string, bestMetric: number | null): string {