@oh-my-pi/pi-coding-agent 15.12.4 → 15.13.0

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 (291) hide show
  1. package/CHANGELOG.md +304 -6
  2. package/dist/cli.js +1015 -881
  3. package/dist/types/async/job-manager.d.ts +15 -0
  4. package/dist/types/autolearn/controller.d.ts +25 -0
  5. package/dist/types/autolearn/managed-skills.d.ts +45 -0
  6. package/dist/types/autoresearch/state.d.ts +1 -1
  7. package/dist/types/autoresearch/types.d.ts +1 -1
  8. package/dist/types/cli/args.d.ts +19 -1
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -1
  11. package/dist/types/cli/setup-model-picker.d.ts +14 -0
  12. package/dist/types/collab/protocol.d.ts +1 -1
  13. package/dist/types/commands/say.d.ts +24 -0
  14. package/dist/types/config/keybindings.d.ts +3 -3
  15. package/dist/types/config/model-registry.d.ts +10 -0
  16. package/dist/types/config/models-config-schema.d.ts +12 -0
  17. package/dist/types/config/models-config.d.ts +8 -2
  18. package/dist/types/config/settings-schema.d.ts +261 -58
  19. package/dist/types/export/html/index.d.ts +2 -1
  20. package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
  21. package/dist/types/extensibility/extensions/runner.d.ts +3 -1
  22. package/dist/types/extensibility/extensions/types.d.ts +47 -1
  23. package/dist/types/extensibility/hooks/index.d.ts +2 -1
  24. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
  25. package/dist/types/extensibility/plugins/loader.d.ts +11 -0
  26. package/dist/types/extensibility/shared-events.d.ts +1 -1
  27. package/dist/types/extensibility/skills.d.ts +10 -0
  28. package/dist/types/goals/guided-setup.d.ts +18 -0
  29. package/dist/types/goals/state.d.ts +1 -1
  30. package/dist/types/hindsight/transcript.d.ts +1 -1
  31. package/dist/types/index.d.ts +5 -0
  32. package/dist/types/internal-urls/local-protocol.d.ts +4 -2
  33. package/dist/types/main.d.ts +4 -3
  34. package/dist/types/mcp/startup-events.d.ts +11 -0
  35. package/dist/types/memories/index.d.ts +7 -0
  36. package/dist/types/memory-backend/local-backend.d.ts +4 -3
  37. package/dist/types/mnemopi/config.d.ts +4 -4
  38. package/dist/types/modes/components/agent-hub.d.ts +6 -0
  39. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  40. package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
  41. package/dist/types/modes/components/custom-editor.d.ts +39 -1
  42. package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
  43. package/dist/types/modes/components/session-selector.d.ts +1 -1
  44. package/dist/types/modes/components/tool-execution.d.ts +26 -16
  45. package/dist/types/modes/components/transcript-container.d.ts +23 -2
  46. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  47. package/dist/types/modes/components/usage-row.d.ts +3 -0
  48. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  49. package/dist/types/modes/controllers/input-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  51. package/dist/types/modes/gradient-highlight.d.ts +9 -4
  52. package/dist/types/modes/image-references.d.ts +6 -0
  53. package/dist/types/modes/interactive-mode.d.ts +27 -3
  54. package/dist/types/modes/magic-keywords.d.ts +13 -1
  55. package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
  56. package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
  57. package/dist/types/modes/runtime-init.d.ts +4 -0
  58. package/dist/types/modes/theme/theme.d.ts +13 -2
  59. package/dist/types/modes/types.d.ts +8 -2
  60. package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
  61. package/dist/types/registry/agent-registry.d.ts +17 -0
  62. package/dist/types/secrets/obfuscator.d.ts +1 -1
  63. package/dist/types/session/agent-session.d.ts +14 -2
  64. package/dist/types/session/indexed-session-storage.d.ts +3 -4
  65. package/dist/types/session/session-context.d.ts +39 -0
  66. package/dist/types/session/session-entries.d.ts +159 -0
  67. package/dist/types/session/session-listing.d.ts +69 -0
  68. package/dist/types/session/session-loader.d.ts +16 -0
  69. package/dist/types/session/session-manager.d.ts +82 -474
  70. package/dist/types/session/session-migrations.d.ts +12 -0
  71. package/dist/types/session/session-paths.d.ts +25 -0
  72. package/dist/types/session/session-persistence.d.ts +8 -0
  73. package/dist/types/session/session-storage.d.ts +11 -12
  74. package/dist/types/session/snapcompact-inline.d.ts +12 -1
  75. package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
  76. package/dist/types/session/tool-choice-queue.d.ts +6 -6
  77. package/dist/types/stt/asr-client.d.ts +90 -0
  78. package/dist/types/stt/asr-protocol.d.ts +97 -0
  79. package/dist/types/stt/asr-worker.d.ts +2 -0
  80. package/dist/types/stt/downloader.d.ts +38 -0
  81. package/dist/types/stt/endpointer.d.ts +59 -0
  82. package/dist/types/stt/index.d.ts +5 -1
  83. package/dist/types/stt/models.d.ts +120 -0
  84. package/dist/types/stt/recorder.d.ts +17 -0
  85. package/dist/types/stt/stt-controller.d.ts +6 -0
  86. package/dist/types/stt/transcriber.d.ts +5 -7
  87. package/dist/types/stt/wav.d.ts +29 -0
  88. package/dist/types/system-prompt.d.ts +4 -0
  89. package/dist/types/task/executor.d.ts +2 -0
  90. package/dist/types/task/index.d.ts +9 -1
  91. package/dist/types/task/types.d.ts +36 -0
  92. package/dist/types/tools/bash.d.ts +2 -2
  93. package/dist/types/tools/eval-render.d.ts +1 -1
  94. package/dist/types/tools/index.d.ts +11 -1
  95. package/dist/types/tools/irc.d.ts +1 -0
  96. package/dist/types/tools/learn.d.ts +51 -0
  97. package/dist/types/tools/manage-skill.d.ts +40 -0
  98. package/dist/types/tools/plan-mode-guard.d.ts +10 -0
  99. package/dist/types/tools/renderers.d.ts +7 -11
  100. package/dist/types/tools/ssh.d.ts +1 -1
  101. package/dist/types/tools/todo.d.ts +1 -1
  102. package/dist/types/tools/tts.d.ts +25 -0
  103. package/dist/types/tools/write.d.ts +1 -1
  104. package/dist/types/tts/downloader.d.ts +20 -0
  105. package/dist/types/tts/index.d.ts +8 -0
  106. package/dist/types/tts/models.d.ts +82 -0
  107. package/dist/types/tts/player.d.ts +32 -0
  108. package/dist/types/tts/runtime.d.ts +6 -0
  109. package/dist/types/tts/streaming-player.d.ts +41 -0
  110. package/dist/types/tts/tts-client.d.ts +93 -0
  111. package/dist/types/tts/tts-protocol.d.ts +95 -0
  112. package/dist/types/tts/tts-worker.d.ts +2 -0
  113. package/dist/types/tts/vocalizer.d.ts +41 -0
  114. package/dist/types/tts/wav.d.ts +8 -0
  115. package/dist/types/utils/tool-choice.d.ts +8 -0
  116. package/dist/types/utils/tools-manager.d.ts +2 -1
  117. package/dist/types/utils/tools-manager.test.d.ts +1 -0
  118. package/dist/types/web/scrapers/github.d.ts +1 -1
  119. package/package.json +15 -14
  120. package/src/async/job-manager.ts +49 -0
  121. package/src/autolearn/controller.ts +139 -0
  122. package/src/autolearn/managed-skills.ts +257 -0
  123. package/src/autoresearch/state.ts +1 -1
  124. package/src/autoresearch/types.ts +1 -1
  125. package/src/cli/args.ts +56 -2
  126. package/src/cli/session-picker.ts +2 -1
  127. package/src/cli/setup-cli.ts +148 -47
  128. package/src/cli/setup-model-picker.ts +43 -0
  129. package/src/cli-commands.ts +1 -0
  130. package/src/cli.ts +45 -13
  131. package/src/collab/host.ts +1 -1
  132. package/src/collab/protocol.ts +1 -1
  133. package/src/commands/say.ts +102 -0
  134. package/src/commands/setup.ts +1 -1
  135. package/src/commit/agentic/tools/analyze-file.ts +3 -0
  136. package/src/config/keybindings.ts +2 -2
  137. package/src/config/model-discovery.ts +11 -5
  138. package/src/config/model-registry.ts +64 -9
  139. package/src/config/models-config-schema.ts +4 -1
  140. package/src/config/models-config.ts +2 -1
  141. package/src/config/settings-schema.ts +248 -32
  142. package/src/config/settings.ts +10 -0
  143. package/src/discovery/builtin.ts +23 -1
  144. package/src/discovery/claude-plugins.ts +44 -5
  145. package/src/discovery/helpers.ts +41 -1
  146. package/src/eval/__tests__/budget-bridge.test.ts +1 -1
  147. package/src/eval/js/shared/prelude.txt +69 -17
  148. package/src/export/html/index.ts +3 -6
  149. package/src/extensibility/extensions/model-api.ts +41 -0
  150. package/src/extensibility/extensions/runner.ts +4 -0
  151. package/src/extensibility/extensions/types.ts +52 -1
  152. package/src/extensibility/extensions/wrapper.ts +41 -5
  153. package/src/extensibility/hooks/index.ts +2 -1
  154. package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
  155. package/src/extensibility/plugins/loader.ts +30 -19
  156. package/src/extensibility/plugins/manager.ts +221 -90
  157. package/src/extensibility/shared-events.ts +1 -1
  158. package/src/extensibility/skills.ts +96 -15
  159. package/src/goals/guided-setup.ts +133 -0
  160. package/src/goals/state.ts +1 -1
  161. package/src/hindsight/transcript.ts +1 -1
  162. package/src/index.ts +5 -0
  163. package/src/internal-urls/docs-index.generated.ts +10 -10
  164. package/src/internal-urls/history-protocol.ts +1 -1
  165. package/src/internal-urls/local-protocol.ts +29 -7
  166. package/src/main.ts +27 -7
  167. package/src/mcp/startup-events.ts +21 -0
  168. package/src/mcp/transports/stdio.ts +2 -1
  169. package/src/memories/index.ts +146 -11
  170. package/src/memory-backend/local-backend.ts +11 -5
  171. package/src/mnemopi/backend.ts +1 -0
  172. package/src/mnemopi/config.ts +26 -10
  173. package/src/modes/acp/acp-agent.ts +3 -5
  174. package/src/modes/components/agent-hub.ts +49 -4
  175. package/src/modes/components/assistant-message.ts +4 -37
  176. package/src/modes/components/compaction-summary-message.ts +125 -26
  177. package/src/modes/components/custom-editor.test.ts +96 -0
  178. package/src/modes/components/custom-editor.ts +164 -8
  179. package/src/modes/components/session-selector.ts +1 -1
  180. package/src/modes/components/settings-defs.ts +7 -0
  181. package/src/modes/components/tool-execution.ts +82 -43
  182. package/src/modes/components/transcript-container.ts +70 -1
  183. package/src/modes/components/tree-selector.ts +1 -1
  184. package/src/modes/components/usage-row.ts +18 -0
  185. package/src/modes/components/user-message.ts +4 -2
  186. package/src/modes/controllers/command-controller.ts +14 -4
  187. package/src/modes/controllers/event-controller.ts +78 -11
  188. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  189. package/src/modes/controllers/input-controller.ts +258 -27
  190. package/src/modes/controllers/selector-controller.ts +12 -2
  191. package/src/modes/gradient-highlight.ts +21 -9
  192. package/src/modes/image-references.ts +20 -0
  193. package/src/modes/interactive-mode.ts +286 -40
  194. package/src/modes/magic-keywords.ts +27 -5
  195. package/src/modes/rpc/rpc-mode.ts +146 -14
  196. package/src/modes/rpc/rpc-subagents.ts +2 -2
  197. package/src/modes/rpc/rpc-types.ts +8 -2
  198. package/src/modes/runtime-init.ts +28 -3
  199. package/src/modes/theme/theme.ts +98 -50
  200. package/src/modes/types.ts +6 -2
  201. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  202. package/src/modes/utils/ui-helpers.ts +34 -6
  203. package/src/priority.json +5 -1
  204. package/src/prompts/agents/task.md +1 -0
  205. package/src/prompts/goals/guided-goal-interview.md +8 -0
  206. package/src/prompts/goals/guided-goal-system.md +12 -0
  207. package/src/prompts/memories/read-path.md +6 -0
  208. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  209. package/src/prompts/system/autolearn-guidance.md +7 -0
  210. package/src/prompts/system/autolearn-nudge.md +3 -0
  211. package/src/prompts/system/eager-task.md +7 -0
  212. package/src/prompts/system/eager-todo.md +11 -6
  213. package/src/prompts/system/subagent-system-prompt.md +4 -0
  214. package/src/prompts/system/system-prompt.md +10 -5
  215. package/src/prompts/system/title-marker-instruction.md +1 -0
  216. package/src/prompts/system/title-system-marker.md +16 -0
  217. package/src/prompts/tools/job.md +1 -0
  218. package/src/prompts/tools/learn.md +7 -0
  219. package/src/prompts/tools/manage-skill.md +9 -0
  220. package/src/prompts/tools/task.md +3 -0
  221. package/src/registry/agent-registry.ts +30 -0
  222. package/src/sdk.ts +88 -24
  223. package/src/secrets/obfuscator.ts +1 -1
  224. package/src/session/agent-session.ts +209 -87
  225. package/src/session/history-storage.ts +2 -2
  226. package/src/session/indexed-session-storage.ts +7 -17
  227. package/src/session/session-context.ts +352 -0
  228. package/src/session/session-entries.ts +194 -0
  229. package/src/session/session-listing.ts +588 -0
  230. package/src/session/session-loader.ts +106 -0
  231. package/src/session/session-manager.ts +933 -3145
  232. package/src/session/session-migrations.ts +78 -0
  233. package/src/session/session-paths.ts +193 -0
  234. package/src/session/session-persistence.ts +131 -0
  235. package/src/session/session-storage.ts +91 -50
  236. package/src/session/snapcompact-inline.ts +21 -1
  237. package/src/session/snapcompact-savings-journal.ts +113 -0
  238. package/src/session/tool-choice-queue.ts +23 -11
  239. package/src/slash-commands/builtin-registry.ts +25 -3
  240. package/src/stt/asr-client.ts +520 -0
  241. package/src/stt/asr-protocol.ts +65 -0
  242. package/src/stt/asr-worker.ts +790 -0
  243. package/src/stt/downloader.ts +107 -47
  244. package/src/stt/endpointer.ts +259 -0
  245. package/src/stt/index.ts +5 -1
  246. package/src/stt/models.ts +150 -0
  247. package/src/stt/recorder.ts +247 -60
  248. package/src/stt/stt-controller.ts +201 -22
  249. package/src/stt/transcriber.ts +37 -68
  250. package/src/stt/wav.ts +173 -0
  251. package/src/system-prompt.ts +8 -0
  252. package/src/task/agents.ts +1 -2
  253. package/src/task/executor.ts +49 -15
  254. package/src/task/index.ts +60 -6
  255. package/src/task/render.ts +83 -8
  256. package/src/task/types.ts +53 -0
  257. package/src/tools/ask.ts +8 -0
  258. package/src/tools/bash.ts +4 -3
  259. package/src/tools/eval-render.ts +4 -3
  260. package/src/tools/index.ts +40 -4
  261. package/src/tools/irc.ts +10 -2
  262. package/src/tools/job.ts +14 -2
  263. package/src/tools/learn.ts +144 -0
  264. package/src/tools/manage-skill.ts +104 -0
  265. package/src/tools/plan-mode-guard.ts +53 -19
  266. package/src/tools/renderers.ts +7 -11
  267. package/src/tools/ssh.ts +4 -3
  268. package/src/tools/todo.ts +1 -1
  269. package/src/tools/tts.ts +203 -92
  270. package/src/tools/write.ts +18 -2
  271. package/src/tts/downloader.ts +64 -0
  272. package/src/tts/index.ts +8 -0
  273. package/src/tts/models.ts +137 -0
  274. package/src/tts/player.ts +137 -0
  275. package/src/tts/runtime.ts +21 -0
  276. package/src/tts/streaming-player.ts +266 -0
  277. package/src/tts/tts-client.ts +647 -0
  278. package/src/tts/tts-protocol.ts +60 -0
  279. package/src/tts/tts-worker.ts +497 -0
  280. package/src/tts/vocalizer.ts +162 -0
  281. package/src/tts/wav.ts +58 -0
  282. package/src/utils/title-generator.ts +48 -5
  283. package/src/utils/tool-choice.ts +16 -0
  284. package/src/utils/tools-manager.test.ts +25 -0
  285. package/src/utils/tools-manager.ts +19 -1
  286. package/src/web/scrapers/github.ts +96 -0
  287. package/src/web/search/index.ts +13 -0
  288. package/src/web/search/providers/searxng.ts +13 -1
  289. package/dist/types/stt/setup.d.ts +0 -18
  290. package/src/stt/setup.ts +0 -52
  291. package/src/stt/transcribe.py +0 -70
