mixdog 0.7.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 (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,607 @@
1
+ #!/usr/bin/env bun
2
+ // mixdog statusline wrapper — v0.1.45 (Bun/Node port of statusline.sh)
3
+ // Functional 1:1 parity with the bash original. Replaces bash startup (~50-100ms
4
+ // MSYS overhead) with a single Bun/Node process launch (~5-15ms).
5
+ // Node-builtins only: fs, os, node:http — no third-party deps.
6
+
7
+ import fs from 'fs';
8
+ import os from 'os';
9
+ import http from 'node:http';
10
+ import path from 'path';
11
+
12
+ // ── ANSI palette (identical to bash original) ────────────────────────────────
13
+ const R = '\x1b[0m';
14
+ const B = '\x1b[1m';
15
+ const D = '\x1b[2m';
16
+ const RED = '\x1b[31m';
17
+ const GRN = '\x1b[32m';
18
+ const YLW = '\x1b[33m';
19
+ const CYN = '\x1b[36m';
20
+ const GREY = '\x1b[90m';
21
+
22
+ // ── Terminal width ────────────────────────────────────────────────────────────
23
+ let COLS = parseInt(process.env.COLUMNS || '120', 10);
24
+ if (!Number.isFinite(COLS) || COLS <= 0) COLS = 120;
25
+
26
+ // ── Read CC stdin JSON synchronously ─────────────────────────────────────────
27
+ let CC_JSON = '';
28
+ try { CC_JSON = fs.readFileSync(0, 'utf8'); } catch { CC_JSON = ''; }
29
+
30
+ if (process.env.MIXDOG_STATUSLINE_TRACE && CC_JSON && process.env.CLAUDE_PLUGIN_DATA) {
31
+ try {
32
+ fs.writeFileSync(
33
+ path.join(process.env.CLAUDE_PLUGIN_DATA, 'statusline-stdin.json'),
34
+ CC_JSON
35
+ );
36
+ } catch {}
37
+ }
38
+
39
+ // ── One-shot log rotation at entry (5 MB threshold) ──────────────────────────
40
+ if (process.env.MIXDOG_STATUSLINE_TRACE) {
41
+ try {
42
+ const traceFile = path.join(
43
+ os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin', 'statusline-trace.log'
44
+ );
45
+ const st = fs.statSync(traceFile);
46
+ if (st.size > 5 * 1024 * 1024) fs.writeFileSync(traceFile, '');
47
+ } catch {}
48
+ }
49
+
50
+ // ── JSON field extraction helpers (regex, matching bash anchoring) ────────────
51
+ function extract(json, re) {
52
+ const m = re.exec(json);
53
+ return m ? m[1] : '';
54
+ }
55
+
56
+ // Slice a substring from after `key` up to (but not including) `stopKey`.
57
+ // Mirrors bash's `${CC_JSON#*"key"}` / `${var%%"stopKey"*}`.
58
+ function slice(json, key, stopKey) {
59
+ const idx = json.indexOf(key);
60
+ if (idx < 0) return null;
61
+ const tail = json.slice(idx + key.length);
62
+ if (stopKey) {
63
+ const stop = tail.indexOf(stopKey);
64
+ return stop >= 0 ? tail.slice(0, stop) : tail;
65
+ }
66
+ return tail;
67
+ }
68
+
69
+ // ── Extract CC fields ─────────────────────────────────────────────────────────
70
+ let CC_MODEL = extract(CC_JSON, /"display_name"\s*:\s*"([^"]+)"/);
71
+ let CC_CTX_USED = '';
72
+ let CC_RL_5H = '';
73
+ let CC_RL_7D = '';
74
+ let CC_RL_5H_RESET = '';
75
+
76
+ // context_window scope — cap at "rate_limits" sibling so cold-start payloads
77
+ // don't accidentally pick up the five_hour used_percentage.
78
+ const ctxTail = slice(CC_JSON, '"context_window"', '"rate_limits"');
79
+ if (ctxTail !== null) {
80
+ CC_CTX_USED = extract(ctxTail, /"used_percentage"\s*:\s*([0-9.]+)/);
81
+ }
82
+
83
+ const fiveTail = slice(CC_JSON, '"five_hour"', '"seven_day"');
84
+ if (fiveTail !== null) {
85
+ CC_RL_5H = extract(fiveTail, /"used_percentage"\s*:\s*([0-9.]+)/);
86
+ CC_RL_5H_RESET = extract(fiveTail, /"resets_at"\s*:\s*([0-9]+)/);
87
+ }
88
+
89
+ const sevenTail = slice(CC_JSON, '"seven_day"', null);
90
+ if (sevenTail !== null) {
91
+ CC_RL_7D = extract(sevenTail, /"used_percentage"\s*:\s*([0-9.]+)/);
92
+ }
93
+
94
+ // ── Extract effort level ──────────────────────────────────────────────────────
95
+ // Priority: CC_JSON effort.level → env CLAUDE_CODE_EFFORT_LEVEL → settings.json
96
+ let CC_EFFORT = extract(CC_JSON, /"effort"\s*:\s*\{[^}]*"level"\s*:\s*"([^"]+)"/);
97
+ if (!CC_EFFORT) CC_EFFORT = process.env.CLAUDE_CODE_EFFORT_LEVEL || '';
98
+ if (!CC_EFFORT) {
99
+ try {
100
+ const settingsRaw = fs.readFileSync(
101
+ path.join(os.homedir(), '.claude', 'settings.json'), 'utf8'
102
+ );
103
+ CC_EFFORT = extract(settingsRaw, /"effortLevel"\s*:\s*"([^"]+)"/);
104
+ } catch {}
105
+ }
106
+
107
+ // ── Extract CC session_id ─────────────────────────────────────────────────────
108
+ const CC_SESSION_ID = extract(CC_JSON, /"session_id"\s*:\s*"([^"]+)"/);
109
+ const STATUS_ARGS = (() => {
110
+ try {
111
+ const parsed = JSON.parse(CC_JSON);
112
+ return Array.isArray(parsed?._args) ? parsed._args.map(String) : [];
113
+ } catch { return []; }
114
+ })();
115
+ function statusArg(prefix) {
116
+ return STATUS_ARGS.find(arg => arg.startsWith(prefix))?.slice(prefix.length) || '';
117
+ }
118
+ function positiveInt(value) {
119
+ const n = parseInt(String(value || ''), 10);
120
+ return Number.isFinite(n) && n > 0 ? n : 0;
121
+ }
122
+ const CLIENT_HOST_PID = positiveInt(statusArg('--client-host-pid=')) || positiveInt(process.ppid);
123
+ // Bash-jobs scope pid: ONLY the explicitly passed --client-host-pid (the
124
+ // shim-provided claude.exe pid). No process.ppid fallback here — under a
125
+ // no-shim invocation ppid is the renderer's parent (the daemon/launcher),
126
+ // NOT claude.exe, so falling back would count jobs that merely match that
127
+ // unrelated pid. Absent ⇒ 0 ⇒ the segment attributes nothing.
128
+ const CLIENT_HOST_PID_JOBS = positiveInt(statusArg('--client-host-pid='));
129
+ function advertPidAlive(content) {
130
+ const pid = positiveInt(extract(content, /"pid"\s*:\s*([0-9]+)/));
131
+ if (!pid) return false;
132
+ try { process.kill(pid, 0); return true; } catch { return false; }
133
+ }
134
+ function advertCcMatches(content) {
135
+ return !!(CC_SESSION_ID && content.includes('"cc_session_id"') && content.includes(`"${CC_SESSION_ID}"`));
136
+ }
137
+ function advertClaimed(content) {
138
+ return content.includes('"cc_session_id"');
139
+ }
140
+ function advertClientHostPid(content) {
141
+ return positiveInt(extract(content, /"clientHostPid"\s*:\s*([0-9]+)/))
142
+ || positiveInt(extract(content, /"client_host_pid"\s*:\s*([0-9]+)/));
143
+ }
144
+ function advertHostMatches(content, { allowUnclaimed = false } = {}) {
145
+ if (!CLIENT_HOST_PID) return true;
146
+ const clientHostPid = advertClientHostPid(content);
147
+ if (clientHostPid) return clientHostPid === CLIENT_HOST_PID;
148
+ if (allowUnclaimed && !advertClaimed(content)) return true;
149
+ const ownerHostPid = positiveInt(extract(content, /"ownerHostPid"\s*:\s*([0-9]+)/));
150
+ return ownerHostPid === CLIENT_HOST_PID;
151
+ }
152
+
153
+ // ── Advert routing ────────────────────────────────────────────────────────────
154
+ // Fast path: read `.cc-<sessionId>.path` mapping (written after a successful
155
+ // claim on an earlier tick) and resolve straight to the cached advert.
156
+ // Verified by pid liveness + cc_session_id match in the advert body — any
157
+ // mismatch falls through to the O(N) scan and re-writes the mapping at the
158
+ // end. With a CC session_id, never bind to an advert already claimed by a
159
+ // different CC session: that leaks another terminal's workers into this
160
+ // statusline.
161
+ let statusAdvert = '';
162
+ let needClaim = false;
163
+ const advertDir = path.join(os.homedir(), '.claude', 'mixdog-status');
164
+ const mappingPath = CC_SESSION_ID
165
+ ? path.join(advertDir, `.cc-${CC_SESSION_ID}.path`)
166
+ : '';
167
+
168
+ if (mappingPath) {
169
+ try {
170
+ const cached = fs.readFileSync(mappingPath, 'utf8').trim();
171
+ if (cached) {
172
+ const cachedAdvert = path.isAbsolute(cached) ? cached : path.join(advertDir, cached);
173
+ const advertContent = fs.readFileSync(cachedAdvert, 'utf8');
174
+ if (advertPidAlive(advertContent) && advertCcMatches(advertContent) && advertHostMatches(advertContent)) {
175
+ statusAdvert = cachedAdvert;
176
+ needClaim = false;
177
+ } else {
178
+ try { fs.unlinkSync(mappingPath); } catch {}
179
+ }
180
+ } else {
181
+ try { fs.unlinkSync(mappingPath); } catch {}
182
+ }
183
+ } catch {
184
+ try { fs.unlinkSync(mappingPath); } catch {}
185
+ }
186
+ }
187
+
188
+ if (!statusAdvert) {
189
+ try {
190
+ const files = fs.readdirSync(advertDir)
191
+ .filter(f => f.endsWith('.json'))
192
+ .map(f => path.join(advertDir, f));
193
+
194
+ for (const f of files) {
195
+ let content;
196
+ try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
197
+ if (!advertPidAlive(content)) continue;
198
+
199
+ // Step 1: paired — cc_session_id matches
200
+ if (advertCcMatches(content)) {
201
+ if (!advertHostMatches(content)) continue;
202
+ statusAdvert = f;
203
+ needClaim = false;
204
+ break;
205
+ }
206
+ // Step 2: first unclaimed same-host advert. Never fetch an advert already
207
+ // claimed by a different CC session; that would leak another terminal's
208
+ // bridge workers into this statusline for at least one tick.
209
+ if (!statusAdvert && CC_SESSION_ID && !advertClaimed(content) && advertHostMatches(content, { allowUnclaimed: true })) {
210
+ statusAdvert = f;
211
+ needClaim = true;
212
+ // keep scanning — a paired advert may appear later
213
+ }
214
+ }
215
+
216
+ // Legacy/no-session fallback only. A statusline with session_id must not
217
+ // attach to another CC session's claimed advert.
218
+ if (!statusAdvert && !CC_SESSION_ID) {
219
+ for (const f of files) {
220
+ try { fs.readFileSync(f, 'utf8'); statusAdvert = f; break; } catch {}
221
+ }
222
+ }
223
+ } catch {}
224
+
225
+ // Persist mapping for the next tick — fast path will reuse it. Skip when
226
+ // we still need to claim (the next tick will validate and write itself).
227
+ if (statusAdvert && mappingPath && !needClaim) {
228
+ try { fs.writeFileSync(mappingPath, statusAdvert); } catch {}
229
+ }
230
+ }
231
+
232
+ // Legacy single-file fallback.
233
+ if (!statusAdvert && !CC_SESSION_ID) {
234
+ statusAdvert = path.join(os.homedir(), '.claude', 'mixdog-status.json');
235
+ }
236
+
237
+ // ── Read port from advert ─────────────────────────────────────────────────────
238
+ let statusPort = '';
239
+ try {
240
+ const advertContent = fs.readFileSync(statusAdvert, 'utf8');
241
+ statusPort = extract(advertContent, /"port"\s*:\s*([0-9]+)/);
242
+ } catch {}
243
+
244
+ // ── Fire-and-forget claim POST ────────────────────────────────────────────────
245
+ // Response MUST be consumed (res.resume()) — Node http keeps the socket
246
+ // open until the body is read or destroyed, so an unread 200 leaks one
247
+ // socket per tick on a 1-5 s refresh cadence. setTimeout < the GET
248
+ // budget below so a stuck POST never overlaps the next statusline spawn.
249
+ if (needClaim && CC_SESSION_ID && statusPort) {
250
+ const claimPayload = { cc_session_id: CC_SESSION_ID };
251
+ if (CLIENT_HOST_PID) claimPayload.client_host_pid = CLIENT_HOST_PID;
252
+ const body = JSON.stringify(claimPayload);
253
+ try {
254
+ const req = http.request({
255
+ hostname: '127.0.0.1',
256
+ port: parseInt(statusPort, 10),
257
+ path: '/register-cc-session',
258
+ method: 'POST',
259
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
260
+ }, (res) => { res.resume(); });
261
+ req.on('error', () => {});
262
+ req.setTimeout(800, () => { try { req.destroy(); } catch {} });
263
+ req.write(body);
264
+ req.end();
265
+ } catch {}
266
+ }
267
+
268
+ // ── Fetch /bridge/status with 1s timeout ─────────────────────────────────────
269
+ let bridgeJson = '';
270
+ if (statusPort) {
271
+ bridgeJson = await new Promise(resolve => {
272
+ try {
273
+ const statusUrl = `http://127.0.0.1:${statusPort}/bridge/status?format=statusline-json`
274
+ + (CLIENT_HOST_PID ? `&clientHostPid=${CLIENT_HOST_PID}` : '');
275
+ const req = http.get(
276
+ statusUrl,
277
+ { timeout: 500 },
278
+ res => {
279
+ let data = '';
280
+ res.on('data', chunk => { data += chunk; });
281
+ res.on('end', () => resolve(data));
282
+ }
283
+ );
284
+ req.on('error', () => resolve(''));
285
+ req.on('timeout', () => { req.destroy(); resolve(''); });
286
+ } catch { resolve(''); }
287
+ });
288
+ }
289
+ if (!bridgeJson.startsWith('{')) bridgeJson = '';
290
+
291
+ // ── NOBRIDGE trace ────────────────────────────────────────────────────────────
292
+ if (!bridgeJson && process.env.MIXDOG_STATUSLINE_TRACE) {
293
+ try {
294
+ const traceDir = path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin');
295
+ if (fs.existsSync(traceDir)) {
296
+ const advertPresent = (() => { try { fs.accessSync(statusAdvert); return 'present'; } catch { return 'missing'; } })();
297
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
298
+ fs.appendFileSync(
299
+ path.join(traceDir, 'statusline-trace.log'),
300
+ `${ts} NOBRIDGE port=${statusPort || '?'} advert=${advertPresent}\n`
301
+ );
302
+ }
303
+ } catch {}
304
+ }
305
+
306
+ // ── Extract bridge fields ─────────────────────────────────────────────────────
307
+ let bSessRoles = '';
308
+ let bSchedNextAt = '';
309
+ let bSchedNextName = '';
310
+ // Worker list carrying running/idle status alongside each tag, surfaced by
311
+ // the aggregator's sessions.workers segment. Falls back to roles (running-
312
+ // only) when an older aggregator payload lacks the workers array.
313
+ let bWorkers = [];
314
+
315
+ if (bridgeJson) {
316
+ const sessRaw = extract(bridgeJson, /"sessions"\s*:\s*\{[^}]*"roles"\s*:\s*\[([^\]]*)\]/);
317
+ if (sessRaw) bSessRoles = sessRaw.replace(/"/g, '').replace(/\s/g, '');
318
+ const workersRaw = extract(bridgeJson, /"workers"\s*:\s*\[([^\]]*)\]/);
319
+ if (workersRaw) {
320
+ // Parse [{"tag":"x","status":"running"},...] without a full JSON.parse
321
+ // of the whole payload (matches the existing regex-extract approach).
322
+ const re = /\{[^}]*?"tag"\s*:\s*"([^"]*)"[^}]*?"status"\s*:\s*"([^"]*)"[^}]*?\}/g;
323
+ let m;
324
+ while ((m = re.exec(workersRaw)) !== null) {
325
+ bWorkers.push({ tag: m[1], status: m[2] === 'idle' ? 'idle' : 'running' });
326
+ }
327
+ }
328
+
329
+ bSchedNextAt = extract(bridgeJson, /"next"\s*:\s*\{[^}]*"fireAt"\s*:\s*([0-9]+)/);
330
+ bSchedNextName = extract(bridgeJson, /"next"\s*:\s*\{[^}]*"name"\s*:\s*"([^"]*)"/);
331
+ }
332
+
333
+ // ── Format helpers ────────────────────────────────────────────────────────────
334
+
335
+ // Model short form — collapse "(1M context)" → "(1M)", prefix with family name.
336
+ let modelStr = '';
337
+ if (CC_MODEL) {
338
+ let raw = CC_MODEL.replace('(1M context)', '(1M)');
339
+ if (raw.includes('Opus')) modelStr = 'Opus' + raw.slice(raw.indexOf('Opus') + 4);
340
+ else if (raw.includes('Sonnet')) modelStr = 'Sonnet' + raw.slice(raw.indexOf('Sonnet') + 6);
341
+ else if (raw.includes('Haiku')) modelStr = 'Haiku' + raw.slice(raw.indexOf('Haiku') + 5);
342
+ else modelStr = raw;
343
+ }
344
+ const modelShort = modelStr.split(' ')[0];
345
+ const effortStr = CC_EFFORT ? CC_EFFORT.toUpperCase() : '';
346
+
347
+ // Round float string to nearest integer (replicates printf "%.0f").
348
+ function roundPct(s) {
349
+ const n = parseFloat(s);
350
+ return Number.isFinite(n) ? Math.round(n) : null;
351
+ }
352
+
353
+ const ctxInt = roundPct(CC_CTX_USED);
354
+ const rl5hInt = roundPct(CC_RL_5H);
355
+ const rl7dInt = roundPct(CC_RL_7D);
356
+
357
+ // HH:MM from ms epoch (local time).
358
+ function epochMsToHHMM(ms) {
359
+ const d = new Date(parseInt(ms, 10));
360
+ if (isNaN(d.getTime())) return '';
361
+ return d.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit', hour12: false });
362
+ }
363
+
364
+ const resetStr = CC_RL_5H_RESET ? epochMsToHHMM(CC_RL_5H_RESET * 1000) : '';
365
+
366
+ // Schedule next HH:MM — fireAt is ms epoch directly.
367
+ const schedNextHHMM = bSchedNextAt ? epochMsToHHMM(parseInt(bSchedNextAt, 10)) : '';
368
+
369
+ // Colour a percentage integer string.
370
+ function colourPct(p) {
371
+ if (p >= 90) return `${RED}${p}%${R}`;
372
+ if (p >= 70) return `${YLW}${p}%${R}`;
373
+ return `${GRN}${p}%${R}`;
374
+ }
375
+
376
+ // Build bar: ▓ for filled, ░ for empty.
377
+ function makeBar(pct, cells) {
378
+ if (pct === null || cells <= 0) return '';
379
+ let filled = Math.floor(pct * cells / 100);
380
+ if (filled < 0) filled = 0;
381
+ if (filled > cells) filled = cells;
382
+ if (pct > 0 && filled === 0) filled = 1;
383
+ return '▓'.repeat(filled) + '░'.repeat(cells - filled);
384
+ }
385
+
386
+ // ── Build L1 ──────────────────────────────────────────────────────────────────
387
+ const SEP = `${D}│${R}`;
388
+ const l1Parts = [];
389
+ function addL1(seg) { if (seg) l1Parts.push(seg); }
390
+
391
+ // Model + effort
392
+ if (modelStr) {
393
+ const m = COLS >= 120 ? modelStr : modelShort;
394
+ if (effortStr) {
395
+ addL1(`${CYN}◆${R} ${B}${m}${R} ${D}·${R} ${B}${effortStr}${R}`);
396
+ } else {
397
+ addL1(`${CYN}◆${R} ${B}${m}${R}`);
398
+ }
399
+ }
400
+
401
+ // Context bar
402
+ if (ctxInt !== null) {
403
+ const fill = ctxInt >= 90 ? RED : ctxInt >= 70 ? YLW : GRN;
404
+ let barOut = '';
405
+ if (COLS >= 120) barOut = makeBar(ctxInt, 14);
406
+ else if (COLS >= 80) barOut = makeBar(ctxInt, 8);
407
+
408
+ if (barOut) {
409
+ const filledPart = barOut.replace(/░/g, '');
410
+ const emptyPart = barOut.replace(/▓/g, '');
411
+ const bar = `${fill}${filledPart}${R}${D}${emptyPart}${R}`;
412
+ addL1(`${bar} ${ctxInt}%`);
413
+ } else {
414
+ addL1(`${fill}${ctxInt}%${R}`);
415
+ }
416
+ }
417
+
418
+ // Rate limits + reset
419
+ if (rl5hInt !== null) {
420
+ addL1(`${D}5H${R} ${colourPct(rl5hInt)}`);
421
+ }
422
+ if (COLS >= 80) {
423
+ if (rl7dInt !== null) {
424
+ addL1(`${D}7D${R} ${colourPct(rl7dInt)}`);
425
+ }
426
+ if (resetStr) {
427
+ addL1(`${D}↻ ${resetStr}${R}`);
428
+ }
429
+ }
430
+
431
+ // ── Role classification ───────────────────────────────────────────────────────
432
+ let workCount = 0;
433
+ let workOrder = '';
434
+ let hasCycle1 = false;
435
+ let hasCycle2 = false;
436
+ let hasCycle3 = false;
437
+ let hasSched = false;
438
+ let hasWebhook = false;
439
+ let hasExplorer = false;
440
+
441
+ function classifyMaint(role) {
442
+ switch (role) {
443
+ case 'cycle1-agent': hasCycle1 = true; return true;
444
+ case 'cycle2-agent': hasCycle2 = true; return true;
445
+ case 'cycle3-agent': hasCycle3 = true; return true;
446
+ case 'scheduler-task': hasSched = true; return true;
447
+ case 'webhook-handler': hasWebhook = true; return true;
448
+ case 'explorer': hasExplorer = true; return true;
449
+ default: return false;
450
+ }
451
+ }
452
+
453
+ // Idle worker tags (greyed in L2), threaded from the aggregator's
454
+ // sessions.workers[].status. Running user-workers still feed workCount /
455
+ // workOrder so the existing "N Running (tags)" badge is unchanged.
456
+ const idleWorkers = [];
457
+ if (bWorkers.length) {
458
+ // Preferred path: per-worker running/idle status from the aggregator.
459
+ for (const w of bWorkers) {
460
+ if (classifyMaint(w.tag)) continue; // maintenance → L1, not the worker badge
461
+ if (w.status === 'idle') {
462
+ idleWorkers.push(w.tag);
463
+ } else {
464
+ workCount++;
465
+ workOrder = workOrder ? `${workOrder}, ${w.tag}` : w.tag;
466
+ }
467
+ }
468
+ } else if (bSessRoles) {
469
+ // Fallback: legacy roles array (running-only, no idle/status info).
470
+ for (const role of bSessRoles.split(',')) {
471
+ if (!role) continue;
472
+ if (classifyMaint(role)) continue;
473
+ workCount++;
474
+ workOrder = workOrder ? `${workOrder}, ${role}` : role;
475
+ }
476
+ }
477
+
478
+ // Maint/system/retrieval badges → single L1 segment
479
+ const maintParts = [];
480
+ if (hasCycle1) maintParts.push(`${GRN}↻${R} ${B}cycle1${R}`);
481
+ if (hasCycle2) maintParts.push(`${GRN}↻${R} ${B}cycle2${R}`);
482
+ if (hasCycle3) maintParts.push(`${GRN}↻${R} ${B}cycle3${R}`);
483
+ if (hasSched) maintParts.push(`${GRN}↻${R} ${B}scheduler${R}`);
484
+ if (hasWebhook) maintParts.push(`${GRN}↻${R} ${B}webhook${R}`);
485
+ if (hasExplorer) maintParts.push(`${GRN}↻${R} ${B}explorer${R}`);
486
+ if (maintParts.length) addL1(maintParts.join(' '));
487
+
488
+ // Background bash jobs: a running job is a `<jobId>.json` whose sibling
489
+ // `<jobId>.done` (written on exit) is absent. Surviving candidates are then
490
+ // liveness-filtered — the `.json` carries the wrapper `pid`; an orphaned job
491
+ // (wrapper crashed, `.done` never written, pid dead for days) is skipped via
492
+ // process.kill(pid, 0). Bounded per tick: candidates are ordered newest-first
493
+ // (the jobId embeds its spawn Date.now()) and at most JOB_SCAN_CAP of them are
494
+ // read — keeping each render O(cap), not O(total on-disk jobs). When the scan
495
+ // is truncated a trailing `+` overflow marker is appended to the count. One
496
+ // readFileSync/statSync per scanned job for the oldest startedAt (the .json is
497
+ // written at job start); tolerate the dir being missing and never throw.
498
+ const JOB_SCAN_CAP = 30;
499
+ const bashJobsSeg = (() => {
500
+ try {
501
+ const dir = path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin', 'shell-jobs');
502
+ const names = fs.readdirSync(dir);
503
+ const done = new Set();
504
+ const jobs = [];
505
+ const ownerByJob = new Map();
506
+ for (const n of names) {
507
+ if (n.endsWith('.done')) done.add(n.slice(0, -5));
508
+ else if (n.endsWith('.json')) jobs.push(n.slice(0, -5));
509
+ else {
510
+ // Owner sidecar `<jobId>.owner-<pid>` — a zero-byte marker whose NAME
511
+ // carries the owning CC host pid, written next to the .json at spawn.
512
+ const i = n.lastIndexOf('.owner-');
513
+ if (i > 0) { const pid = positiveInt(n.slice(i + 7)); if (pid) ownerByJob.set(n.slice(0, i), pid); }
514
+ }
515
+ }
516
+ // Owner-filter BEFORE the scan cap, from the directory listing alone: each
517
+ // job's owning claude.exe pid is read from its `.owner-<pid>` marker name
518
+ // (no JSON read), so another session's newer jobs can never evict ours at
519
+ // the cap. Only jobs whose marker pid equals THIS statusline's
520
+ // --client-host-pid survive; legacy jobs without a marker — and every job
521
+ // when no host pid was passed (CLIENT_HOST_PID_JOBS absent) — are excluded.
522
+ // Then ordered newest-first by the spawn timestamp embedded in
523
+ // `job_<ms>_<rand>`, so truncation drops only this session's oldest tail.
524
+ const jobStampMs = (id) => { const m = /^job_(\d+)/.exec(id); return m ? Number(m[1]) : 0; };
525
+ const candidates = jobs
526
+ .filter((id) => !done.has(id) && CLIENT_HOST_PID_JOBS && ownerByJob.get(id) === CLIENT_HOST_PID_JOBS)
527
+ .sort((a, b) => jobStampMs(b) - jobStampMs(a));
528
+ if (candidates.length === 0) return '';
529
+ const scan = candidates.slice(0, JOB_SCAN_CAP);
530
+ const truncated = candidates.length > JOB_SCAN_CAP;
531
+ let count = 0;
532
+ let oldestMs = Infinity;
533
+ for (const id of scan) {
534
+ const p = path.join(dir, `${id}.json`);
535
+ let pid, tmo, enforced;
536
+ try {
537
+ const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
538
+ pid = d.pid; tmo = Number(d.timeoutMs);
539
+ // Runtime enforcement proof: PS records timeoutEnforced:true; the
540
+ // posix wrapper touches <id>.enforced iff its `timeout` branch ran.
541
+ enforced = d.timeoutEnforced === true || fs.existsSync(path.join(dir, `${id}.enforced`));
542
+ }
543
+ catch { continue; } // unreadable/unparseable → skip
544
+ let st;
545
+ try { st = fs.statSync(p); }
546
+ catch { continue; }
547
+ // Deadline: the wrapper force-kills at timeoutMs, so a job older than
548
+ // timeoutMs + grace is dead even when its pid was recycled by an
549
+ // unrelated live process (pid-reuse-proof, mirrors the sweep). Trusted
550
+ // only when the record proves in-wrapper enforcement (timeoutEnforced).
551
+ if (enforced && Number.isFinite(tmo) && tmo > 0 && (Date.now() - st.mtimeMs) > tmo + 30 * 60_000) continue;
552
+ // kill(0, 0) probes the whole process group and "succeeds" — a
553
+ // malformed pid (0, "", []) must be rejected before the probe.
554
+ pid = Number(pid);
555
+ if (!Number.isInteger(pid) || pid <= 0) continue;
556
+ // Liveness: process.kill(pid, 0) succeeds or throws EPERM → alive;
557
+ // ESRCH/invalid pid → dead → skip.
558
+ let alive = false;
559
+ try { process.kill(pid, 0); alive = true; }
560
+ catch (e) { alive = e && e.code === 'EPERM'; }
561
+ if (!alive) continue;
562
+ count++;
563
+ if (st.mtimeMs < oldestMs) oldestMs = st.mtimeMs;
564
+ }
565
+ if (count === 0) return '';
566
+ let elapsed = '';
567
+ if (Number.isFinite(oldestMs)) {
568
+ const secs = Math.max(0, Math.floor((Date.now() - oldestMs) / 1000));
569
+ elapsed = secs < 60 ? ` ${secs}s` : ` ${Math.floor(secs / 60)}m`;
570
+ }
571
+ // Overflow marker: the scan was capped, so jobs may exist beyond what was
572
+ // counted — surface a trailing `+` so the figure reads as a lower bound.
573
+ const overflow = truncated ? '+' : '';
574
+ return `${GREY}⚙ bash:${count}${overflow}${elapsed}${R}`;
575
+ } catch { return ''; }
576
+ })();
577
+ if (bashJobsSeg) addL1(bashJobsSeg);
578
+
579
+ // ── Build L2 ──────────────────────────────────────────────────────────────────
580
+ const l2Parts = [];
581
+ function addL2(seg) { if (seg) l2Parts.push(seg); }
582
+
583
+ // Work sessions
584
+ if (workCount > 0 && workOrder) {
585
+ addL2(`${GRN}●${R} ${B}${workCount} Running${R} ${D}(${R}${CYN}${workOrder}${R}${D})${R}`);
586
+ }
587
+
588
+ if (idleWorkers.length) {
589
+ // Idle workers: filled grey dot (●) + explicit 'idle' marker + tag list.
590
+ // Filled (not hollow ○) so the glyph matches the running ● weight; only
591
+ // the colour (grey vs green) distinguishes idle from running.
592
+ const idleTags = idleWorkers.join(', ');
593
+ addL2(`${GREY}● ${idleWorkers.length} idle (${idleTags})${R}`);
594
+ }
595
+
596
+ // Schedule next
597
+ if (bSchedNextName && schedNextHHMM) {
598
+ addL2(`${YLW}⏰${R} ${B}${bSchedNextName}${R} ${D}${schedNextHHMM}${R}`);
599
+ }
600
+
601
+ // Suppress "Idle" fallback (matches bash: if L2 = "Idle" → suppress)
602
+ const l1 = l1Parts.join(` ${SEP} `) || 'mixdog';
603
+ let l2 = l2Parts.join(` ${SEP} `);
604
+ if (l2 === 'Idle') l2 = '';
605
+
606
+ process.stdout.write(l1 + '\n');
607
+ if (l2) process.stdout.write(l2 + '\n');