mixdog 0.8.1 → 0.9.1

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 (299) hide show
  1. package/README.md +47 -23
  2. package/package.json +34 -28
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +205 -33
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1222 -90
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -29
  62. package/src/lib/plugin-paths.cjs +1 -7
  63. package/src/lib/rules-builder.cjs +242 -98
  64. package/src/lib/text-utils.cjs +1 -1
  65. package/src/mixdog-session-runtime.mjs +2211 -446
  66. package/src/output-styles/default.md +12 -28
  67. package/src/output-styles/extreme-simple.md +9 -6
  68. package/src/output-styles/simple.md +22 -9
  69. package/src/repl.mjs +108 -59
  70. package/src/rules/agent/00-common.md +15 -0
  71. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  72. package/src/rules/agent/30-explorer.md +22 -0
  73. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  75. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  76. package/src/rules/lead/01-general.md +9 -5
  77. package/src/rules/lead/04-workflow.md +51 -12
  78. package/src/rules/lead/lead-tool.md +6 -0
  79. package/src/rules/shared/01-tool.md +12 -1
  80. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  81. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  85. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  86. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  87. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  88. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  89. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  90. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  92. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  93. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  94. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  95. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
  97. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  98. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  100. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  101. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  102. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  107. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
  110. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  111. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  112. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
  113. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  114. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  115. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  116. package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
  117. package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
  118. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  119. package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
  120. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  121. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  122. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  123. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
  124. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
  125. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
  126. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  127. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  130. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  133. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  138. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  139. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  143. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  146. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  147. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  148. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  149. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  150. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  151. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  152. package/src/runtime/channels/index.mjs +149 -51
  153. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  154. package/src/runtime/channels/lib/config.mjs +0 -1
  155. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  156. package/src/runtime/channels/lib/executor.mjs +0 -3
  157. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  158. package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
  159. package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
  160. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  161. package/src/runtime/channels/lib/session-discovery.mjs +3 -6
  162. package/src/runtime/channels/lib/tool-format.mjs +0 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
  164. package/src/runtime/channels/lib/webhook.mjs +1 -1
  165. package/src/runtime/channels/tool-defs.mjs +29 -29
  166. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  167. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  168. package/src/runtime/memory/index.mjs +519 -118
  169. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  170. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  171. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  172. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  173. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  174. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  175. package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
  176. package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
  177. package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
  178. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
  179. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  180. package/src/runtime/memory/lib/memory.mjs +88 -9
  181. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  182. package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
  183. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  184. package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
  185. package/src/runtime/memory/lib/session-ingest.mjs +196 -0
  186. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  187. package/src/runtime/memory/tool-defs.mjs +46 -37
  188. package/src/runtime/search/index.mjs +104 -473
  189. package/src/runtime/search/lib/config.mjs +0 -4
  190. package/src/runtime/search/lib/state.mjs +1 -15
  191. package/src/runtime/search/lib/web-tools.mjs +44 -25
  192. package/src/runtime/search/tool-defs.mjs +16 -23
  193. package/src/runtime/shared/abort-controller.mjs +1 -1
  194. package/src/runtime/shared/atomic-file.mjs +4 -3
  195. package/src/runtime/shared/background-tasks.mjs +122 -11
  196. package/src/runtime/shared/child-spawn-gate.mjs +139 -0
  197. package/src/runtime/shared/config.mjs +7 -4
  198. package/src/runtime/shared/err-text.mjs +131 -4
  199. package/src/runtime/shared/llm/cost.mjs +2 -2
  200. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  201. package/src/runtime/shared/llm/index.mjs +34 -11
  202. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  203. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  204. package/src/runtime/shared/singleton-owner.mjs +104 -0
  205. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  206. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  207. package/src/runtime/shared/tool-surface.mjs +624 -95
  208. package/src/runtime/shared/user-data-guard.mjs +0 -2
  209. package/src/standalone/agent-task-status.mjs +203 -0
  210. package/src/standalone/agent-task-status.test.mjs +76 -0
  211. package/src/standalone/agent-tool.mjs +1913 -0
  212. package/src/standalone/channel-worker.mjs +370 -14
  213. package/src/standalone/explore-tool.mjs +165 -70
  214. package/src/standalone/folder-dialog.mjs +314 -0
  215. package/src/standalone/hook-bus.mjs +898 -22
  216. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  217. package/src/standalone/projects.mjs +226 -0
  218. package/src/standalone/provider-admin.mjs +41 -24
  219. package/src/standalone/seeds.mjs +1 -78
  220. package/src/standalone/usage-dashboard.mjs +96 -8
  221. package/src/tui/App.jsx +4800 -2140
  222. package/src/tui/components/AnsiText.jsx +39 -28
  223. package/src/tui/components/ContextPanel.jsx +14 -4
  224. package/src/tui/components/Markdown.jsx +43 -77
  225. package/src/tui/components/MarkdownTable.jsx +9 -184
  226. package/src/tui/components/Message.jsx +28 -11
  227. package/src/tui/components/Picker.jsx +95 -56
  228. package/src/tui/components/PromptInput.jsx +428 -239
  229. package/src/tui/components/QueuedCommands.jsx +1 -1
  230. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  231. package/src/tui/components/Spinner.jsx +67 -38
  232. package/src/tui/components/StatusLine.jsx +606 -38
  233. package/src/tui/components/TextEntryPanel.jsx +128 -9
  234. package/src/tui/components/ToolExecution.jsx +597 -362
  235. package/src/tui/components/TurnDone.jsx +3 -3
  236. package/src/tui/components/UsagePanel.jsx +3 -5
  237. package/src/tui/components/tool-output-format.mjs +499 -0
  238. package/src/tui/components/tool-output-format.test.mjs +312 -0
  239. package/src/tui/dist/index.mjs +9192 -2399
  240. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  241. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  242. package/src/tui/engine.mjs +1455 -279
  243. package/src/tui/figures.mjs +21 -40
  244. package/src/tui/index.jsx +75 -31
  245. package/src/tui/input-editing.mjs +25 -0
  246. package/src/tui/markdown/format-token.mjs +671 -69
  247. package/src/tui/markdown/format-token.test.mjs +312 -0
  248. package/src/tui/markdown/render-ansi.mjs +94 -0
  249. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  250. package/src/tui/markdown/stream-fence.mjs +34 -0
  251. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  252. package/src/tui/markdown/table-layout.mjs +250 -0
  253. package/src/tui/paste-attachments.mjs +0 -7
  254. package/src/tui/spinner-verbs.mjs +1 -2
  255. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  256. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  257. package/src/tui/theme.mjs +756 -24
  258. package/src/tui/time-format.mjs +1 -1
  259. package/src/tui/transcript-tool-failures.mjs +67 -0
  260. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  261. package/src/ui/ansi.mjs +1 -2
  262. package/src/ui/markdown.mjs +85 -26
  263. package/src/ui/markdown.test.mjs +70 -0
  264. package/src/ui/model-display.mjs +121 -0
  265. package/src/ui/session-stats.mjs +44 -0
  266. package/src/ui/statusline-context-label.test.mjs +15 -0
  267. package/src/ui/statusline.mjs +386 -178
  268. package/src/ui/tool-card.mjs +2 -13
  269. package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
  270. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  271. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  272. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  273. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  274. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  275. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  276. package/src/workflows/default/WORKFLOW.md +12 -5
  277. package/src/workflows/default/workflow.json +0 -1
  278. package/src/workflows/solo/WORKFLOW.md +15 -0
  279. package/src/workflows/solo/workflow.json +7 -0
  280. package/vendor/ink/build/ink.js +54 -8
  281. package/vendor/ink/build/output.js +6 -1
  282. package/src/agents/scheduler-task.md +0 -3
  283. package/src/agents/web-researcher/AGENT.md +0 -3
  284. package/src/agents/web-researcher/agent.json +0 -6
  285. package/src/agents/webhook-handler.md +0 -3
  286. package/src/hooks/lib/permission-rules.cjs +0 -170
  287. package/src/hooks/lib/settings-loader.cjs +0 -112
  288. package/src/lib/hook-pipe-path.cjs +0 -10
  289. package/src/rules/bridge/00-common.md +0 -5
  290. package/src/rules/bridge/30-explorer.md +0 -4
  291. package/src/rules/lead/00-tool-lead.md +0 -5
  292. package/src/rules/shared/00-language.md +0 -3
  293. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  294. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  295. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  296. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  297. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  298. package/src/standalone/bridge-tool.mjs +0 -1414
  299. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -181,6 +181,11 @@ export default class Ink {
181
181
  // cells ({ x1, y1, x2, y2 } inclusive, normalized) or null. The App sets it
182
182
  // via setSelection(); onRender forwards it to the renderer for highlighting.
183
183
  selectionRect = null;
184
+ // [mixdog fork] Coalesce drag-selection repaints when a frame is pending.
185
+ isRendering = false;
186
+ selectionRepaintQueued = false;
187
+ selectionRepaintFlushPending = false;
188
+ selectionRepaintEpoch = 0;
184
189
  // [mixdog fork] text under the current selection rect, refreshed every render
185
190
  // from the output grid. Read back via getSelectionText() on drag-release.
186
191
  selectedText = null;
@@ -318,9 +323,9 @@ export default class Ink {
318
323
  this.log.setCursorPosition(position);
319
324
  };
320
325
  // [mixdog fork] Update the mouse drag-selection rectangle and repaint so the
321
- // inverse highlight tracks the drag live. Called by the App's mouse handler.
326
+ // inverse highlight tracks the drag. Called by the App's mouse handler.
322
327
  // A no-op-equal update is skipped to avoid redundant frames during motion.
323
- setSelection = (rect) => {
328
+ setSelection = (rect, options = {}) => {
324
329
  const a = this.selectionRect;
325
330
  const same = a === rect ||
326
331
  (a && rect &&
@@ -333,16 +338,47 @@ export default class Ink {
333
338
  a.clipY2 === rect.clipY2 &&
334
339
  a.captureText === rect.captureText);
335
340
  if (same) {
336
- return;
341
+ if (!options.immediate) {
342
+ return;
343
+ }
344
+ }
345
+ else {
346
+ this.selectionRect = rect ?? null;
337
347
  }
338
- this.selectionRect = rect ?? null;
339
348
  if (!this.isUnmounted) {
340
- // Drag selection is a direct manipulation gesture. Route it through
341
- // the unthrottled renderer so the highlight tracks the pointer
342
- // instead of trailing behind at maxFps.
343
- this.rootNode.onImmediateRender();
349
+ if (options.immediate) {
350
+ this.selectionRepaintEpoch++;
351
+ this.selectionRepaintFlushPending = false;
352
+ this.rootNode.onImmediateRender();
353
+ return;
354
+ }
355
+ this.scheduleSelectionRepaint();
344
356
  }
345
357
  };
358
+ scheduleSelectionRepaint = () => {
359
+ if (this.hasPendingThrottledRender) {
360
+ return;
361
+ }
362
+ if (this.isRendering) {
363
+ this.selectionRepaintQueued = true;
364
+ return;
365
+ }
366
+ if (this.selectionRepaintFlushPending) {
367
+ return;
368
+ }
369
+ this.selectionRepaintFlushPending = true;
370
+ const epoch = ++this.selectionRepaintEpoch;
371
+ queueMicrotask(() => {
372
+ this.selectionRepaintFlushPending = false;
373
+ if (epoch !== this.selectionRepaintEpoch || this.isUnmounted) {
374
+ return;
375
+ }
376
+ if (this.hasPendingThrottledRender) {
377
+ return;
378
+ }
379
+ this.rootNode.onImmediateRender();
380
+ });
381
+ };
346
382
  // [mixdog fork] Given a 0-based cell (x, y), return the inclusive rect of the
347
383
  // word (maximal run of non-whitespace cells) on that single row, or null if
348
384
  // the cell is whitespace/empty or out of range. Reuses the cached cell-value
@@ -401,6 +437,8 @@ export default class Ink {
401
437
  this.nextRenderCommit.resolve();
402
438
  this.nextRenderCommit = undefined;
403
439
  }
440
+ this.isRendering = true;
441
+ try {
404
442
  const startTime = performance.now();
405
443
  const { output, outputHeight, staticOutput, cursor, selectedText, plainRows } = render(this.rootNode, this.isScreenReaderEnabled, this.selectionRect);
406
444
  // [mixdog fork] Cache the text under the current selection rect so the App
@@ -488,6 +526,14 @@ export default class Ink {
488
526
  this.fullStaticOutput += staticOutput;
489
527
  }
490
528
  this.renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : '');
529
+ }
530
+ finally {
531
+ this.isRendering = false;
532
+ if (this.selectionRepaintQueued) {
533
+ this.selectionRepaintQueued = false;
534
+ this.scheduleSelectionRepaint();
535
+ }
536
+ }
491
537
  };
492
538
  render(node) {
493
539
  const tree = (React.createElement(AccessibilityContext.Provider, { value: { isScreenReaderEnabled: this.isScreenReaderEnabled } },
@@ -308,7 +308,12 @@ export default class Output {
308
308
  // [mixdog fork] Snapshot per-row, column-indexed cell values so the App
309
309
  // can compute word boundaries (double-click select) without retaining
310
310
  // this Output instance, which is created fresh per render and discarded.
311
- const plainRows = sel ? undefined : output.map((row) => (row || []).map((cell) => (cell?.value ?? '')));
311
+ // ALWAYS build this, even while a selection rect is active: gating it on
312
+ // `!sel` froze the snapshot at the frame the first selection appeared, so
313
+ // every later double-click read STALE cell rows (wrong/empty words) until
314
+ // the selection cleared. Applying the selection above only rewrites cell
315
+ // `styles`, never `value`, so reading `value` here stays correct.
316
+ const plainRows = output.map((row) => (row || []).map((cell) => (cell?.value ?? '')));
312
317
  const generatedOutput = output
313
318
  .map(line => {
314
319
  // See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
@@ -1,3 +0,0 @@
1
- # Scheduler Task
2
-
3
- Scheduled task execution agent.
@@ -1,3 +0,0 @@
1
- # Web Researcher
2
-
3
- External web and documentation research agent.
@@ -1,6 +0,0 @@
1
- {
2
- "id": "web-researcher",
3
- "name": "Web Researcher",
4
- "description": "Web, documentation, and external reference research.",
5
- "entry": "AGENT.md"
6
- }
@@ -1,3 +0,0 @@
1
- # Webhook Handler
2
-
3
- Inbound webhook event analysis agent.
@@ -1,170 +0,0 @@
1
- 'use strict';
2
- /**
3
- * permission-rules.cjs
4
- * Pattern matcher and priority evaluator. Priority order and Tool(content)
5
- * parsing mirror Claude Code native logic; specifier matching uses a local
6
- * path/command glob (native delegates per-tool content matching to each tool).
7
- *
8
- * Priority (highest → lowest): deny > ask > allow > mode default
9
- *
10
- * Pattern forms:
11
- * mcp__* → toolName starts with "mcp__"
12
- * ToolName → exact toolName match (no parens)
13
- * ToolName(*) → toolName matches AND specifier glob matches any
14
- * ToolName(specifier) → toolName matches AND specifier glob matches
15
- * toolInput.path / .command / .file
16
- * Glob chars: * (any chars within segment), ** (any segments)
17
- *
18
- * No external deps; no eval/Function.
19
- */
20
-
21
- // Read-only tool set (mixdog tool names).
22
- const READ_ONLY_TOOLS = new Set([
23
- 'read', 'list', 'glob', 'grep', 'find_symbol', 'find_references', 'find_callers', 'explore',
24
- 'recall', 'search', 'fetch', 'web_fetch',
25
- 'schedule_status', 'list_models', 'job_wait', 'download_attachment',
26
- ]);
27
-
28
- /**
29
- * Find the index of the first/last UNescaped occurrence of a char.
30
- * A char is escaped when preceded by an odd number of backslashes.
31
- * Mirrors native permissionRuleParser.findFirst/LastUnescapedChar.
32
- */
33
- function findFirstUnescapedChar(str, char) {
34
- for (let i = 0; i < str.length; i++) {
35
- if (str[i] !== char) continue;
36
- let bs = 0, j = i - 1;
37
- while (j >= 0 && str[j] === '\\') { bs++; j--; }
38
- if (bs % 2 === 0) return i;
39
- }
40
- return -1;
41
- }
42
-
43
- function findLastUnescapedChar(str, char) {
44
- for (let i = str.length - 1; i >= 0; i--) {
45
- if (str[i] !== char) continue;
46
- let bs = 0, j = i - 1;
47
- while (j >= 0 && str[j] === '\\') { bs++; j--; }
48
- if (bs % 2 === 0) return i;
49
- }
50
- return -1;
51
- }
52
-
53
- /** Reverse escapeRuleContent: \( -> (, \) -> ), \\ -> \ (order matters). */
54
- function unescapeRuleContent(content) {
55
- return content
56
- .replace(/\\\(/g, '(')
57
- .replace(/\\\)/g, ')')
58
- .replace(/\\\\/g, '\\');
59
- }
60
-
61
- /**
62
- * Convert a glob pattern string to a RegExp.
63
- * Supports: * (non-slash wildcard), ** (any), ? (single char).
64
- * Safe: no eval.
65
- */
66
- function globToRegex(glob) {
67
- let src = '';
68
- let i = 0;
69
- while (i < glob.length) {
70
- const ch = glob[i];
71
- if (ch === '*' && glob[i + 1] === '*') {
72
- src += '.*';
73
- i += 2;
74
- if (glob[i] === '/') i++; // consume trailing slash
75
- } else if (ch === '*') {
76
- src += '[^/\\\\]*';
77
- i++;
78
- } else if (ch === '?') {
79
- src += '[^/\\\\]';
80
- i++;
81
- } else {
82
- // Escape regex metacharacters
83
- src += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
84
- i++;
85
- }
86
- }
87
- return new RegExp('^' + src + '$', 'i');
88
- }
89
-
90
- /**
91
- * Match a single pattern entry against (toolName, toolInput).
92
- * Returns true if the pattern applies.
93
- */
94
- function matchesPattern(pattern, toolName, toolInput) {
95
- if (typeof pattern !== 'string') return false;
96
-
97
- // Detect Tool(specifier) form. Escape-aware, mirroring native
98
- // permissionRuleValueFromString: only UNescaped parens delimit the
99
- // specifier, and the close paren must be the final char.
100
- const parenIdx = findFirstUnescapedChar(pattern, '(');
101
- const closeIdx = findLastUnescapedChar(pattern, ')');
102
- if (parenIdx > 0 && closeIdx === pattern.length - 1 && closeIdx > parenIdx) {
103
- const namePart = pattern.slice(0, parenIdx);
104
- const rawSpecifier = pattern.slice(parenIdx + 1, closeIdx);
105
-
106
- if (toolName !== namePart) return false;
107
-
108
- // Empty "()" or wildcard "(*)" specifier → tool-wide rule, match any
109
- if (rawSpecifier === '' || rawSpecifier === '*') return true;
110
-
111
- // Unescape \( \) before glob matching
112
- const specifier = unescapeRuleContent(rawSpecifier);
113
- // Match specifier against known path-bearing fields
114
- const specRe = globToRegex(specifier);
115
- const inp = toolInput || {};
116
- const candidates = [...new Set([
117
- inp.path, inp.command, inp.file, inp.file_path, inp.base_path, inp.cwd,
118
- ...(Array.isArray(inp.path) ? inp.path.map(p => (p && typeof p === 'object' ? p.path : p)) : []),
119
- ...(Array.isArray(inp.reads) ? inp.reads.map(r => r?.path) : []),
120
- ...(Array.isArray(inp.edits) ? inp.edits.flatMap(e => [e?.path, e?.file_path]) : []),
121
- ...(Array.isArray(inp.writes) ? inp.writes.flatMap(w => [w?.path, w?.file_path]) : []),
122
- ].filter(v => typeof v === 'string'))];
123
-
124
- return candidates.some(c => specRe.test(c));
125
- }
126
-
127
- // Prefix wildcard: e.g. "mcp__*"
128
- if (pattern.endsWith('*')) {
129
- const prefix = pattern.slice(0, -1);
130
- return toolName.startsWith(prefix);
131
- }
132
-
133
- // Glob on toolName itself (may contain * or **)
134
- if (pattern.includes('*') || pattern.includes('?')) {
135
- return globToRegex(pattern).test(toolName);
136
- }
137
-
138
- // Exact match
139
- return toolName === pattern;
140
- }
141
-
142
- /**
143
- * Evaluate priority rules against a tool call.
144
- *
145
- * @param {string} toolName
146
- * @param {object} toolInput
147
- * @param {string[]} allowList
148
- * @param {string[]} denyList
149
- * @param {string[]} askList
150
- * @returns {'deny'|'ask'|'allow'|null} null = no list matched (use mode default)
151
- */
152
- function evaluateRules(toolName, toolInput, allowList, denyList, askList) {
153
- if (denyList.some(p => matchesPattern(p, toolName, toolInput))) return 'deny';
154
- if (askList.some(p => matchesPattern(p, toolName, toolInput))) return 'ask';
155
- if (allowList.some(p => matchesPattern(p, toolName, toolInput))) return 'allow';
156
- return null;
157
- }
158
-
159
- /**
160
- * Determine whether a tool is considered read-only.
161
- * For mcp__ tools: strip the mcp__ prefix chain and check the short tool name.
162
- */
163
- function isReadOnlyTool(toolName) {
164
- // Strip mcp__plugin_...__ prefix (one or more double-underscore segments)
165
- const parts = toolName.split('__');
166
- const shortName = parts[parts.length - 1];
167
- return READ_ONLY_TOOLS.has(shortName);
168
- }
169
-
170
- module.exports = { matchesPattern, evaluateRules, isReadOnlyTool, READ_ONLY_TOOLS };
@@ -1,112 +0,0 @@
1
- 'use strict';
2
- /**
3
- * settings-loader.cjs
4
- * Loads and merges Mixdog settings from three tiers (lowest → highest):
5
- * 1. User global: $MIXDOG_CONFIG_DIR/settings.json (or ~/.mixdog/settings.json)
6
- * 2. Project: $MIXDOG_PROJECT_DIR/.mixdog/settings.json (or cwd/.mixdog/settings.json)
7
- * 3. Project local: $MIXDOG_PROJECT_DIR/.mixdog/settings.local.json
8
- *
9
- * Only `permissions` sub-tree is returned; the rest is ignored.
10
- * Pure fs/path — no external deps.
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
- const os = require('os');
16
-
17
- // ── In-process mtime-driven cache ────────────────────────────────────────────
18
- // Keyed by resolved file path. Each entry: { mtime: number|null, data: any }.
19
- // Invalidated per-tier independently when mtime changes.
20
- // TTL: never-expire by default (mtime-driven only).
21
- const _fileCache = new Map();
22
-
23
- function readJsonCached(filePath) {
24
- let entry = _fileCache.get(filePath);
25
- let mtime = null;
26
- try { mtime = fs.statSync(filePath).mtimeMs; } catch { /* file absent */ }
27
- if (entry && entry.mtime === mtime) return entry.data;
28
- let data = null;
29
- if (mtime !== null) {
30
- try { data = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { data = null; }
31
- }
32
- _fileCache.set(filePath, { mtime, data });
33
- return data;
34
- }
35
-
36
- /** Clear the entire settings cache (for test isolation). */
37
- function clearSettingsCache() {
38
- _fileCache.clear();
39
- }
40
-
41
- /**
42
- * Merge two permissions objects (base then overlay).
43
- * Arrays are concatenated and de-duped; scalar fields are overwritten.
44
- */
45
- function mergePermissions(base, overlay) {
46
- if (!overlay) return base || {};
47
- if (!base) return overlay || {};
48
-
49
- const merged = Object.assign({}, base);
50
-
51
- for (const key of ['allow', 'deny', 'ask']) {
52
- const b = Array.isArray(base[key]) ? base[key] : [];
53
- const o = Array.isArray(overlay[key]) ? overlay[key] : [];
54
- const combined = [...b, ...o];
55
- if (combined.length) merged[key] = [...new Set(combined)];
56
- }
57
-
58
- // scalar: overlay wins
59
- if (overlay.defaultMode !== undefined) merged.defaultMode = overlay.defaultMode;
60
-
61
- return merged;
62
- }
63
-
64
- /**
65
- * Load and merge settings from all three tiers.
66
- * Returns `{ allow: string[], deny: string[], ask: string[], defaultMode: string }`.
67
- */
68
- function loadPermissions(projectDir) {
69
- const userConfigDir = process.env.MIXDOG_CONFIG_DIR ||
70
- path.join(os.homedir(), '.mixdog');
71
- const projDir = projectDir ||
72
- process.env.MIXDOG_PROJECT_DIR ||
73
- process.cwd();
74
-
75
- const userSettings = readJsonCached(path.join(userConfigDir, 'settings.json'));
76
- const projectSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.json'));
77
- const localSettings = readJsonCached(path.join(projDir, '.mixdog', 'settings.local.json'));
78
-
79
- // Accept permissions from `settings.permissions` (canonical) OR top-level
80
- // `allow`/`deny`/`ask`/`defaultMode` fields (common user shorthand).
81
- function extractPerms(s) {
82
- if (!s) return {};
83
- const nested = (s.permissions && typeof s.permissions === 'object') ? s.permissions : {};
84
- const topLevel = {};
85
- for (const key of ['allow', 'deny', 'ask']) {
86
- if (Array.isArray(s[key])) topLevel[key] = s[key];
87
- }
88
- if (typeof s.defaultMode === 'string') topLevel.defaultMode = s.defaultMode;
89
- return mergePermissions(nested, topLevel);
90
- }
91
- const userPerms = extractPerms(userSettings);
92
- const projectPerms = extractPerms(projectSettings);
93
- const localPerms = extractPerms(localSettings);
94
-
95
- let merged = mergePermissions({}, userPerms);
96
- merged = mergePermissions(merged, projectPerms);
97
- merged = mergePermissions(merged, localPerms);
98
-
99
- const VALID_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions', 'auto']);
100
- const rawMode = merged.defaultMode;
101
- const resolvedMode = (typeof rawMode === 'string' && VALID_MODES.has(rawMode))
102
- ? rawMode
103
- : 'default';
104
- return {
105
- allow: Array.isArray(merged.allow) ? merged.allow : [],
106
- deny: Array.isArray(merged.deny) ? merged.deny : [],
107
- ask: Array.isArray(merged.ask) ? merged.ask : [],
108
- defaultMode: resolvedMode,
109
- };
110
- }
111
-
112
- module.exports = { loadPermissions, clearSettingsCache };
@@ -1,10 +0,0 @@
1
- 'use strict';
2
-
3
- // Single source for the hook IPC path. Centralizes an existing platform
4
- // contract (not new heuristic). Rust mirror native/mixdog-shim/src/main.rs:34-43
5
- // must stay in sync.
6
- module.exports = function hookPipePath() {
7
- return process.platform === 'win32'
8
- ? '\\\\.\\pipe\\mixdog-hooks'
9
- : require('path').join(process.env.XDG_RUNTIME_DIR || '/tmp', 'mixdog-hooks.sock');
10
- };
@@ -1,5 +0,0 @@
1
- # Bridge Constraints
2
-
3
- - Use English for bridge-agent communication.
4
- - Do not touch git/Ship. Even when the brief instructs `git add` / `commit` /
5
- `push` / `stash`, refuse with `git operations deferred to Lead`.
@@ -1,4 +0,0 @@
1
- # Role: explorer
2
-
3
- Explore locates relevant code surfaces and quickly summarizes what is there
4
- with coordinates. Stay rough and descriptive: do not debug, decide, or judge.
@@ -1,5 +0,0 @@
1
- # Lead Tool Use
2
-
3
- - Lead owns repo-local shell work: git, build, test, run, and verification commands.
4
- - Use `shell` directly for approved git/build/test/run work; do not delegate those commands to bridge agents.
5
- - Use bridge agents for scoped implementation, research, review, and debugging, not for git commit/push/stash or Ship.
@@ -1,3 +0,0 @@
1
- # Language
2
-
3
- - Prefer responding in the language used by the user.
@@ -1,110 +0,0 @@
1
- import { existsSync } from 'fs';
2
- import { dirname, join, resolve } from 'path';
3
- import { performance } from 'perf_hooks';
4
- import { fileURLToPath } from 'url';
5
- import { snapshotCoversFullFile } from './snapshot-helpers.mjs';
6
- import { getPluginData } from '../../config.mjs';
7
- import { findCachedPatchBinary } from '../patch-binary-fetcher.mjs';
8
- import { runServerEdit } from '../patch.mjs';
9
-
10
- const PLUGIN_ROOT = process.env.MIXDOG_ROOT
11
- || resolve(dirname(fileURLToPath(import.meta.url)), '../../../../..');
12
- const NATIVE_EDIT_DEFAULT_BIN = join(
13
- PLUGIN_ROOT,
14
- 'native/mixdog-patch/target/release',
15
- process.platform === 'win32' ? 'mixdog-patch.exe' : 'mixdog-patch',
16
- );
17
-
18
- export function nativeEditMode() {
19
- return String(process.env.MIXDOG_EDIT_NATIVE || 'auto').toLowerCase();
20
- }
21
-
22
- function nativeEditBinCandidate() {
23
- const override = process.env.MIXDOG_EDIT_NATIVE_BIN || process.env.MIXDOG_PATCH_NATIVE_BIN;
24
- if (override) return { path: override, kind: 'override' };
25
- if (existsSync(NATIVE_EDIT_DEFAULT_BIN)) return { path: NATIVE_EDIT_DEFAULT_BIN, kind: 'local' };
26
- const cached = findCachedPatchBinary(getPluginData());
27
- if (cached) return { path: cached, kind: 'cached' };
28
- return { path: NATIVE_EDIT_DEFAULT_BIN, kind: 'missing' };
29
- }
30
-
31
- export function nativeEditBinPath() {
32
- return nativeEditBinCandidate().path;
33
- }
34
-
35
- export function nativeEditShouldAttempt({ editSnapshot, oldStr, newStr, preloadedContent, preloadedRawBuf }) {
36
- const mode = nativeEditMode();
37
- if (/^(0|false|no|off|js|legacy)$/i.test(mode)) return false;
38
- const forcedNative = /^(1|true|yes|on|native)$/i.test(mode);
39
- const candidate = nativeEditBinCandidate();
40
- if (!existsSync(candidate.path)) return false;
41
- // Cached release prebuilds are guaranteed valid for apply_patch, but older
42
- // manifests (currently v0.6.5 in clean CI) predate the EDIT server protocol.
43
- // In auto mode, native edit is only an acceleration, so require either a
44
- // local cargo build or an explicit override. If a user forces native mode,
45
- // still try the cached binary and surface any protocol failure.
46
- if (candidate.kind === 'cached' && !forcedNative) return false;
47
- if (!snapshotCoversFullFile(editSnapshot)) return false;
48
- if (preloadedContent !== null || preloadedRawBuf !== null) return false;
49
- if (typeof oldStr !== 'string' || oldStr.length === 0 || typeof newStr !== 'string') return false;
50
- if (forcedNative) return true;
51
- // auto: the persistent server removed per-call spawn cost, so route edits to
52
- // native edit2 by default (B3). Same-size edits keep the JS in-place partial
53
- // write, which rewrites bytes in place instead of the whole file.
54
- const oldBytes = Buffer.byteLength(oldStr, 'utf-8');
55
- const newBytes = Buffer.byteLength(newStr, 'utf-8');
56
- if (oldBytes === newBytes) return false;
57
- return true;
58
- }
59
-
60
- export async function runNativeExactEdit({ fullPath, oldStr, newStr, replaceAll, signal = null }) {
61
- const forcedNative = /^(1|true|yes|on|native)$/i.test(nativeEditMode());
62
- if (signal?.aborted) {
63
- return { ok: false, fallback: false, error: signal.reason?.message || signal.reason || 'native edit aborted' };
64
- }
65
- const oldBuf = Buffer.from(oldStr, 'utf-8');
66
- const newBuf = Buffer.from(newStr, 'utf-8');
67
- const started = performance.now();
68
- try {
69
- // PARITY GUARD: the native engine MATCHES via the curly-quote fold
70
- // tier but applies new_string verbatim, silently downgrading the
71
- // file's typographic quotes (JS slow path preserves them via
72
- // preserveQuoteTypography). When old_string carries quote-family
73
- // chars — the only inputs that can land on the curly tier — probe
74
- // with a dry run (persistent server, ~ms) and defer curly-tier
75
- // matches to the JS editor.
76
- if (/["'‘’“”]/.test(oldStr)) {
77
- const probe = await runServerEdit({ fullPath, oldBuf, newBuf, replaceAll, dryRun: true, signal });
78
- if (probe?.tier === 'curly') {
79
- return { ok: false, fallback: true, error: 'curly-quote fold match — deferred to JS editor for typography preservation' };
80
- }
81
- }
82
- const res = await runServerEdit({ fullPath, oldBuf, newBuf, replaceAll, signal });
83
- return {
84
- ok: true,
85
- replacements: res.replacements,
86
- readMs: res.readMs,
87
- applyMs: res.applyMs,
88
- writeMs: res.writeMs,
89
- totalMs: res.totalMs,
90
- roundtripMs: res.roundtripMs ?? (performance.now() - started),
91
- stage: res.tier,
92
- contentHash: res.contentHash,
93
- };
94
- } catch (err) {
95
- if (err?.name === 'AbortError') {
96
- return { ok: false, fallback: false, error: err.message };
97
- }
98
- const msg = String(err?.message || err);
99
- // Tier misses and not-found map to a JS fallback; transport/spawn errors
100
- // also fall back so a server hiccup never blocks an edit. Older cached
101
- // mixdog-patch binaries (for example the v0.6.5 release prebuilds used
102
- // by clean CI before a local cargo build exists) support APPLY but not
103
- // the EDIT server protocol, and answer EDIT with the APPLY parser's
104
- // "bad header" error. In auto mode that means "native edit unavailable",
105
- // not "the edit is invalid", so fall through to the JS editor. When the
106
- // user explicitly forces native mode, keep surfacing the native failure.
107
- const fallback = !forcedNative && /old_string (?:not found|found \d+ times)|not valid UTF-8|no exact match|not found|server|bad header|bad edit header/i.test(msg);
108
- return { ok: false, fallback, error: msg };
109
- }
110
- }
@@ -1,67 +0,0 @@
1
- import * as nodeBuffer from 'node:buffer';
2
- import { readFileSync } from 'node:fs';
3
- import { readFile as readFileAsync } from 'node:fs/promises';
4
- import { decodeRawBufferForSnapshotCheck } from './builtin/snapshot-helpers.mjs';
5
-
6
- function keyForPath(fullPath) {
7
- const text = String(fullPath || '');
8
- return process.platform === 'win32' ? text.toLowerCase() : text;
9
- }
10
-
11
- export function isValidUtf8Buffer(buf) {
12
- if (typeof nodeBuffer.isUtf8 === 'function') return nodeBuffer.isUtf8(buf);
13
- if (typeof Buffer.isUtf8 === 'function') return Buffer.isUtf8(buf);
14
- return Buffer.from(buf.toString('utf-8'), 'utf-8').equals(buf);
15
- }
16
-
17
- export function createMutationContentCache() {
18
- const entries = new Map();
19
-
20
- function getOrCreate(fullPath) {
21
- const key = keyForPath(fullPath);
22
- let entry = entries.get(key);
23
- if (!entry) {
24
- entry = { fullPath, rawBuf: null, content: null };
25
- entries.set(key, entry);
26
- }
27
- return entry;
28
- }
29
-
30
- function seedBuffer(fullPath, rawBuf) {
31
- const entry = getOrCreate(fullPath);
32
- entry.rawBuf = rawBuf;
33
- entry.content = null;
34
- return entry;
35
- }
36
-
37
- function readBufferSync(fullPath) {
38
- const entry = getOrCreate(fullPath);
39
- if (!Buffer.isBuffer(entry.rawBuf)) entry.rawBuf = readFileSync(fullPath);
40
- return entry.rawBuf;
41
- }
42
-
43
- async function readBuffer(fullPath) {
44
- const entry = getOrCreate(fullPath);
45
- if (!Buffer.isBuffer(entry.rawBuf)) entry.rawBuf = await readFileAsync(fullPath);
46
- return entry.rawBuf;
47
- }
48
-
49
- function readTextSync(fullPath) {
50
- const entry = getOrCreate(fullPath);
51
- if (typeof entry.content !== 'string') {
52
- entry.content = decodeRawBufferForSnapshotCheck(readBufferSync(fullPath));
53
- }
54
- return entry.content;
55
- }
56
-
57
- function getEntry(fullPath) {
58
- return entries.get(keyForPath(fullPath)) || null;
59
- }
60
-
61
- function clear(fullPath = null) {
62
- if (fullPath) entries.delete(keyForPath(fullPath));
63
- else entries.clear();
64
- }
65
-
66
- return { seedBuffer, readBuffer, readBufferSync, readTextSync, getEntry, clear };
67
- }