@@ -0,0 +1,32 @@
1
+ export interface PlayerCommand {
2
+ cmd: string;
3
+ args: string[];
4
+ }
5
+ /** Injection seam for {@link playerCommandsFor} — defaults to real PATH/tools lookups. */
6
+ export interface PlayerLookup {
7
+ which?: (bin: string) => string | null;
8
+ ffmpeg?: () => string | null;
9
+ }
10
+ /**
11
+ * Build the ordered list of playback commands to try for `filePath` on the
12
+ * given platform. Pure + injectable so the selection logic is testable without
13
+ * spawning anything.
14
+ *
15
+ * - darwin: `afplay` (always present on macOS).
16
+ * - win32: PowerShell `Media.SoundPlayer.PlaySync()` (no extra deps).
17
+ * - linux/other POSIX: `paplay` (PulseAudio) → `aplay` (ALSA) → the bundled
18
+ * static `ffmpeg` (`-f pulse` then `-f alsa`). Empty result means nothing is
19
+ * available and the caller should surface an install hint.
20
+ */
21
+ export declare function playerCommandsFor(platform: NodeJS.Platform, filePath: string, lookup?: PlayerLookup): PlayerCommand[];
22
+ export interface PlayAudioOptions {
23
+ signal?: AbortSignal;
24
+ }
25
+ /**
26
+ * Play `filePath` through the speakers, trying each candidate command in order
27
+ * and returning on the first clean exit. Throws an actionable Error if no
28
+ * player exists or every candidate fails (with the collected stderr).
29
+ */
30
+ export declare function playAudioFile(filePath: string, options?: PlayAudioOptions): Promise<void>;
31
+ /** Best-effort temp-file cleanup used by callers after playback. */
32
+ export declare function removeTempFile(filePath: string): Promise<void>;
@@ -0,0 +1,6 @@
1
+ export declare const KOKORO_PACKAGE = "kokoro-js";
2
+ export declare const KOKORO_VERSION = "1.2.1";
3
+ export declare const ONNXRUNTIME_NODE_PACKAGE = "onnxruntime-node";
4
+ export declare const ONNXRUNTIME_NODE_VERSION = "1.26.0";
5
+ export declare function getTtsRuntimeDir(): string;
6
+ export declare function isTtsRuntimeCached(): Promise<boolean>;
@@ -0,0 +1,41 @@
1
+ import { type PlayerCommand } from "./player";
2
+ /** Output gain applied while ducked (the user is speaking over the assistant). */
3
+ export declare const DUCK_GAIN = 0.25;
4
+ /** Injection seam for {@link streamingPlayerCommandsFor} — defaults to real PATH/tools lookups. */
5
+ export interface StreamingPlayerLookup {
6
+ which?: (bin: string) => string | null;
7
+ ffmpeg?: () => string | null;
8
+ }
9
+ /**
10
+ * Ordered candidate commands for a persistent raw-PCM player on `platform`: each
11
+ * reads 32-bit-float little-endian mono PCM at `sampleRate` from stdin (`pipe:0`)
12
+ * and plays it to the default output device. An empty list means no streaming
13
+ * backend is available and the caller should fall back to per-file playback.
14
+ *
15
+ * - darwin: none; `afplay` is file-only, so macOS uses the interruptible
16
+ * per-file fallback.
17
+ * - linux/other POSIX: `ffmpeg` (`-f pulse` then `-f alsa`) → `paplay`/`aplay`
18
+ * raw fallbacks.
19
+ * - win32: none (PowerShell `SoundPlayer` is file-only).
20
+ */
21
+ export declare function streamingPlayerCommandsFor(platform: NodeJS.Platform, sampleRate: number, lookup?: StreamingPlayerLookup): PlayerCommand[];
22
+ /**
23
+ * Single-session gapless player. Lifecycle: {@link start} once, {@link write}
24
+ * chunks in order, then {@link end} to drain or {@link stop} to abort. Not
25
+ * reusable after stop/end — create a new instance per utterance.
26
+ */
27
+ export declare class StreamingAudioPlayer {
28
+ #private;
29
+ /** Pick a backend and begin draining. Idempotent; the first call's rate wins. */
30
+ start(sampleRate: number): void;
31
+ /** Queue a mono float32 PCM chunk for playback in arrival order. */
32
+ write(pcm: Float32Array): void;
33
+ /** Scale subsequent output (1 = normal, <1 = ducked). Applies within {@link LEAD_SECONDS}. */
34
+ setGain(gain: number): void;
35
+ /** Close the input; resolves once all queued audio has finished playing. */
36
+ end(): Promise<void>;
37
+ /** Stop immediately: kill the player, drop everything still queued. */
38
+ stop(): void;
39
+ }
40
+ /** Factory the vocalizer calls; a function so tests can stub it without spawning a player. */
41
+ export declare function createStreamingPlayer(): StreamingAudioPlayer;
@@ -0,0 +1,93 @@
1
+ import type { Subprocess } from "bun";
2
+ import type { TtsProgressEvent, TtsWorkerInbound, TtsWorkerOutbound } from "./tts-protocol";
3
+ /** Decoded PCM returned by a local synthesis request. */
4
+ export interface TtsAudio {
5
+ pcm: Float32Array;
6
+ sampleRate: number;
7
+ }
8
+ /**
9
+ * Abstraction over the TTS subprocess. The runtime implementation is a Bun child
10
+ * process so `onnxruntime-node`'s NAPI finalizer never runs inside the main agent
11
+ * address space — that destructor segfaults Bun during shutdown (issue #1606).
12
+ */
13
+ interface WorkerHandle {
14
+ send(message: TtsWorkerInbound): void;
15
+ onMessage(handler: (message: TtsWorkerOutbound) => void): () => void;
16
+ onError(handler: (error: Error) => void): () => void;
17
+ /** Re-reference the subprocess so a pending request keeps the parent event loop alive. */
18
+ ref(): void;
19
+ /** Drop the reference once the worker is idle so it never blocks process exit. */
20
+ unref(): void;
21
+ terminate(): Promise<void>;
22
+ }
23
+ export interface TtsSynthesizeOptions {
24
+ voice?: string;
25
+ signal?: AbortSignal;
26
+ }
27
+ export interface TtsDownloadOptions {
28
+ signal?: AbortSignal;
29
+ onProgress?: (event: TtsProgressEvent) => void;
30
+ }
31
+ export interface TtsStreamOptions {
32
+ voice?: string;
33
+ signal?: AbortSignal;
34
+ }
35
+ /** One synthesized sentence of a streaming session, in emission order. */
36
+ export interface TtsAudioChunk {
37
+ index: number;
38
+ text: string;
39
+ pcm: Float32Array;
40
+ sampleRate: number;
41
+ }
42
+ /**
43
+ * A live streaming-synthesis session. Feed text incrementally with {@link push}
44
+ * and close the input with {@link end}; `chunks` yields each synthesized
45
+ * sentence's audio as soon as it is ready, then completes once the worker
46
+ * finishes draining the closed input.
47
+ */
48
+ export interface TtsStreamHandle {
49
+ push(text: string): void;
50
+ end(): void;
51
+ chunks: AsyncIterableIterator<TtsAudioChunk>;
52
+ }
53
+ /**
54
+ * Hidden subcommand on the main CLI that boots the TTS worker in the spawned
55
+ * subprocess. Kept in sync with the dispatch in `cli.ts` (Main-owned).
56
+ */
57
+ export declare const TTS_WORKER_ARG = "__omp_tts_worker";
58
+ interface SpawnedSubprocess {
59
+ proc: Subprocess<"ignore", "ignore", "ignore">;
60
+ inbound: Set<(message: TtsWorkerOutbound) => void>;
61
+ errors: Set<(error: Error) => void>;
62
+ /** Flipped to `true` right before the deliberate SIGKILL so `onExit` can tell it apart from a crash. */
63
+ intentionalExit: {
64
+ value: boolean;
65
+ };
66
+ }
67
+ /**
68
+ * Spawn the TTS worker as a subprocess. Exported for tests and the smoke probe;
69
+ * production callers go through {@link spawnTtsWorker}.
70
+ */
71
+ export declare function createTtsSubprocess(): SpawnedSubprocess;
72
+ export declare class TtsClient {
73
+ #private;
74
+ constructor(spawnWorker?: () => WorkerHandle);
75
+ onProgress(listener: (event: TtsProgressEvent) => void): () => void;
76
+ synthesize(modelKey: string, text: string, options?: TtsSynthesizeOptions): Promise<TtsAudio | null>;
77
+ /**
78
+ * Open a streaming-synthesis session. Text is fed incrementally through the
79
+ * returned handle's `push`/`end`; audio is emitted one synthesized sentence at
80
+ * a time via `chunks`, so playback can begin before the full text is known.
81
+ * Returns an inert handle (immediately-ended `chunks`) for unknown models or
82
+ * an already-aborted signal, and fails the iterator if the worker cannot spawn.
83
+ */
84
+ synthesizeStream(modelKey: string, options?: TtsStreamOptions): TtsStreamHandle;
85
+ downloadModel(modelKey: string, options?: TtsDownloadOptions): Promise<boolean>;
86
+ terminate(): Promise<void>;
87
+ }
88
+ export declare const ttsClient: TtsClient;
89
+ export declare function shutdownTtsClient(): Promise<void>;
90
+ export declare function smokeTestTtsWorker({ timeoutMs, }?: {
91
+ timeoutMs?: number;
92
+ }): Promise<void>;
93
+ export {};
@@ -0,0 +1,95 @@
1
+ import type { TtsLocalModelKey } from "./models";
2
+ export type TtsProgressStatus = "initiate" | "download" | "progress" | "progress_total" | "done" | "ready" | "error";
3
+ export interface TtsProgressFileState {
4
+ loaded: number;
5
+ total: number;
6
+ }
7
+ export interface TtsProgressEvent {
8
+ modelKey: TtsLocalModelKey;
9
+ status: TtsProgressStatus;
10
+ name?: string;
11
+ file?: string;
12
+ progress?: number;
13
+ loaded?: number;
14
+ total?: number;
15
+ files?: Record<string, TtsProgressFileState>;
16
+ task?: string;
17
+ model?: string;
18
+ }
19
+ export type TtsWorkerInbound = {
20
+ type: "ping";
21
+ id: string;
22
+ } | {
23
+ type: "synthesize";
24
+ id: string;
25
+ modelKey: TtsLocalModelKey;
26
+ text: string;
27
+ voice?: string;
28
+ } | {
29
+ type: "download";
30
+ id: string;
31
+ modelKey: TtsLocalModelKey;
32
+ } | {
33
+ type: "stream-start";
34
+ id: string;
35
+ modelKey: TtsLocalModelKey;
36
+ voice?: string;
37
+ } | {
38
+ type: "stream-push";
39
+ id: string;
40
+ text: string;
41
+ } | {
42
+ type: "stream-end";
43
+ id: string;
44
+ } | {
45
+ type: "stream-cancel";
46
+ id: string;
47
+ };
48
+ export type TtsWorkerOutbound = {
49
+ type: "pong";
50
+ id: string;
51
+ } | {
52
+ type: "audio";
53
+ id: string;
54
+ pcm: Float32Array;
55
+ sampleRate: number;
56
+ } | {
57
+ type: "downloaded";
58
+ id: string;
59
+ } | {
60
+ type: "error";
61
+ id: string;
62
+ error: string;
63
+ } | {
64
+ type: "progress";
65
+ id: string;
66
+ event: TtsProgressEvent;
67
+ } | {
68
+ type: "log";
69
+ level: "debug" | "warn" | "error";
70
+ msg: string;
71
+ meta?: Record<string, unknown>;
72
+ } | {
73
+ type: "audio-chunk";
74
+ id: string;
75
+ index: number;
76
+ text: string;
77
+ pcm: Float32Array;
78
+ sampleRate: number;
79
+ } | {
80
+ type: "stream-done";
81
+ id: string;
82
+ };
83
+ /**
84
+ * Wire transport between the parent (`TtsClient`) and the local TTS subprocess.
85
+ * The parent owns the subprocess lifecycle (graceful work, hard SIGKILL on
86
+ * shutdown); the protocol carries no explicit close handshake — once the parent
87
+ * decides to terminate, it signals the OS to reap the child so
88
+ * `onnxruntime-node`'s NAPI finalizer never runs in the main agent address
89
+ * space (it segfaults Bun on shutdown — issue #1606). See `tts-client.ts` for
90
+ * the spawn/kill glue.
91
+ */
92
+ export interface TtsTransport {
93
+ send(message: TtsWorkerOutbound): void;
94
+ onMessage(handler: (message: TtsWorkerInbound) => void): () => void;
95
+ }
@@ -0,0 +1,2 @@
1
+ import type { TtsTransport } from "./tts-protocol";
2
+ export declare function startTtsWorker(transport: TtsTransport): void;
@@ -0,0 +1,41 @@
1
+ export interface VocalizerPlayer {
2
+ start(sampleRate: number): void;
3
+ write(pcm: Float32Array): void;
4
+ setGain(gain: number): void;
5
+ end(): Promise<void>;
6
+ stop(): void;
7
+ }
8
+ export declare class Vocalizer {
9
+ #private;
10
+ constructor(createPlayer?: () => VocalizerPlayer);
11
+ /**
12
+ * Stream a delta of assistant text into the engine. No-op when vocalization
13
+ * is disabled. The engine buffers the running text and emits audio for each
14
+ * complete sentence; the trailing partial is flushed by {@link flush}.
15
+ */
16
+ pushDelta(text: string): void;
17
+ /**
18
+ * Close the current input stream (call at message/turn end). The engine
19
+ * flushes its trailing partial as a final chunk; the player keeps draining
20
+ * queued audio until it completes.
21
+ */
22
+ flush(): void;
23
+ /**
24
+ * Speak a complete piece of text in one shot (ask questions, yield-mode final
25
+ * message): stream it in and immediately close the input. No-op when disabled.
26
+ */
27
+ speak(text: string): void;
28
+ /**
29
+ * Interrupt and drop the current session, killing in-flight playback and
30
+ * synthesis (new turn / user message / Esc interrupt). Audio stops at once.
31
+ */
32
+ clear(): void;
33
+ /** Lower the volume while the user is speaking (push-to-talk), so speech doesn't drown them out. */
34
+ duck(): void;
35
+ /** Restore full volume once the user stops speaking. */
36
+ unduck(): void;
37
+ /** Resolve once the playback chain has drained (tests / shutdown). */
38
+ idle(): Promise<void>;
39
+ }
40
+ /** Process-level vocalizer shared by the event controller and the ask tool. */
41
+ export declare const vocalizer: Vocalizer;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Assemble a mono PCM16 WAV byte buffer from Float32 PCM samples (the shape
3
+ * transformers.js `RawAudio` emits: normalized [-1, 1] amplitudes plus a sample
4
+ * rate). No external encoder is involved — we write a canonical 44-byte RIFF/
5
+ * WAVE header followed by little-endian signed 16-bit samples. Samples are
6
+ * clamped before quantization so out-of-range float values do not wrap.
7
+ */
8
+ export declare function encodeWav(samples: Float32Array, sampleRate: number): Uint8Array;
@@ -5,3 +5,11 @@ import type { Api, Model, ToolChoice } from "@oh-my-pi/pi-ai";
5
5
  * narrowing their request tool list before transport.
