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,550 @@
1
+ // mixdog statusline renderer — function form for in-daemon reuse.
2
+ //
3
+ // Mirrors `bin/statusline.mjs` exactly, but takes the CC stdin JSON as an
4
+ // argument and returns the rendered status text instead of reading fd 0 and
5
+ // writing to process.stdout. This allows the hook-pipe daemon to compute the
6
+ // statusline without paying the ~100ms bun cold-start on every refresh tick.
7
+
8
+ import fs from 'fs';
9
+ import os from 'os';
10
+ import http from 'node:http';
11
+ import path from 'path';
12
+
13
+ export async function renderStatusLine(ccJsonInput) {
14
+ // ── ANSI palette (identical to bash original) ────────────────────────────────
15
+ const R = '\x1b[0m';
16
+ const B = '\x1b[1m';
17
+ const D = '\x1b[2m';
18
+ const RED = '\x1b[31m';
19
+ const GRN = '\x1b[32m';
20
+ const YLW = '\x1b[33m';
21
+ const CYN = '\x1b[36m';
22
+ const GREY = '\x1b[90m';
23
+
24
+ // ── Terminal width ──────────────────────────────────────────────────────────
25
+ let COLS = parseInt(process.env.COLUMNS || '120', 10);
26
+ if (!Number.isFinite(COLS) || COLS <= 0) COLS = 120;
27
+
28
+ // ── CC stdin JSON (from caller) ─────────────────────────────────────────────
29
+ const CC_JSON = typeof ccJsonInput === 'string' ? ccJsonInput : '';
30
+
31
+ if (process.env.MIXDOG_STATUSLINE_TRACE && CC_JSON && process.env.CLAUDE_PLUGIN_DATA) {
32
+ try {
33
+ fs.writeFileSync(
34
+ path.join(process.env.CLAUDE_PLUGIN_DATA, 'statusline-stdin.json'),
35
+ CC_JSON
36
+ );
37
+ } catch {}
38
+ }
39
+
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
+ // ── helpers ────────────────────────────────────────────────────────────────
51
+ function extract(json, re) {
52
+ const m = re.exec(json);
53
+ return m ? m[1] : '';
54
+ }
55
+
56
+ function slice(json, key, stopKey) {
57
+ const idx = json.indexOf(key);
58
+ if (idx < 0) return null;
59
+ const tail = json.slice(idx + key.length);
60
+ if (stopKey) {
61
+ const stop = tail.indexOf(stopKey);
62
+ return stop >= 0 ? tail.slice(0, stop) : tail;
63
+ }
64
+ return tail;
65
+ }
66
+
67
+ // ── Extract CC fields ──────────────────────────────────────────────────────
68
+ let CC_MODEL = extract(CC_JSON, /"display_name"\s*:\s*"([^"]+)"/);
69
+ let CC_CTX_USED = '';
70
+ let CC_RL_5H = '';
71
+ let CC_RL_7D = '';
72
+ let CC_RL_5H_RESET = '';
73
+
74
+ const ctxTail = slice(CC_JSON, '"context_window"', '"rate_limits"');
75
+ if (ctxTail !== null) {
76
+ CC_CTX_USED = extract(ctxTail, /"used_percentage"\s*:\s*([0-9.]+)/);
77
+ }
78
+ const fiveTail = slice(CC_JSON, '"five_hour"', '"seven_day"');
79
+ if (fiveTail !== null) {
80
+ CC_RL_5H = extract(fiveTail, /"used_percentage"\s*:\s*([0-9.]+)/);
81
+ CC_RL_5H_RESET = extract(fiveTail, /"resets_at"\s*:\s*([0-9]+)/);
82
+ }
83
+ const sevenTail = slice(CC_JSON, '"seven_day"', null);
84
+ if (sevenTail !== null) {
85
+ CC_RL_7D = extract(sevenTail, /"used_percentage"\s*:\s*([0-9.]+)/);
86
+ }
87
+
88
+ let CC_EFFORT = extract(CC_JSON, /"effort"\s*:\s*\{[^}]*"level"\s*:\s*"([^"]+)"/);
89
+ if (!CC_EFFORT) CC_EFFORT = process.env.CLAUDE_CODE_EFFORT_LEVEL || '';
90
+ if (!CC_EFFORT) {
91
+ try {
92
+ const settingsRaw = fs.readFileSync(
93
+ path.join(os.homedir(), '.claude', 'settings.json'), 'utf8'
94
+ );
95
+ CC_EFFORT = extract(settingsRaw, /"effortLevel"\s*:\s*"([^"]+)"/);
96
+ } catch {}
97
+ }
98
+
99
+ const CC_SESSION_ID = extract(CC_JSON, /"session_id"\s*:\s*"([^"]+)"/);
100
+ const STATUS_ARGS = (() => {
101
+ try {
102
+ const parsed = JSON.parse(CC_JSON);
103
+ return Array.isArray(parsed?._args) ? parsed._args.map(String) : [];
104
+ } catch { return []; }
105
+ })();
106
+ function statusArg(prefix) {
107
+ return STATUS_ARGS.find(arg => arg.startsWith(prefix))?.slice(prefix.length) || '';
108
+ }
109
+ function positiveInt(value) {
110
+ const n = parseInt(String(value || ''), 10);
111
+ return Number.isFinite(n) && n > 0 ? n : 0;
112
+ }
113
+ const CLIENT_HOST_PID = positiveInt(statusArg('--client-host-pid=')) || positiveInt(process.ppid);
114
+ // Bash-jobs scope pid: ONLY the explicitly passed --client-host-pid (the
115
+ // shim-provided claude.exe pid). No process.ppid fallback here — under a
116
+ // no-shim invocation ppid is the renderer's parent (the daemon/launcher),
117
+ // NOT claude.exe, so falling back would count jobs that merely match that
118
+ // unrelated pid. Absent ⇒ 0 ⇒ the segment attributes nothing.
119
+ const CLIENT_HOST_PID_JOBS = positiveInt(statusArg('--client-host-pid='));
120
+ function advertPidAlive(content) {
121
+ const pid = positiveInt(extract(content, /"pid"\s*:\s*([0-9]+)/));
122
+ if (!pid) return false;
123
+ try { process.kill(pid, 0); return true; } catch { return false; }
124
+ }
125
+ function advertCcMatches(content) {
126
+ return !!(CC_SESSION_ID && content.includes('"cc_session_id"') && content.includes(`"${CC_SESSION_ID}"`));
127
+ }
128
+ function advertClaimed(content) {
129
+ return content.includes('"cc_session_id"');
130
+ }
131
+ function advertClientHostPid(content) {
132
+ return positiveInt(extract(content, /"clientHostPid"\s*:\s*([0-9]+)/))
133
+ || positiveInt(extract(content, /"client_host_pid"\s*:\s*([0-9]+)/));
134
+ }
135
+ function advertHostMatches(content, { allowUnclaimed = false } = {}) {
136
+ if (!CLIENT_HOST_PID) return true;
137
+ const clientHostPid = advertClientHostPid(content);
138
+ if (clientHostPid) return clientHostPid === CLIENT_HOST_PID;
139
+ if (allowUnclaimed && !advertClaimed(content)) return true;
140
+ const ownerHostPid = positiveInt(extract(content, /"ownerHostPid"\s*:\s*([0-9]+)/));
141
+ return ownerHostPid === CLIENT_HOST_PID;
142
+ }
143
+
144
+ // ── Advert routing ─────────────────────────────────────────────────────────
145
+ let statusAdvert = '';
146
+ let needClaim = false;
147
+ const advertDir = path.join(os.homedir(), '.claude', 'mixdog-status');
148
+ const mappingPath = CC_SESSION_ID
149
+ ? path.join(advertDir, `.cc-${CC_SESSION_ID}.path`)
150
+ : '';
151
+
152
+ if (mappingPath) {
153
+ try {
154
+ const cached = fs.readFileSync(mappingPath, 'utf8').trim();
155
+ if (cached) {
156
+ const cachedAdvert = path.isAbsolute(cached) ? cached : path.join(advertDir, cached);
157
+ const advertContent = fs.readFileSync(cachedAdvert, 'utf8');
158
+ if (advertPidAlive(advertContent) && advertCcMatches(advertContent) && advertHostMatches(advertContent)) {
159
+ statusAdvert = cachedAdvert;
160
+ needClaim = false;
161
+ } else {
162
+ try { fs.unlinkSync(mappingPath); } catch {}
163
+ }
164
+ } else {
165
+ try { fs.unlinkSync(mappingPath); } catch {}
166
+ }
167
+ } catch {
168
+ try { fs.unlinkSync(mappingPath); } catch {}
169
+ }
170
+ }
171
+
172
+ if (!statusAdvert) {
173
+ try {
174
+ const files = fs.readdirSync(advertDir)
175
+ .filter(f => f.endsWith('.json'))
176
+ .map(f => path.join(advertDir, f));
177
+ for (const f of files) {
178
+ let content;
179
+ try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
180
+ if (!advertPidAlive(content)) continue;
181
+ if (advertCcMatches(content)) {
182
+ if (!advertHostMatches(content)) continue;
183
+ statusAdvert = f;
184
+ needClaim = false;
185
+ break;
186
+ }
187
+ if (!statusAdvert && CC_SESSION_ID && !advertClaimed(content) && advertHostMatches(content, { allowUnclaimed: true })) {
188
+ statusAdvert = f;
189
+ needClaim = true;
190
+ }
191
+ }
192
+ if (!statusAdvert && !CC_SESSION_ID) {
193
+ for (const f of files) {
194
+ try { fs.readFileSync(f, 'utf8'); statusAdvert = f; break; } catch {}
195
+ }
196
+ }
197
+ } catch {}
198
+ if (statusAdvert && mappingPath && !needClaim) {
199
+ try { fs.writeFileSync(mappingPath, statusAdvert); } catch {}
200
+ }
201
+ }
202
+ if (!statusAdvert && !CC_SESSION_ID) {
203
+ statusAdvert = path.join(os.homedir(), '.claude', 'mixdog-status.json');
204
+ }
205
+
206
+ // ── Read port from advert ──────────────────────────────────────────────────
207
+ let statusPort = '';
208
+ try {
209
+ const advertContent = fs.readFileSync(statusAdvert, 'utf8');
210
+ statusPort = extract(advertContent, /"port"\s*:\s*([0-9]+)/);
211
+ } catch {}
212
+
213
+ if (needClaim && CC_SESSION_ID && statusPort) {
214
+ const claimPayload = { cc_session_id: CC_SESSION_ID };
215
+ if (CLIENT_HOST_PID) claimPayload.client_host_pid = CLIENT_HOST_PID;
216
+ const body = JSON.stringify(claimPayload);
217
+ try {
218
+ const req = http.request({
219
+ hostname: '127.0.0.1',
220
+ port: parseInt(statusPort, 10),
221
+ path: '/register-cc-session',
222
+ method: 'POST',
223
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
224
+ }, (res) => { res.resume(); });
225
+ req.on('error', () => {});
226
+ req.setTimeout(800, () => { try { req.destroy(); } catch {} });
227
+ req.write(body);
228
+ req.end();
229
+ } catch {}
230
+ }
231
+
232
+ // ── Fetch /bridge/status ───────────────────────────────────────────────────
233
+ let bridgeJson = '';
234
+ if (statusPort) {
235
+ bridgeJson = await new Promise(resolve => {
236
+ try {
237
+ const statusUrl = `http://127.0.0.1:${statusPort}/bridge/status?format=statusline-json`
238
+ + (CLIENT_HOST_PID ? `&clientHostPid=${CLIENT_HOST_PID}` : '');
239
+ const req = http.get(
240
+ statusUrl,
241
+ { timeout: 500 },
242
+ res => {
243
+ let data = '';
244
+ res.on('data', chunk => { data += chunk; });
245
+ res.on('end', () => resolve(data));
246
+ }
247
+ );
248
+ req.on('error', () => resolve(''));
249
+ req.on('timeout', () => { req.destroy(); resolve(''); });
250
+ } catch { resolve(''); }
251
+ });
252
+ }
253
+ if (!bridgeJson.startsWith('{')) bridgeJson = '';
254
+
255
+ if (!bridgeJson && process.env.MIXDOG_STATUSLINE_TRACE) {
256
+ try {
257
+ const traceDir = path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin');
258
+ if (fs.existsSync(traceDir)) {
259
+ const advertPresent = (() => { try { fs.accessSync(statusAdvert); return 'present'; } catch { return 'missing'; } })();
260
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
261
+ fs.appendFileSync(
262
+ path.join(traceDir, 'statusline-trace.log'),
263
+ `${ts} NOBRIDGE port=${statusPort || '?'} advert=${advertPresent}\n`
264
+ );
265
+ }
266
+ } catch {}
267
+ }
268
+
269
+ // ── Extract bridge fields ──────────────────────────────────────────────────
270
+ let bSessRoles = '';
271
+ let bSchedNextAt = '';
272
+ let bSchedNextName = '';
273
+ // Worker list carrying running/idle status alongside each tag, surfaced by
274
+ // the aggregator's sessions.workers segment. Falls back to roles (running-
275
+ // only) when an older aggregator payload lacks the workers array.
276
+ let bWorkers = [];
277
+
278
+ if (bridgeJson) {
279
+ const sessRaw = extract(bridgeJson, /"sessions"\s*:\s*\{[^}]*"roles"\s*:\s*\[([^\]]*)\]/);
280
+ if (sessRaw) bSessRoles = sessRaw.replace(/"/g, '').replace(/\s/g, '');
281
+ const workersRaw = extract(bridgeJson, /"workers"\s*:\s*\[([^\]]*)\]/);
282
+ if (workersRaw) {
283
+ // Parse [{"tag":"x","status":"running"},...] without a full JSON.parse
284
+ // of the whole payload (matches the existing regex-extract approach).
285
+ const re = /\{[^}]*?"tag"\s*:\s*"([^"]*)"[^}]*?"status"\s*:\s*"([^"]*)"[^}]*?\}/g;
286
+ let m;
287
+ while ((m = re.exec(workersRaw)) !== null) {
288
+ bWorkers.push({ tag: m[1], status: m[2] === 'idle' ? 'idle' : 'running' });
289
+ }
290
+ }
291
+ bSchedNextAt = extract(bridgeJson, /"next"\s*:\s*\{[^}]*"fireAt"\s*:\s*([0-9]+)/);
292
+ bSchedNextName = extract(bridgeJson, /"next"\s*:\s*\{[^}]*"name"\s*:\s*"([^"]*)"/);
293
+ }
294
+
295
+ // ── Format helpers ─────────────────────────────────────────────────────────
296
+ let modelStr = '';
297
+ if (CC_MODEL) {
298
+ let raw = CC_MODEL.replace('(1M context)', '(1M)');
299
+ if (raw.includes('Opus')) modelStr = 'Opus' + raw.slice(raw.indexOf('Opus') + 4);
300
+ else if (raw.includes('Sonnet')) modelStr = 'Sonnet' + raw.slice(raw.indexOf('Sonnet') + 6);
301
+ else if (raw.includes('Haiku')) modelStr = 'Haiku' + raw.slice(raw.indexOf('Haiku') + 5);
302
+ else modelStr = raw;
303
+ }
304
+ const modelShort = modelStr.split(' ')[0];
305
+ const effortStr = CC_EFFORT ? CC_EFFORT.toUpperCase() : '';
306
+
307
+ function roundPct(s) {
308
+ const n = parseFloat(s);
309
+ return Number.isFinite(n) ? Math.round(n) : null;
310
+ }
311
+
312
+ const ctxInt = roundPct(CC_CTX_USED);
313
+ const rl5hInt = roundPct(CC_RL_5H);
314
+ const rl7dInt = roundPct(CC_RL_7D);
315
+
316
+ function epochMsToHHMM(ms) {
317
+ const d = new Date(parseInt(ms, 10));
318
+ if (isNaN(d.getTime())) return '';
319
+ return d.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit', hour12: false });
320
+ }
321
+
322
+ const resetStr = CC_RL_5H_RESET ? epochMsToHHMM(CC_RL_5H_RESET * 1000) : '';
323
+ const schedNextHHMM = bSchedNextAt ? epochMsToHHMM(parseInt(bSchedNextAt, 10)) : '';
324
+
325
+ function colourPct(p) {
326
+ if (p >= 90) return `${RED}${p}%${R}`;
327
+ if (p >= 70) return `${YLW}${p}%${R}`;
328
+ return `${GRN}${p}%${R}`;
329
+ }
330
+
331
+ function makeBar(pct, cells) {
332
+ if (pct === null || cells <= 0) return '';
333
+ let filled = Math.floor(pct * cells / 100);
334
+ if (filled < 0) filled = 0;
335
+ if (filled > cells) filled = cells;
336
+ if (pct > 0 && filled === 0) filled = 1;
337
+ return '▓'.repeat(filled) + '░'.repeat(cells - filled);
338
+ }
339
+
340
+ // ── Build L1 ───────────────────────────────────────────────────────────────
341
+ const SEP = `${D}│${R}`;
342
+ const l1Parts = [];
343
+ function addL1(seg) { if (seg) l1Parts.push(seg); }
344
+
345
+ if (modelStr) {
346
+ const m = COLS >= 120 ? modelStr : modelShort;
347
+ if (effortStr) {
348
+ addL1(`${CYN}◆${R} ${B}${m}${R} ${D}·${R} ${B}${effortStr}${R}`);
349
+ } else {
350
+ addL1(`${CYN}◆${R} ${B}${m}${R}`);
351
+ }
352
+ }
353
+
354
+ if (ctxInt !== null) {
355
+ const fill = ctxInt >= 90 ? RED : ctxInt >= 70 ? YLW : GRN;
356
+ let barOut = '';
357
+ if (COLS >= 120) barOut = makeBar(ctxInt, 14);
358
+ else if (COLS >= 80) barOut = makeBar(ctxInt, 8);
359
+ if (barOut) {
360
+ const filledPart = barOut.replace(/░/g, '');
361
+ const emptyPart = barOut.replace(/▓/g, '');
362
+ const bar = `${fill}${filledPart}${R}${D}${emptyPart}${R}`;
363
+ addL1(`${bar} ${ctxInt}%`);
364
+ } else {
365
+ addL1(`${fill}${ctxInt}%${R}`);
366
+ }
367
+ }
368
+
369
+ if (rl5hInt !== null) {
370
+ addL1(`${D}5H${R} ${colourPct(rl5hInt)}`);
371
+ }
372
+ if (COLS >= 80) {
373
+ if (rl7dInt !== null) addL1(`${D}7D${R} ${colourPct(rl7dInt)}`);
374
+ if (resetStr) addL1(`${D}↻ ${resetStr}${R}`);
375
+ }
376
+
377
+ // ── Role classification ────────────────────────────────────────────────────
378
+ let workCount = 0;
379
+ let workOrder = '';
380
+ let hasCycle1 = false;
381
+ let hasCycle2 = false;
382
+ let hasCycle3 = false;
383
+ let hasSched = false;
384
+ let hasWebhook = false;
385
+ let hasExplorer = false;
386
+
387
+ function classifyMaint(role) {
388
+ switch (role) {
389
+ case 'cycle1-agent': hasCycle1 = true; return true;
390
+ case 'cycle2-agent': hasCycle2 = true; return true;
391
+ case 'cycle3-agent': hasCycle3 = true; return true;
392
+ case 'scheduler-task': hasSched = true; return true;
393
+ case 'webhook-handler': hasWebhook = true; return true;
394
+ case 'explorer': hasExplorer = true; return true;
395
+ default: return false;
396
+ }
397
+ }
398
+
399
+ // Idle worker tags (greyed in L2), threaded from the aggregator's
400
+ // sessions.workers[].status. Running user-workers still feed workCount /
401
+ // workOrder so the existing "N Running (tags)" badge is unchanged.
402
+ const idleWorkers = [];
403
+ if (bWorkers.length) {
404
+ // Preferred path: per-worker running/idle status from the aggregator.
405
+ for (const w of bWorkers) {
406
+ if (classifyMaint(w.tag)) continue; // maintenance → L1, not the worker badge
407
+ if (w.status === 'idle') {
408
+ idleWorkers.push(w.tag);
409
+ } else {
410
+ workCount++;
411
+ workOrder = workOrder ? `${workOrder}, ${w.tag}` : w.tag;
412
+ }
413
+ }
414
+ } else if (bSessRoles) {
415
+ // Fallback: legacy roles array (running-only, no idle/status info).
416
+ for (const role of bSessRoles.split(',')) {
417
+ if (!role) continue;
418
+ if (classifyMaint(role)) continue;
419
+ workCount++;
420
+ workOrder = workOrder ? `${workOrder}, ${role}` : role;
421
+ }
422
+ }
423
+
424
+ const maintParts = [];
425
+ if (hasCycle1) maintParts.push(`${GRN}↻${R} ${B}cycle1${R}`);
426
+ if (hasCycle2) maintParts.push(`${GRN}↻${R} ${B}cycle2${R}`);
427
+ if (hasCycle3) maintParts.push(`${GRN}↻${R} ${B}cycle3${R}`);
428
+ if (hasSched) maintParts.push(`${GRN}↻${R} ${B}scheduler${R}`);
429
+ if (hasWebhook) maintParts.push(`${GRN}↻${R} ${B}webhook${R}`);
430
+ if (hasExplorer) maintParts.push(`${GRN}↻${R} ${B}explorer${R}`);
431
+ if (maintParts.length) addL1(maintParts.join(' '));
432
+
433
+ // Background bash jobs: a running job is a `<jobId>.json` whose sibling
434
+ // `<jobId>.done` (written on exit) is absent. Surviving candidates are then
435
+ // liveness-filtered — the `.json` carries the wrapper `pid`; an orphaned job
436
+ // (wrapper crashed, `.done` never written, pid dead for days) is skipped via
437
+ // process.kill(pid, 0). Bounded per tick: candidates are ordered newest-first
438
+ // (the jobId embeds its spawn Date.now()) and at most JOB_SCAN_CAP of them are
439
+ // read — keeping each render O(cap), not O(total on-disk jobs). When the scan
440
+ // is truncated a trailing `+` overflow marker is appended to the count. One
441
+ // readFileSync/statSync per scanned job for the oldest startedAt (the .json is
442
+ // written at job start); tolerate the dir being missing and never throw.
443
+ const JOB_SCAN_CAP = 30;
444
+ const bashJobsSeg = (() => {
445
+ try {
446
+ const dir = path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin', 'shell-jobs');
447
+ const names = fs.readdirSync(dir);
448
+ const done = new Set();
449
+ const jobs = [];
450
+ const ownerByJob = new Map();
451
+ for (const n of names) {
452
+ if (n.endsWith('.done')) done.add(n.slice(0, -5));
453
+ else if (n.endsWith('.json')) jobs.push(n.slice(0, -5));
454
+ else {
455
+ // Owner sidecar `<jobId>.owner-<pid>` — a zero-byte marker whose NAME
456
+ // carries the owning CC host pid, written next to the .json at spawn.
457
+ const i = n.lastIndexOf('.owner-');
458
+ if (i > 0) { const pid = positiveInt(n.slice(i + 7)); if (pid) ownerByJob.set(n.slice(0, i), pid); }
459
+ }
460
+ }
461
+ // Owner-filter BEFORE the scan cap, from the directory listing alone: each
462
+ // job's owning claude.exe pid is read from its `.owner-<pid>` marker name
463
+ // (no JSON read), so another session's newer jobs can never evict ours at
464
+ // the cap. Only jobs whose marker pid equals THIS statusline's
465
+ // --client-host-pid survive; legacy jobs without a marker — and every job
466
+ // when no host pid was passed (CLIENT_HOST_PID_JOBS absent) — are excluded.
467
+ // Then ordered newest-first by the spawn timestamp embedded in
468
+ // `job_<ms>_<rand>`, so truncation drops only this session's oldest tail.
469
+ const jobStampMs = (id) => { const m = /^job_(\d+)/.exec(id); return m ? Number(m[1]) : 0; };
470
+ const candidates = jobs
471
+ .filter((id) => !done.has(id) && CLIENT_HOST_PID_JOBS && ownerByJob.get(id) === CLIENT_HOST_PID_JOBS)
472
+ .sort((a, b) => jobStampMs(b) - jobStampMs(a));
473
+ if (candidates.length === 0) return '';
474
+ const scan = candidates.slice(0, JOB_SCAN_CAP);
475
+ const truncated = candidates.length > JOB_SCAN_CAP;
476
+ let count = 0;
477
+ let oldestMs = Infinity;
478
+ for (const id of scan) {
479
+ const p = path.join(dir, `${id}.json`);
480
+ let pid, tmo, enforced;
481
+ try {
482
+ const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
483
+ pid = d.pid; tmo = Number(d.timeoutMs);
484
+ // Runtime enforcement proof: PS records timeoutEnforced:true; the
485
+ // posix wrapper touches <id>.enforced iff its `timeout` branch ran.
486
+ enforced = d.timeoutEnforced === true || fs.existsSync(path.join(dir, `${id}.enforced`));
487
+ }
488
+ catch { continue; } // unreadable/unparseable → skip
489
+ let st;
490
+ try { st = fs.statSync(p); }
491
+ catch { continue; }
492
+ // Deadline: the wrapper force-kills at timeoutMs, so a job older than
493
+ // timeoutMs + grace is dead even when its pid was recycled by an
494
+ // unrelated live process (pid-reuse-proof, mirrors the sweep). Trusted
495
+ // only when the record proves in-wrapper enforcement (timeoutEnforced).
496
+ if (enforced && Number.isFinite(tmo) && tmo > 0 && (Date.now() - st.mtimeMs) > tmo + 30 * 60_000) continue;
497
+ // kill(0, 0) probes the whole process group and "succeeds" — a
498
+ // malformed pid (0, "", []) must be rejected before the probe.
499
+ pid = Number(pid);
500
+ if (!Number.isInteger(pid) || pid <= 0) continue;
501
+ // Liveness: process.kill(pid, 0) succeeds or throws EPERM → alive;
502
+ // ESRCH/invalid pid → dead → skip.
503
+ let alive = false;
504
+ try { process.kill(pid, 0); alive = true; }
505
+ catch (e) { alive = e && e.code === 'EPERM'; }
506
+ if (!alive) continue;
507
+ count++;
508
+ if (st.mtimeMs < oldestMs) oldestMs = st.mtimeMs;
509
+ }
510
+ if (count === 0) return '';
511
+ let elapsed = '';
512
+ if (Number.isFinite(oldestMs)) {
513
+ const secs = Math.max(0, Math.floor((Date.now() - oldestMs) / 1000));
514
+ elapsed = secs < 60 ? ` ${secs}s` : ` ${Math.floor(secs / 60)}m`;
515
+ }
516
+ // `+` overflow marker: more live/recent candidates existed than the
517
+ // per-tick scan cap, so the rendered count is a floor, not the ground.
518
+ const overflow = truncated ? '+' : '';
519
+ return `${GREY}⚙ bash:${count}${overflow}${elapsed}${R}`;
520
+ } catch { return ''; }
521
+ })();
522
+ if (bashJobsSeg) addL1(bashJobsSeg);
523
+
524
+ // ── Build L2 ───────────────────────────────────────────────────────────────
525
+ const l2Parts = [];
526
+ function addL2(seg) { if (seg) l2Parts.push(seg); }
527
+
528
+ if (workCount > 0 && workOrder) {
529
+ addL2(`${GRN}●${R} ${B}${workCount} Running${R} ${D}(${R}${CYN}${workOrder}${R}${D})${R}`);
530
+ }
531
+
532
+ if (idleWorkers.length) {
533
+ // Idle workers: filled grey dot (●) + explicit 'idle' marker + tag list.
534
+ // Filled (not hollow ○) so the glyph matches the running ● weight; only
535
+ // the colour (grey vs green) distinguishes idle from running.
536
+ const idleTags = idleWorkers.join(', ');
537
+ addL2(`${GREY}● ${idleWorkers.length} idle (${idleTags})${R}`);
538
+ }
539
+ if (bSchedNextName && schedNextHHMM) {
540
+ addL2(`${YLW}⏰${R} ${B}${bSchedNextName}${R} ${D}${schedNextHHMM}${R}`);
541
+ }
542
+
543
+ const l1 = l1Parts.join(` ${SEP} `) || 'mixdog';
544
+ let l2 = l2Parts.join(` ${SEP} `);
545
+ if (l2 === 'Idle') l2 = '';
546
+
547
+ let out = l1 + '\n';
548
+ if (l2) out += l2 + '\n';
549
+ return out;
550
+ }