6
6
  */
7
7
  export declare function buildNamedToolChoice(toolName: string, model?: Model<Api>): ToolChoice | undefined;
8
+ /**
9
+ * Whether the given tool choice can be satisfied by the active tool set for the
10
+ * upcoming turn. Non-named choices (`"none"`, `"required"`, etc.) do not name a
11
+ * specific tool and are therefore always active.
12
+ */
13
+ export declare function isToolChoiceActive(toolChoice: ToolChoice | undefined, tools: readonly {
14
+ name: string;
15
+ }[]): boolean;
@@ -1,4 +1,5 @@
1
- export type ToolName = "sd" | "sg" | "yt-dlp" | "trafilatura";
1
+ export declare function ffmpegAssetName(_version: string, plat: string, architecture: string): string | null;
2
+ export type ToolName = "sd" | "sg" | "yt-dlp" | "trafilatura" | "ffmpeg";
2
3
  export declare function getToolPath(tool: ToolName): string | null;
3
4
  type EnsureToolOptions = {
4
5
  signal?: AbortSignal;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import type { SpecialHandler } from "./types";
2
2
  interface GitHubUrl {
3
- type: "blob" | "tree" | "repo" | "issue" | "issues" | "pull" | "pulls" | "discussion" | "discussions" | "actions-run" | "actions-job" | "other";
3
+ type: "blob" | "tree" | "repo" | "commit" | "issue" | "issues" | "pull" | "pulls" | "discussion" | "discussions" | "actions-run" | "actions-job" | "other";
4
4
  owner: string;
5
5
  repo: string;
6
6
  ref?: string;
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.12.4",
4
+ "version": "15.13.0",
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 --parallel",
38
+ "test": "bun test --parallel=2",
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,17 +47,17 @@
47
47
  "@agentclientprotocol/sdk": "0.25.0",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.12.4",
51
- "@oh-my-pi/omp-stats": "15.12.4",
52
- "@oh-my-pi/pi-agent-core": "15.12.4",
53
- "@oh-my-pi/pi-ai": "15.12.4",
54
- "@oh-my-pi/pi-catalog": "15.12.4",
55
- "@oh-my-pi/pi-mnemopi": "15.12.4",
56
- "@oh-my-pi/pi-natives": "15.12.4",
57
- "@oh-my-pi/pi-tui": "15.12.4",
58
- "@oh-my-pi/pi-utils": "15.12.4",
59
- "@oh-my-pi/pi-wire": "15.12.4",
60
- "@oh-my-pi/snapcompact": "15.12.4",
50
+ "@oh-my-pi/hashline": "15.13.0",
51
+ "@oh-my-pi/omp-stats": "15.13.0",
52
+ "@oh-my-pi/pi-agent-core": "15.13.0",
53
+ "@oh-my-pi/pi-ai": "15.13.0",
54
+ "@oh-my-pi/pi-catalog": "15.13.0",
55
+ "@oh-my-pi/pi-mnemopi": "15.13.0",
56
+ "@oh-my-pi/pi-natives": "15.13.0",
57
+ "@oh-my-pi/pi-tui": "15.13.0",
58
+ "@oh-my-pi/pi-utils": "15.13.0",
59
+ "@oh-my-pi/pi-wire": "15.13.0",
60
+ "@oh-my-pi/snapcompact": "15.13.0",
61
61
  "@opentelemetry/api": "^1.9.1",
62
62
  "@opentelemetry/context-async-hooks": "^2.7.1",
63
63
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -80,7 +80,8 @@
80
80
  "zod": "^4"
81
81
  },
82
82
  "optionalDependencies": {
83
- "@huggingface/transformers": "^4.2.0"
83
+ "@huggingface/transformers": "^4.2.0",
84
+ "sherpa-onnx-node": "1.13.2"
84
85
  },
85
86
  "devDependencies": {
86
87
  "@types/bun": "^1.3.14"
@@ -6,6 +6,27 @@ const DELIVERY_RETRY_JITTER_MS = 200;
6
6
  const DEFAULT_RETENTION_MS = 5 * 60 * 1000;
7
7
  const DEFAULT_MAX_RUNNING_JOBS = 15;
8
8
 
9
+ /**
10
+ * Adaptive ("smart") `job` poll-wait ladder (ms). A tight poll loop climbs
11
+ * these rungs so each immediate re-poll backs off and stops spending turns on
12
+ * "still running" frames; the floor (first rung) is the shortest wait and the
13
+ * top rung is the longest a smart poll will ever block. Only used when
14
+ * `async.pollWaitDuration` is set to `smart`; fixed durations wait verbatim.
15
+ */
16
+ const POLL_WAIT_LADDER_MS = [5_000, 10_000, 30_000, 60_000, 300_000] as const;
17
+ /**
18
+ * Going at least this long between poll calls means the agent stepped out of
19
+ * the poll loop to do real work — the next poll drops back to the ladder floor.
20
+ */
21
+ const POLL_ESCALATION_RESET_MS = 60_000;
22
+
23
+ interface PollEscalationState {
24
+ /** Index into POLL_WAIT_LADDER_MS used for the most recent poll wait. */
25
+ level: number;
26
+ /** Timestamp (ms) when the most recent poll wait returned. */
27
+ lastPollEndAt: number;
28
+ }
29
+
9
30
  export interface AsyncJob {
10
31
  id: string;
11
32
  type: "bash" | "task";
@@ -96,6 +117,7 @@ export class AsyncJobManager {
96
117
  readonly #suppressedDeliveries = new Set<string>();
97
118
  readonly #watchedJobs = new Set<string>();
98
119
  readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
120
+ readonly #pollEscalation = new Map<string | undefined, PollEscalationState>();
99
121
  readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
100
122
  readonly #maxRunningJobs: number;
101
123
  readonly #retentionMs: number;
@@ -295,6 +317,32 @@ export class AsyncJobManager {
295
317
  return removed;
296
318
  }
297
319
 
320
+ /**
321
+ * Compute the next adaptive ("smart") wait (ms) for a blocking `job` poll by
322
+ * the given owner. Consecutive polls — those starting within
323
+ * POLL_ESCALATION_RESET_MS of the previous poll returning — climb
324
+ * POLL_WAIT_LADDER_MS so a tight wait loop backs off; a longer gap means the
325
+ * agent left to do real work, so the wait resets to the floor. Pair each call
326
+ * with `recordPollWaitEnd()` once the wait returns.
327
+ */
328
+ nextPollWaitMs(ownerId: string | undefined, now: number = Date.now()): number {
329
+ const prev = this.#pollEscalation.get(ownerId);
330
+ const reset = !prev || now - prev.lastPollEndAt >= POLL_ESCALATION_RESET_MS;
331
+ const level = reset ? 0 : Math.min(prev.level + 1, POLL_WAIT_LADDER_MS.length - 1);
332
+ this.#pollEscalation.set(ownerId, { level, lastPollEndAt: prev?.lastPollEndAt ?? now });
333
+ return POLL_WAIT_LADDER_MS[level];
334
+ }
335
+
336
+ /**
337
+ * Mark a blocking poll wait as finished so the idle-reset window is measured
338
+ * from now. Polling again before POLL_ESCALATION_RESET_MS elapses keeps
339
+ * climbing the ladder; waiting longer resets it to the floor.
340
+ */
341
+ recordPollWaitEnd(ownerId: string | undefined, now: number = Date.now()): void {
342
+ const prev = this.#pollEscalation.get(ownerId);
343
+ this.#pollEscalation.set(ownerId, { level: prev?.level ?? 0, lastPollEndAt: now });
344
+ }
345
+
298
346
  acknowledgeDeliveries(jobIds: string[]): number {
299
347
  const uniqueJobIds = Array.from(new Set(jobIds.map(id => id.trim()).filter(id => id.length > 0)));
300
348
  if (uniqueJobIds.length === 0) return 0;
@@ -405,6 +453,7 @@ export class AsyncJobManager {
405
453
  this.#inFlightDeliveries.length = 0;
406
454
  this.#suppressedDeliveries.clear();
407
455
  this.#watchedJobs.clear();
456
+ this.#pollEscalation.clear();
408
457
  return drained;
409
458
  }
410
459
 
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Auto-learn session controller (experimental).
3
+ *
4
+ * Subscribes to the session event stream and, after a substantive turn,
5
+ * nudges the agent to capture reusable lessons. Default posture is passive
6
+ * (a hidden reminder rides the next real turn); with `autolearn.autoContinue`
7
+ * it auto-runs exactly one synthetic capture turn at stop.
8
+ *
9
+ * Installed once per top-level session (taskDepth 0). The subscription lives
10
+ * for the session's lifetime — `newSession` resets the session in place
11
+ * without re-running startup — so the controller needs no disposal.
12
+ */
13
+ import { logger } from "@oh-my-pi/pi-utils";
14
+ import type { Settings } from "../config/settings";
15
+ import autolearnGuidance from "../prompts/system/autolearn-guidance.md" with { type: "text" };
16
+ import autolearnGuidanceLearn from "../prompts/system/autolearn-guidance-learn.md" with { type: "text" };
17
+ import autolearnNudge from "../prompts/system/autolearn-nudge.md" with { type: "text" };
18
+ import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
19
+
20
+ const AUTOLEARN_NUDGE = autolearnNudge.trim();
21
+ const DEFAULT_MIN_TOOL_CALLS = 5;
22
+
23
+ /**
24
+ * Build the standing auto-learn guidance for the system prompt from the tools
25
+ * actually present in the active set, or null when `manage_skill` is absent.
26
+ *
27
+ * Driven by tool presence rather than live settings: the `learn`/`manage_skill`
28
+ * registry is built ONCE at session start (and only for top-level sessions), so
29
+ * keying the guidance on `autolearn.enabled` would let a mid-session enable — or
30
+ * a subagent that filtered the tools out — inject guidance pointing at tools the
31
+ * session never built. The `learn` addendum is included only when the `learn`
32
+ * tool is present (it requires a memory backend).
33
+ */
34
+ export function buildAutoLearnInstructions(available: { manageSkill: boolean; learn: boolean }): string | null {
35
+ if (!available.manageSkill) return null;
36
+ const parts = [autolearnGuidance.trim()];
37
+ if (available.learn) parts.push(autolearnGuidanceLearn.trim());
38
+ return parts.join("\n\n");
39
+ }
40
+
41
+ export interface AutoLearnControllerOptions {
42
+ session: AgentSession;
43
+ settings: Settings;
44
+ }
45
+
46
+ export class AutoLearnController {
47
+ readonly #session: AgentSession;
48
+ readonly #settings: Settings;
49
+ #toolCalls = 0;
50
+ /**
51
+ * Whether the in-flight turn BEGAN while goal mode was active. Captured at
52
+ * agent_start because a `goal` tool can complete or drop the goal mid-turn,
53
+ * clearing the live flag before agent_end — so the end-of-turn state alone
54
+ * would let a goal-continuation turn slip through and get nudged.
55
+ */
56
+ #turnStartedInGoalMode = false;
57
+ /** Swallow the agent_end produced by an auto-run capture turn so it cannot re-trigger. */
58
+ #suppressNext = false;
59
+
60
+ constructor(options: AutoLearnControllerOptions) {
61
+ this.#session = options.session;
62
+ this.#settings = options.settings;
63
+ // The listener closure captures `this`, so the session's listener array
64
+ // keeps the controller alive — no stored unsubscribe needed.
65
+ this.#session.subscribe(event => this.#onEvent(event));
66
+ }
67
+
68
+ #onEvent(event: AgentSessionEvent): void {
69
+ if (event.type === "agent_start") {
70
+ // Capture goal-mode state at the turn boundary, before any tool runs.
71
+ this.#turnStartedInGoalMode = this.#session.getGoalModeState()?.enabled === true;
72
+ return;
73
+ }
74
+ if (event.type === "tool_execution_end") {
75
+ this.#toolCalls++;
76
+ return;
77
+ }
78
+ if (event.type === "agent_end") {
79
+ this.#onAgentEnd();
80
+ }
81
+ }
82
+
83
+ #onAgentEnd(): void {
84
+ // Snapshot and reset every turn: the counter describes only the
85
+ // just-finished turn, so below-threshold, disabled, and plan-mode stops
86
+ // must not let tool calls accumulate into a later turn.
87
+ const toolCalls = this.#toolCalls;
88
+ this.#toolCalls = 0;
89
+ // Snapshot the turn-start goal flag alongside the counter so a turn that
90
+ // observed no agent_start can never inherit a stale value.
91
+ const startedInGoalMode = this.#turnStartedInGoalMode;
92
+ this.#turnStartedInGoalMode = false;
93
+
94
+ if (this.#suppressNext) {
95
+ this.#suppressNext = false;
96
+ return;
97
+ }
98
+ // Honor a live opt-out: the subscription outlives the setting, so re-check
99
+ // the current flag rather than trusting install-time state.
100
+ if (!this.#settings.get("autolearn.enabled")) return;
101
+ const minToolCalls = this.#settings.get("autolearn.minToolCalls") ?? DEFAULT_MIN_TOOL_CALLS;
102
+ if (toolCalls < minToolCalls) return;
103
+ // Never interrupt plan-mode review.
104
+ if (this.#session.getPlanModeState()?.enabled) return;
105
+ // Never divert a goal loop. Skip when the turn STARTED in goal mode — a
106
+ // `goal` tool may have completed/dropped the goal before this stop — or is
107
+ // still in it: a passive nudge would ride the goal continuation, and
108
+ // auto-continue would compete with it.
109
+ if (startedInGoalMode || this.#session.getGoalModeState()?.enabled) return;
110
+
111
+ // Auto-run a capture turn only when explicitly enabled; otherwise the
112
+ // hidden reminder rides the next real turn passively.
113
+ const autoContinue = this.#settings.get("autolearn.autoContinue") === true;
114
+ // Arm suppression synchronously: the synthetic capture turn's agent_end
115
+ // fires inside sendCustomMessage (before it resolves), so the flag must be
116
+ // set before then. Disarm when no turn actually started — a deferred/queued
117
+ // dispatch or a failed send produces no agent_end, and a latched flag would
118
+ // otherwise swallow the next real stop.
119
+ if (autoContinue) this.#suppressNext = true;
120
+
121
+ this.#session
122
+ .sendCustomMessage(
123
+ {
124
+ customType: "autolearn-nudge",
125
+ content: AUTOLEARN_NUDGE,
126
+ display: false,
127
+ attribution: "user",
128
+ },
129
+ { deliverAs: "nextTurn", triggerTurn: autoContinue },
130
+ )
131
+ .then(started => {
132
+ if (!started) this.#suppressNext = false;
133
+ })
134
+ .catch(err => {
135
+ this.#suppressNext = false;
136
+ logger.warn("auto-learn nudge delivery failed", { err });
137
+ });
138
+ }
139
+ }