mixdog 1.2.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +19 -0
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/gate-scheduler.mjs +2 -2
  7. package/scripts/lib/isolated-test-env.mjs +16 -0
  8. package/scripts/lib/run-node-tests.mjs +5 -14
  9. package/scripts/provider-pingpong-live.mjs +9 -9
  10. package/src/agents/advisor/AGENT.md +4 -4
  11. package/src/agents/advisor/agent.json +1 -1
  12. package/src/agents/reviewer/AGENT.md +5 -4
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/defaults/shipped-history.json +17 -0
  15. package/src/defaults/skills/browser-use/SKILL.md +7 -1
  16. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  17. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  18. package/src/defaults/skills/setup/references/actions.md +11 -4
  19. package/src/defaults/skills/setup-guide/SKILL.md +175 -0
  20. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  21. package/src/defaults/skills/visualize/SKILL.md +3 -2
  22. package/src/lib/rules-builder.cjs +56 -7
  23. package/src/output-styles/common.md +22 -6
  24. package/src/output-styles/detailed.md +7 -5
  25. package/src/output-styles/minimal.md +7 -3
  26. package/src/output-styles/simple.md +2 -3
  27. package/src/rules/agent/AGENT.md +7 -1
  28. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  29. package/src/rules/agent-mode/MIXY.md +21 -0
  30. package/src/rules/agent-mode/ROUTINE.md +6 -0
  31. package/src/rules/lead/LEAD.md +9 -4
  32. package/src/rules/shared/10-tool-workflow.md +2 -1
  33. package/src/rules/shared/60-verification.md +4 -5
  34. package/src/rules/shared/70-delivery.md +3 -0
  35. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  37. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  39. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +182 -13
  40. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +5 -3
  41. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  42. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -0
  43. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +58 -64
  44. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  45. package/src/runtime/agent/orchestrator/mcp/client.mjs +122 -15
  46. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  49. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  50. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  51. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  53. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +41 -19
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  62. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +11 -5
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  65. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  66. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  69. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  70. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  72. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  80. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  83. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  84. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  86. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  88. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  92. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  94. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  95. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  96. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  97. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  98. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  99. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  100. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +98 -4
  101. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  102. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  103. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +15 -8
  104. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -4
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
  109. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -20
  110. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  111. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  112. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  113. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  114. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  115. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  116. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  117. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +162 -0
  118. package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -0
  119. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  120. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  121. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  122. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  123. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  124. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  125. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  126. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +165 -0
  127. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  128. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  129. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  130. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  131. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  132. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  133. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  134. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  135. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -1
  136. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +82 -33
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +11 -4
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  140. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +7 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +6 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  146. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  147. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  148. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  149. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  150. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  151. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  152. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  153. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  154. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -5
  155. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -8
  156. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  157. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  158. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  159. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +24 -0
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  163. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -0
  166. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  167. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  168. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  169. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  171. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  173. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  174. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  175. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
  176. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +325 -0
  177. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +84 -21
  178. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  179. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  180. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  181. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  182. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  183. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  184. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  185. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  186. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +44 -1
  189. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  190. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  191. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  197. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  198. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  199. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  200. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  201. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  203. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  207. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  208. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  209. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  210. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  211. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  212. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  213. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  214. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  215. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  216. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  217. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  218. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  219. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  220. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  221. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +123 -32
  222. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  224. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  225. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  226. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  227. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  228. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  229. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  230. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  231. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  232. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +64 -2
  233. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +10 -2
  234. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  235. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +8 -0
  236. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -1
  237. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  238. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +104 -55
  239. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  240. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  241. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  242. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  243. package/src/runtime/attachments/office-extract.mjs +11 -9
  244. package/src/runtime/browser-bridge/client.mjs +24 -0
  245. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  246. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  247. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  248. package/src/runtime/channels/lib/webhook.mjs +0 -1
  249. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  250. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  251. package/src/runtime/computer-bridge/client.mjs +28 -1
  252. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  253. package/src/runtime/connectors/google-mcp.mjs +446 -0
  254. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  255. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  256. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  257. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  258. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  259. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  260. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  261. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  262. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  263. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  264. package/src/runtime/memory/lib/memory.mjs +7 -1
  265. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  266. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  268. package/src/runtime/memory/lib/tool-call-handler.mjs +9 -5
  269. package/src/runtime/memory/tool-defs.mjs +5 -0
  270. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  271. package/src/runtime/office/core/tabular.mjs +1 -1
  272. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  273. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  274. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  275. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  276. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  277. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  278. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  279. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  280. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  281. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  282. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  283. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  284. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  285. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  286. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  287. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  288. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  289. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  290. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  291. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  292. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  293. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  294. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  295. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  296. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  297. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  298. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  299. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  300. package/src/runtime/shared/agent-avatar.mjs +320 -0
  301. package/src/runtime/shared/agent-route-config.d.mts +3 -0
  302. package/src/runtime/shared/agent-route-config.mjs +16 -0
  303. package/src/runtime/shared/atomic-file.mjs +36 -14
  304. package/src/runtime/shared/background-tasks.mjs +60 -20
  305. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  306. package/src/runtime/shared/config.mjs +8 -11
  307. package/src/runtime/shared/connector-accounts.mjs +15 -0
  308. package/src/runtime/shared/connector-catalog.d.mts +52 -0
  309. package/src/runtime/shared/connector-catalog.mjs +230 -0
  310. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  311. package/src/runtime/shared/connector-triggers.mjs +43 -0
  312. package/src/runtime/shared/file-lock.mjs +7 -1
  313. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  314. package/src/runtime/shared/file-permissions.d.mts +19 -4
  315. package/src/runtime/shared/file-permissions.mjs +310 -4
  316. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  317. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  318. package/src/runtime/shared/held-request-host.d.mts +12 -0
  319. package/src/runtime/shared/held-request-host.mjs +71 -0
  320. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  321. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  322. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  323. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  324. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  325. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  326. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  327. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  328. package/src/runtime/shared/notify-trace.mjs +8 -1
  329. package/src/runtime/shared/plain-preview.d.mts +1 -0
  330. package/src/runtime/shared/plain-preview.mjs +28 -0
  331. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  332. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  333. package/src/runtime/shared/process-runner.mjs +38 -0
  334. package/src/runtime/shared/provider-accounts.mjs +2 -1
  335. package/src/runtime/shared/provider-api-key.mjs +0 -1
  336. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  337. package/src/runtime/shared/resource-admission.mjs +60 -18
  338. package/src/runtime/shared/routine-limits.d.mts +11 -0
  339. package/src/runtime/shared/routine-limits.mjs +21 -0
  340. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  341. package/src/runtime/shared/routine-trigger.mjs +44 -0
  342. package/src/runtime/shared/run-child.mjs +84 -0
  343. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  344. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  345. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  346. package/src/runtime/shared/singleton-owner.mjs +126 -42
  347. package/src/runtime/shared/slow-span.mjs +4 -12
  348. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  349. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  350. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  351. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  352. package/src/runtime/shared/turn-changes.mjs +0 -8
  353. package/src/runtime/shared/usage-readout-totals.mjs +56 -0
  354. package/src/runtime/shared/user-data-guard.mjs +131 -34
  355. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  356. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  357. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  358. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  359. package/src/runtime/tidy/runners/biome.mjs +3 -1
  360. package/src/runtime/tidy/shadow.mjs +2 -1
  361. package/src/runtime/web-search/index.mjs +5 -7
  362. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  363. package/src/runtime/web-search/tool-defs.mjs +6 -1
  364. package/src/session-runtime/agent-home-delivery.mjs +459 -0
  365. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  366. package/src/session-runtime/agent-room-delivery.mjs +92 -0
  367. package/src/session-runtime/agent-task-sessions.mjs +200 -0
  368. package/src/session-runtime/boot/apis.mjs +50 -18
  369. package/src/session-runtime/boot/facade.mjs +5 -8
  370. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  371. package/src/session-runtime/boot/tools.mjs +3 -0
  372. package/src/session-runtime/channel-config-api.mjs +53 -5
  373. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  374. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  375. package/src/session-runtime/held-requests.mjs +228 -0
  376. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  377. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  378. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  379. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +31 -2
  380. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  381. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  382. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  383. package/src/session-runtime/lifecycle/session-switch/resume.mjs +11 -4
  384. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  385. package/src/session-runtime/lifecycle/shared.mjs +25 -2
  386. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  387. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  388. package/src/session-runtime/lifecycle-api.mjs +3 -1
  389. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  390. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  391. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  392. package/src/session-runtime/model-route/set-route.mjs +14 -3
  393. package/src/session-runtime/owned-automation-run.mjs +229 -0
  394. package/src/session-runtime/provider-auth/login.mjs +10 -6
  395. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  396. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  397. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  398. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  399. package/src/session-runtime/provider-models.mjs +76 -10
  400. package/src/session-runtime/provider-readiness.mjs +4 -0
  401. package/src/session-runtime/resource-api.mjs +5 -0
  402. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  403. package/src/session-runtime/resource-skills-api.mjs +12 -0
  404. package/src/session-runtime/room-member-gate.mjs +48 -0
  405. package/src/session-runtime/routine-home-turn.mjs +156 -0
  406. package/src/session-runtime/routine-owner.mjs +48 -0
  407. package/src/session-runtime/schedule-session-run.mjs +30 -64
  408. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  409. package/src/session-runtime/services/agent-tool/helpers.mjs +10 -0
  410. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  411. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  412. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  413. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  414. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  415. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  416. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  417. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  418. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  419. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  420. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  422. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  423. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  424. package/src/session-runtime/services/agent-tool.mjs +20 -0
  425. package/src/session-runtime/services/channel-admin.mjs +93 -24
  426. package/src/session-runtime/services/memory-access.mjs +37 -0
  427. package/src/session-runtime/services/provider-admin.mjs +166 -59
  428. package/src/session-runtime/services/schedule-access.mjs +91 -0
  429. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  430. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  431. package/src/session-runtime/session-create/session-options.mjs +34 -7
  432. package/src/session-runtime/session-hooks.mjs +16 -8
  433. package/src/session-runtime/setup-tool/executor.mjs +68 -6
  434. package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
  435. package/src/session-runtime/setup-tool/settings-contract.mjs +8 -6
  436. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  437. package/src/session-runtime/skills-api.mjs +16 -1
  438. package/src/session-runtime/tool-policy-refresh.mjs +238 -29
  439. package/src/session-runtime/turn/session-ops.mjs +10 -3
  440. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +11 -2
  441. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +3 -0
  442. package/src/session-runtime/usage-stats-api.mjs +6 -1
  443. package/src/session-runtime/webhook-session-run.mjs +49 -67
  444. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  445. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +556 -22
  446. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  447. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  448. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  449. package/src/standalone/daemon-agent-control.mjs +1 -8
  450. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  451. package/src/standalone/daemon-owner.mjs +117 -25
  452. package/src/standalone/daemon-session-control.mjs +11 -2
  453. package/src/standalone/daemon.mjs +61 -17
  454. package/src/standalone/session-call-cache.mjs +6 -0
  455. package/src/standalone/session-protocol.mjs +28 -1
  456. package/src/standalone/session-service/retention/busy.mjs +7 -2
  457. package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
  458. package/src/standalone/session-service.mjs +19 -0
  459. package/src/standalone/session-transport/routes.mjs +7 -6
  460. package/src/tui/app/model-options.mjs +39 -2
  461. package/src/tui/app/model-picker.mjs +16 -2
  462. package/src/tui/app/provider-setup-picker.mjs +1 -2
  463. package/src/tui/app/slash-commands.mjs +20 -0
  464. package/src/tui/dist/index.mjs +487 -128
  465. package/src/tui/session/context-state.mjs +6 -1
  466. package/src/tui/session/notice-surface.mjs +35 -4
  467. package/src/tui/session/notice-text.mjs +0 -1
  468. package/src/tui/session/queue-helpers.mjs +2 -1
  469. package/src/tui/session/session-api/extensions.mjs +4 -0
  470. package/src/tui/session/session-api/intake/submission.mjs +12 -13
  471. package/src/tui/session/session-api/integrations.mjs +9 -5
  472. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  473. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  474. package/src/tui/session/session-api/routes.mjs +48 -3
  475. package/src/tui/session/session-flow/drain.mjs +20 -3
  476. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  477. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  478. package/src/tui/session/session-flow/steering.mjs +104 -24
  479. package/src/tui/session/session-flow.mjs +2 -0
  480. package/src/tui/session/tool-approval.mjs +26 -5
  481. package/src/tui/session/transcript-intake.mjs +6 -2
  482. package/src/tui/session/transcript-spill.mjs +5 -1
  483. package/src/tui/session/tui-steering-persist.mjs +65 -42
  484. package/src/tui/session/turn-live-call.mjs +56 -0
  485. package/src/tui/session/turn-stream.mjs +33 -0
  486. package/src/tui/session/turn.mjs +50 -7
  487. package/src/ui/statusline.mjs +1 -6
  488. package/src/ui/streaming-markdown-heal.mjs +24 -7
  489. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  490. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  491. package/src/workflows/default/WORKFLOW.md +9 -13
  492. package/src/session-runtime/automation-agents.mjs +0 -94
  493. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -0,0 +1,320 @@
1
+ // The Mixdog mascot: a blocky pixel creature on a 16x12 grid, painted from the
2
+ // string layers in agent-avatar-sprites.mjs (design/mockups/mixy-art). Every
3
+ // look is the same body block; the species (ears, marks, tail, legs), an
4
+ // accessory, the expression and the colour vary. An agent's `agent.json`
5
+ // stores its pick as `avatar: { kind: 'mascot', species, accessory,
6
+ // expression, color }` (an older `variant` reads as its mapped look), or its
7
+ // own images per motion state as `avatar: { kind: 'custom', states: { idle,
8
+ // typing?, message? } }`. An agent without one gets a stable look from its id.
9
+ import { MIXY_AGENT_ID } from '../agent/orchestrator/session/agent-home.mjs';
10
+ import { MASCOT_SPRITES } from './agent-avatar-sprites.mjs';
11
+
12
+ /** The palette an avatar colour is chosen from. */
13
+ export const AGENT_AVATAR_COLORS = Object.freeze([
14
+ '#e6e6e6',
15
+ '#9a9a9a',
16
+ '#f06a5f',
17
+ '#f59a42',
18
+ '#f2c94c',
19
+ '#5cc98a',
20
+ '#3fc1c9',
21
+ '#5b9bff',
22
+ '#9b7bff',
23
+ '#ef6fb0',
24
+ ]);
25
+
26
+ /** Every species a user can pick: the six dogs. Mixy's own (`mixy`) is not one of them. */
27
+ export const MASCOT_SPECIES = Object.freeze(['labrador', 'shepherd', 'terrier', 'corgi', 'husky', 'shiba']);
28
+ export const MASCOT_ACCESSORIES = Object.freeze(['none', 'collar', 'glasses', 'beanie', 'scarf', 'bow']);
29
+ /** Picks that were removed, read as their nearest remaining option. */
30
+ const RETIRED_SPECIES = { dalmatian: 'labrador', cat: 'labrador', bird: 'labrador', robot: 'labrador' };
31
+ const RETIRED_ACCESSORIES = { headset: 'glasses' };
32
+ export const MASCOT_EXPRESSIONS = Object.freeze(['calm', 'happy', 'wink', 'sleepy']);
33
+
34
+ /** Mixy, the primary assistant: its own silver pup with one ear up and a
35
+ * slate collar with the gold star medal. Never offered in the picker. */
36
+ export const MIXY_AVATAR = Object.freeze({
37
+ kind: 'mascot',
38
+ species: 'mixy',
39
+ accessory: 'none',
40
+ expression: 'calm',
41
+ color: '#e6e6e6',
42
+ });
43
+
44
+ /** The fixed inks a sprite letter names; B, D, L and E depend on the look. */
45
+ export const MASCOT_INKS = Object.freeze({
46
+ K: '#141414',
47
+ G: '#f2c94c',
48
+ R: '#e5534b',
49
+ O: '#f59a42',
50
+ W: '#f4f4f4',
51
+ C: '#3fc1c9',
52
+ M: '#8a8a90',
53
+ S: '#2a2b30',
54
+ P: '#ef8fb4',
55
+ N: '#f06a5f',
56
+ n: '#c4544c',
57
+ r: '#b94039',
58
+ F: '#ef6fb0',
59
+ f: '#b8497f',
60
+ });
61
+
62
+ /** `hex` with every channel scaled by `k`. */
63
+ export function shadeHex(hex, k) {
64
+ const n = Number.parseInt(hex.slice(1), 16);
65
+ const channel = (shift) =>
66
+ Math.round(((n >> shift) & 255) * k)
67
+ .toString(16)
68
+ .padStart(2, '0');
69
+ return `#${channel(16)}${channel(8)}${channel(0)}`;
70
+ }
71
+
72
+ /** `hex` mixed `k` of the way to white. */
73
+ function liftHex(hex, k) {
74
+ const n = Number.parseInt(hex.slice(1), 16);
75
+ const channel = (shift) => {
76
+ const value = (n >> shift) & 255;
77
+ return Math.round(value + (255 - value) * k)
78
+ .toString(16)
79
+ .padStart(2, '0');
80
+ };
81
+ return `#${channel(16)}${channel(8)}${channel(0)}`;
82
+ }
83
+
84
+ // The 12 looks before species existed, `<ears>-<expression>-<plain|collar>`:
85
+ // each reads as the species with those ears, the collar as an accessory.
86
+ const LEGACY_EARS = { floppy: 'labrador', perky: 'shepherd', folded: 'terrier' };
87
+ const LEGACY_VARIANTS = Object.keys(LEGACY_EARS).flatMap((ears) =>
88
+ ['calm', 'happy'].flatMap((expression) => ['plain', 'collar'].map((extra) => `${ears}-${expression}-${extra}`))
89
+ );
90
+
91
+ /** An older `variant` as its species, accessory and expression; null if unknown. */
92
+ export function mascotFromVariant(variant) {
93
+ if (!LEGACY_VARIANTS.includes(variant)) return null;
94
+ const [ears, expression, extra] = variant.split('-');
95
+ return { species: LEGACY_EARS[ears], accessory: extra === 'collar' ? 'collar' : 'none', expression };
96
+ }
97
+
98
+ /** `species-accessory-expression`: a look's key (data attributes, tests). */
99
+ export const mascotLook = (avatar) => `${avatar.species}-${avatar.accessory}-${avatar.expression}`;
100
+
101
+ function layerCells(rows, color, eye) {
102
+ if (!rows) return [];
103
+ const ink = (key) => {
104
+ if (key === 'B') return color;
105
+ if (key === 'D') return shadeHex(color, 0.7);
106
+ if (key === 'L') return liftHex(color, 0.35);
107
+ return MASCOT_INKS[key === 'E' ? eye : key];
108
+ };
109
+ const cells = [];
110
+ rows.forEach((row, y) => {
111
+ for (let x = 0; x < row.length; x += 1) if (row[x] !== '.') cells.push({ x, y, fill: ink(row[x]) });
112
+ });
113
+ return cells;
114
+ }
115
+
116
+ /** A look's cells by part, in paint order — the groups the renderer draws
117
+ * (tail, legs, then the bobbing body with its ears, marks, eyes and
118
+ * accessory). `blink` is the closed eyes an idle blink shows. Null for an
119
+ * unknown species. */
120
+ export function mascotParts(avatar) {
121
+ const species = MASCOT_SPRITES.species[avatar.species];
122
+ if (!species) return null;
123
+ const paint = (rows) => layerCells(rows, avatar.color, species.eye);
124
+ return {
125
+ tail: paint(species.tail && MASCOT_SPRITES.tails[species.tail]),
126
+ legs: paint(MASCOT_SPRITES.legs[species.legs]),
127
+ body: paint(MASCOT_SPRITES.body),
128
+ ear: paint(species.ears),
129
+ marks: paint(species.marks),
130
+ eyes: paint(MASCOT_SPRITES.eyes[avatar.expression]),
131
+ blink: paint(MASCOT_SPRITES.eyes.sleepy),
132
+ accessory: paint(MASCOT_SPRITES.accessories[avatar.accessory]),
133
+ };
134
+ }
135
+
136
+ /** The look as one still frame: each cell's final fill. [] when unknown. */
137
+ export function mascotCells(avatar) {
138
+ const parts = mascotParts(avatar);
139
+ if (!parts) return [];
140
+ const cells = new Map();
141
+ for (const part of ['tail', 'legs', 'body', 'ear', 'marks', 'eyes', 'accessory']) {
142
+ for (const cell of parts[part]) cells.set(`${cell.y},${cell.x}`, cell);
143
+ }
144
+ return [...cells.values()];
145
+ }
146
+
147
+ /** Cells as one SVG path per fill, each row's run one rectangle. */
148
+ export function cellPaths(cells) {
149
+ const byFill = new Map();
150
+ for (const { x, y, fill } of cells) {
151
+ const rows = byFill.get(fill) ?? new Map();
152
+ byFill.set(fill, rows);
153
+ rows.set(y, [...(rows.get(y) ?? []), x]);
154
+ }
155
+ return [...byFill].map(([fill, rows]) => {
156
+ let d = '';
157
+ for (const [y, xs] of [...rows].sort(([a], [b]) => a - b)) {
158
+ const sorted = [...new Set(xs)].sort((a, b) => a - b);
159
+ for (let i = 0; i < sorted.length; ) {
160
+ let j = i;
161
+ while (j + 1 < sorted.length && sorted[j + 1] === sorted[j] + 1) j += 1;
162
+ const run = j - i + 1;
163
+ d += `M${sorted[i]} ${y}h${run}v1h-${run}z`;
164
+ i = j + 1;
165
+ }
166
+ }
167
+ return { fill, d };
168
+ });
169
+ }
170
+
171
+ /** The motion states an avatar shows today, `idle` first (a custom avatar's
172
+ * one required image, and every missing state's fallback). */
173
+ export const AGENT_AVATAR_STATES = Object.freeze(['idle', 'typing', 'message']);
174
+
175
+ /** The single image avatar before per-state images: a PNG beside AGENT.md
176
+ * under a content-hash name. Still read, as a custom avatar's idle image. */
177
+ export const AGENT_AVATAR_IMAGE_FILE = /^avatar-[0-9a-f]{12}\.png$/;
178
+ /** One state's image in `agents/<id>/avatar/`, under a content-hash name (a
179
+ * new upload is a new name, so caches by name never go stale). An animated
180
+ * one keeps its still first frame beside it (avatarPosterFile). */
181
+ export const AGENT_AVATAR_STATE_FILE = /^(idle|typing|message)-[0-9a-f]{12}\.(png|gif|webp)$/;
182
+ export const AGENT_AVATAR_DIR = 'avatar';
183
+ /** The largest image upload accepted, and the largest side of an animation. */
184
+ export const AGENT_AVATAR_IMAGE_MAX_BYTES = 1024 * 1024;
185
+ export const AGENT_AVATAR_IMAGE_MAX_SIDE = 512;
186
+
187
+ /** A state image's path under the agent's folder ('/'-separated). */
188
+ export const agentAvatarStatePath = (file) =>
189
+ AGENT_AVATAR_IMAGE_FILE.test(file) ? file : `${AGENT_AVATAR_DIR}/${file}`;
190
+ /** The still first frame stored beside an animated state image. */
191
+ export const avatarPosterFile = (file) => file.replace(/\.(png|gif|webp)$/, '.poster.png');
192
+ /** The image a custom avatar shows in `state`: its own, else idle's. */
193
+ export const customAvatarStateFile = (avatar, state) => avatar.states[state] ?? avatar.states.idle;
194
+
195
+ const u16le = (bytes, at) => bytes[at] | (bytes[at + 1] << 8);
196
+ const u24le = (bytes, at) => bytes[at] | (bytes[at + 1] << 8) | (bytes[at + 2] << 16);
197
+ const u32be = (bytes, at) => ((bytes[at] << 24) | (bytes[at + 1] << 16) | (bytes[at + 2] << 8) | bytes[at + 3]) >>> 0;
198
+ const ascii = (bytes, at, length) => String.fromCharCode(...bytes.subarray(at, at + length));
199
+
200
+ /** An upload's format and size, read from its header bytes: a PNG (an APNG
201
+ * counts as animated), a GIF (always treated as animated) or a WebP; null
202
+ * for anything else — SVG and every other markup included. */
203
+ export function avatarImageInfo(bytes) {
204
+ if (!(bytes instanceof Uint8Array) || bytes.length < 30) return null;
205
+ if (u32be(bytes, 0) === 0x89504e47 && u32be(bytes, 4) === 0x0d0a1a0a && ascii(bytes, 12, 4) === 'IHDR') {
206
+ let animated = false;
207
+ for (let at = 8; at + 8 <= bytes.length; at += 12 + u32be(bytes, at)) {
208
+ const type = ascii(bytes, at + 4, 4);
209
+ if (type === 'acTL') animated = true;
210
+ if (type === 'IDAT' || type === 'IEND') break;
211
+ }
212
+ return { format: 'png', width: u32be(bytes, 16), height: u32be(bytes, 20), animated };
213
+ }
214
+ if (ascii(bytes, 0, 6) === 'GIF87a' || ascii(bytes, 0, 6) === 'GIF89a') {
215
+ return { format: 'gif', width: u16le(bytes, 6), height: u16le(bytes, 8), animated: true };
216
+ }
217
+ if (ascii(bytes, 0, 4) === 'RIFF' && ascii(bytes, 8, 4) === 'WEBP') {
218
+ const chunk = ascii(bytes, 12, 4);
219
+ if (chunk === 'VP8X') {
220
+ const animated = (bytes[20] & 0x02) !== 0;
221
+ return { format: 'webp', width: u24le(bytes, 24) + 1, height: u24le(bytes, 27) + 1, animated };
222
+ }
223
+ if (chunk === 'VP8 ') {
224
+ return { format: 'webp', width: u16le(bytes, 26) & 0x3fff, height: u16le(bytes, 28) & 0x3fff, animated: false };
225
+ }
226
+ if (chunk === 'VP8L') {
227
+ const bits = (bytes[21] | (bytes[22] << 8) | (bytes[23] << 16) | (bytes[24] << 24)) >>> 0;
228
+ return { format: 'webp', width: (bits & 0x3fff) + 1, height: ((bits >>> 14) & 0x3fff) + 1, animated: false };
229
+ }
230
+ }
231
+ return null;
232
+ }
233
+
234
+ /** Why bytes cannot be a state image ('' when they can): not a PNG, GIF or
235
+ * WebP, over 1 MB, not square, or over 512px. */
236
+ export function agentAvatarImageProblem(bytes) {
237
+ const info = avatarImageInfo(bytes);
238
+ if (!info) return 'must be a PNG, GIF or WebP image';
239
+ if (bytes.length > AGENT_AVATAR_IMAGE_MAX_BYTES) return 'is larger than 1 MB';
240
+ if (!info.width || info.width !== info.height) return 'must be square';
241
+ if (info.width > AGENT_AVATAR_IMAGE_MAX_SIDE) return 'must be 512px or smaller';
242
+ return '';
243
+ }
244
+
245
+ function normalizeCustom(states) {
246
+ if (!states || typeof states !== 'object') return null;
247
+ const kept = {};
248
+ for (const state of AGENT_AVATAR_STATES) {
249
+ const file = states[state];
250
+ if (file == null) continue;
251
+ const own = typeof file === 'string' && AGENT_AVATAR_STATE_FILE.test(file) && file.startsWith(`${state}-`);
252
+ const legacy = state === 'idle' && typeof file === 'string' && AGENT_AVATAR_IMAGE_FILE.test(file);
253
+ if (!own && !legacy) return null;
254
+ kept[state] = file;
255
+ }
256
+ return kept.idle ? { kind: 'custom', states: kept } : null;
257
+ }
258
+
259
+ const pick = (value, list, fallback) => (value == null ? fallback : list.includes(value) ? value : null);
260
+
261
+ /** A stored avatar as the app understands it, or null when it is not one. An
262
+ * older single image reads as a custom avatar with only its idle image; an
263
+ * older mascot `variant` as its mapped species, accessory and expression. */
264
+ export function normalizeAgentAvatar(value) {
265
+ if (!value || typeof value !== 'object') return null;
266
+ if (value.kind === 'image') {
267
+ return typeof value.file === 'string' && AGENT_AVATAR_IMAGE_FILE.test(value.file)
268
+ ? { kind: 'custom', states: { idle: value.file } }
269
+ : null;
270
+ }
271
+ if (value.kind === 'custom') return normalizeCustom(value.states);
272
+ if (value.kind !== 'mascot') return null;
273
+ const color = typeof value.color === 'string' ? value.color.toLowerCase() : '';
274
+ if (!AGENT_AVATAR_COLORS.includes(color)) return null;
275
+ const look =
276
+ value.species == null && typeof value.variant === 'string'
277
+ ? mascotFromVariant(value.variant)
278
+ : {
279
+ species: pick(
280
+ RETIRED_SPECIES[value.species] ?? value.species,
281
+ [...MASCOT_SPECIES, MIXY_AVATAR.species],
282
+ null
283
+ ),
284
+ accessory: pick(RETIRED_ACCESSORIES[value.accessory] ?? value.accessory, MASCOT_ACCESSORIES, 'none'),
285
+ expression: pick(value.expression, MASCOT_EXPRESSIONS, 'calm'),
286
+ };
287
+ if (!look?.species || !look.accessory || !look.expression) return null;
288
+ return { kind: 'mascot', ...look, color };
289
+ }
290
+
291
+ // FNV-1a: a small stable string hash, the same on every platform.
292
+ function hashId(id) {
293
+ let hash = 0x811c9dc5;
294
+ for (const char of String(id)) {
295
+ hash ^= char.codePointAt(0);
296
+ hash = Math.imul(hash, 0x01000193) >>> 0;
297
+ }
298
+ return hash;
299
+ }
300
+
301
+ /** The look of an agent that never picked one: fixed by its id — the
302
+ * species, accessory and colour it had before species existed (its old
303
+ * variant, mapped), with the neutral face every default wears. */
304
+ export function defaultAgentAvatar(id) {
305
+ const hash = hashId(id);
306
+ return {
307
+ kind: 'mascot',
308
+ ...mascotFromVariant(LEGACY_VARIANTS[hash % LEGACY_VARIANTS.length]),
309
+ expression: 'calm',
310
+ color: AGENT_AVATAR_COLORS[Math.floor(hash / LEGACY_VARIANTS.length) % AGENT_AVATAR_COLORS.length],
311
+ };
312
+ }
313
+
314
+ /** What an agent shows: its own images, else Mixy's fixed look (for Mixy),
315
+ * its own mascot pick, or its id default. */
316
+ export function agentAvatar(id, stored) {
317
+ const own = normalizeAgentAvatar(stored);
318
+ if (id === MIXY_AGENT_ID) return own ?? { ...MIXY_AVATAR };
319
+ return own ?? defaultAgentAvatar(id);
320
+ }
@@ -0,0 +1,3 @@
1
+ // Types for the desktop's one import of agent-route-config.mjs.
2
+ export const WORKFLOW_AGENT_KIND: 'agent';
3
+ export function isContactAgentId(value: unknown, kind?: unknown): boolean;
@@ -16,6 +16,22 @@ export const DEFAULT_DISABLED_AGENT_IDS = Object.freeze([
16
16
  // runs only once the user turns it on and picks its model.
17
17
  export const DEFAULT_DISABLED_AGENT_ROSTER = Object.freeze([...DEFAULT_DISABLED_AGENT_IDS, 'maintainer']);
18
18
 
19
+ /** The AGENT.md frontmatter `kind` the Workflows page writes when it creates
20
+ * an agent: a workflow delegation role, never a contact (봇). */
21
+ export const WORKFLOW_AGENT_KIND = 'agent';
22
+
23
+ /** A contact agent (a 봇): one the user made — not Mixy ('lead'), not a
24
+ * shipped role (the starter sub-agents Worker, Reviewer, … and the
25
+ * Maintainer), and not marked `kind: agent` (made on the Workflows page).
26
+ * `kind` is the definition's frontmatter `kind`; an unmarked custom agent
27
+ * stays a contact. The one rule for the runtime (delegation, agent catalogs)
28
+ * and the desktop (contacts, agent lists). */
29
+ export function isContactAgentId(value, kind) {
30
+ const id = typeof value === 'string' ? value.trim().toLowerCase() : '';
31
+ const marked = typeof kind === 'string' && kind.trim().toLowerCase() === WORKFLOW_AGENT_KIND;
32
+ return Boolean(id) && id !== 'lead' && !DEFAULT_DISABLED_AGENT_ROSTER.includes(id) && !marked;
33
+ }
34
+
19
35
  function record(value) {
20
36
  return isPlainObject(value) ? value : {};
21
37
  }
@@ -19,7 +19,8 @@ import {
19
19
  unlink as unlinkAsync,
20
20
  writeFile as writeFileAsync,
21
21
  } from 'node:fs/promises';
22
- import { dirname, basename, join } from 'node:path';
22
+ import { tmpdir } from 'node:os';
23
+ import { dirname, basename, join, relative, isAbsolute, resolve } from 'node:path';
23
24
  import { randomBytes } from 'node:crypto';
24
25
  import { DEFAULT_BACKOFFS_MS, recordLockTiming, withFileLock, withFileLockSync } from './file-lock.mjs';
25
26
  import {
@@ -88,21 +89,30 @@ function nextRenameDelayMs(err, attempt, backoffs) {
88
89
  return backoffs[attempt] + Math.floor(Math.random() * Math.min(50, Math.max(1, backoffs[attempt])));
89
90
  }
90
91
 
92
+ /**
93
+ * Attempt `attempt` (0-based) of the renameWithRetry schedule: null when the
94
+ * rename landed, else the delay before the next attempt. Throws the rename's
95
+ * own error when that error or the attempt count ends the loop — the same
96
+ * final error renameWithRetrySync throws. A caller that must not block waits
97
+ * the delay asynchronously between attempts.
98
+ */
99
+ export function renameAttempt(src, dst, attempt, opts = {}) {
100
+ try {
101
+ renameSync(src, dst);
102
+ return null;
103
+ } catch (err) {
104
+ const delayMs = nextRenameDelayMs(err, attempt, renameBackoffs(opts));
105
+ if (delayMs === null) throw err;
106
+ return delayMs;
107
+ }
108
+ }
109
+
91
110
  export function renameWithRetrySync(src, dst, opts = {}) {
92
- const backoffs = renameBackoffs(opts);
93
- let lastErr = null;
94
- for (let attempt = 0; attempt <= backoffs.length; attempt++) {
95
- try {
96
- renameSync(src, dst);
97
- return true;
98
- } catch (err) {
99
- lastErr = err;
100
- const delayMs = nextRenameDelayMs(err, attempt, backoffs);
101
- if (delayMs === null) break;
102
- sleepSync(delayMs);
103
- }
111
+ for (let attempt = 0; ; attempt++) {
112
+ const delayMs = renameAttempt(src, dst, attempt, opts);
113
+ if (delayMs === null) return true;
114
+ sleepSync(delayMs);
104
115
  }
105
- throw lastErr;
106
116
  }
107
117
 
108
118
  async function renameWithRetry(src, dst, opts = {}) {
@@ -155,7 +165,18 @@ async function fsyncPathAsync(path, tolerated) {
155
165
  }
156
166
  }
157
167
 
168
+ // Node sets NODE_TEST_CONTEXT in every `--test` process. A secret (credential)
169
+ // write from a test must land under the temp dir; anything else is a fixture
170
+ // about to overwrite a real profile, so fail loudly instead.
171
+ function assertTestSecretWriteSafe(filePath, opts) {
172
+ if (opts.secret !== true || !process.env.NODE_TEST_CONTEXT) return;
173
+ const rel = relative(resolve(tmpdir()).toLowerCase(), resolve(filePath).toLowerCase());
174
+ if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) return;
175
+ throw new Error(`refusing to write a secret outside the temp dir under node --test: ${filePath}`);
176
+ }
177
+
158
178
  export function writeFileAtomicSync(filePath, data, opts = {}) {
179
+ assertTestSecretWriteSafe(filePath, opts);
159
180
  const run = () => {
160
181
  const dir = dirname(filePath);
161
182
  mkdirSync(dir, { recursive: true });
@@ -280,6 +301,7 @@ export function updateJsonAtomicSync(filePath, mutator, opts = {}) {
280
301
  // block the host event loop on read/write/fsync/rename.
281
302
  // Async writes retain atomic rename publication; truncate recovery is sync-only.
282
303
  export async function writeFileAtomicAsync(filePath, data, opts = {}) {
304
+ assertTestSecretWriteSafe(filePath, opts);
283
305
  const run = async () => {
284
306
  const dir = dirname(filePath);
285
307
  await mkdirAsync(dir, { recursive: true });
@@ -7,6 +7,7 @@ import {
7
7
  } from './tool-execution-contract.mjs';
8
8
  import { presentErrorText } from './err-text.mjs';
9
9
  import { clean } from './clean.mjs';
10
+ import { emitSessionActivityChange } from './session-activity-signal.mjs';
10
11
  import {
11
12
  parseTaskNotification,
12
13
  renderTaskCompletionEnvelope,
@@ -104,12 +105,17 @@ function hasScopeCriteria(scope) {
104
105
  return Boolean(scope?.callerSessionId || scope?.routingSessionId || scope?.clientHostPid);
105
106
  }
106
107
 
107
- function taskMatchesScope(task, options = {}, { includeUnattributed = true } = {}) {
108
+ function taskSessionIdOf(task) {
109
+ return task.ownerSessionId || task.notifyContext?.callerSessionId || task.notifyContext?.routingSessionId || null;
110
+ }
111
+
112
+ // `scope` is normalizeTaskScope(options), normalized once per scan by the
113
+ // caller: a scan is O(tasks), and the daemon runs one per session per
114
+ // busy-count, so per-task normalization multiplied into seconds under load.
115
+ function taskMatchesScope(task, scope, { includeUnattributed = true } = {}) {
108
116
  if (!task) return false;
109
- const scope = normalizeTaskScope(options);
110
117
  if (!hasScopeCriteria(scope)) return true;
111
- const taskSessionId =
112
- task.ownerSessionId || task.notifyContext?.callerSessionId || task.notifyContext?.routingSessionId || null;
118
+ const taskSessionId = taskSessionIdOf(task);
113
119
  const taskClientHostPid = task.clientHostPid || task.notifyContext?.clientHostPid || null;
114
120
  // Legacy/unattributed tasks remain visible so old async jobs are still recoverable.
115
121
  if (!taskSessionId && !taskClientHostPid) return includeUnattributed;
@@ -127,11 +133,13 @@ function pruneTasks(options = {}) {
127
133
  const { force = false, surface } = options;
128
134
  const now = Date.now();
129
135
  const wanted = clean(surface);
130
- for (const [taskId, task] of [...tasks.entries()]) {
136
+ const scope = normalizeTaskScope(options);
137
+ // Deleting the entry being visited is safe during Map iteration.
138
+ for (const [taskId, task] of tasks) {
131
139
  if (wanted && task.surface !== wanted) continue;
132
- if (!taskMatchesScope(task, options)) continue;
133
140
  const finishedAt = task.finishedAtMs || 0;
134
- if (force || (finishedAt > 0 && now - finishedAt > TASK_TTL_MS)) tasks.delete(taskId);
141
+ if (!force && !(finishedAt > 0 && now - finishedAt > TASK_TTL_MS)) continue;
142
+ if (taskMatchesScope(task, scope)) tasks.delete(taskId);
135
143
  }
136
144
  if (tasks.size <= MAX_TASKS) return;
137
145
  const removable = [...tasks.values()]
@@ -197,6 +205,7 @@ export function registerBackgroundTask({
197
205
  notifyOnCancel: notifyOnCancel === true,
198
206
  };
199
207
  tasks.set(id, task);
208
+ emitSessionActivityChange(task.ownerSessionId || '');
200
209
  return task;
201
210
  }
202
211
 
@@ -230,38 +239,64 @@ export function getBackgroundTask(taskId, options = {}) {
230
239
  if (!task) return null;
231
240
  const wanted = clean(options.surface);
232
241
  if (wanted && task.surface !== wanted) return null;
233
- if (!taskMatchesScope(task, options)) return null;
242
+ if (!taskMatchesScope(task, normalizeTaskScope(options))) return null;
234
243
  return task;
235
244
  }
236
245
 
246
+ function countsAsActive(task, wanted, awaitedOnly) {
247
+ return task.status === 'running' && !(awaitedOnly && task.detached === true) && (!wanted || task.surface === wanted);
248
+ }
249
+
237
250
  /** Lifetime signal: a session-owned background task remains live
238
- * work even while the interactive turn is idle and every view is detached. */
251
+ * work even while the interactive turn is idle and every view is detached.
252
+ * `awaitedOnly` leaves out `detached` tasks: processes a finished command
253
+ * deliberately left running (an app or server it launched), which nothing is
254
+ * waiting on and which may outlive the session's work by days.
255
+ * A read: pruning only ever drops finished tasks, which never count here. */
239
256
  export function hasActiveBackgroundTasks(options = {}) {
240
- pruneTasks(options);
241
257
  const wanted = clean(options.surface);
242
- return [...tasks.values()].some(
243
- (task) =>
244
- task.status === 'running' &&
245
- (!wanted || task.surface === wanted) &&
246
- taskMatchesScope(task, options, { includeUnattributed: false })
247
- );
258
+ const awaitedOnly = options.awaitedOnly === true;
259
+ const scope = normalizeTaskScope(options);
260
+ for (const task of tasks.values()) {
261
+ if (countsAsActive(task, wanted, awaitedOnly) && taskMatchesScope(task, scope, { includeUnattributed: false })) {
262
+ return true;
263
+ }
264
+ }
265
+ return false;
266
+ }
267
+
268
+ /** Every owner session `hasActiveBackgroundTasks({ callerSessionId, surface,
269
+ * awaitedOnly })` answers true for, from ONE pass over the tasks: a caller
270
+ * asking for every session (busy counts, pool rows) scans once instead of
271
+ * once per session. */
272
+ export function activeBackgroundTaskSessionIds(options = {}) {
273
+ const wanted = clean(options.surface);
274
+ const awaitedOnly = options.awaitedOnly === true;
275
+ const ids = new Set();
276
+ for (const task of tasks.values()) {
277
+ if (!countsAsActive(task, wanted, awaitedOnly)) continue;
278
+ const sessionId = taskSessionIdOf(task);
279
+ if (sessionId) ids.add(sessionId);
280
+ }
281
+ return ids;
248
282
  }
249
283
 
250
284
  export function listBackgroundTasks(options = {}) {
251
285
  const { surface } = options;
252
286
  pruneTasks(options);
253
287
  const wanted = clean(surface);
288
+ const scope = normalizeTaskScope(options);
254
289
  return [...tasks.values()]
255
290
  .filter((task) => !wanted || task.surface === wanted)
256
- .filter((task) => taskMatchesScope(task, options))
291
+ .filter((task) => taskMatchesScope(task, scope))
257
292
  .map((task) => taskSummary(task));
258
293
  }
259
294
 
260
295
  export function cleanupBackgroundTasks(options = {}) {
261
296
  const wanted = clean(options.surface);
297
+ const scope = normalizeTaskScope(options);
262
298
  const countForSurface = () =>
263
- [...tasks.values()].filter((task) => (!wanted || task.surface === wanted) && taskMatchesScope(task, options))
264
- .length;
299
+ [...tasks.values()].filter((task) => (!wanted || task.surface === wanted) && taskMatchesScope(task, scope)).length;
265
300
  const before = countForSurface();
266
301
  pruneTasks(options);
267
302
  const after = countForSurface();
@@ -331,6 +366,7 @@ export function cancelBackgroundTasks(options = {}) {
331
366
  const reason = clean(options.reason) || 'cancelled';
332
367
  const wanted = clean(options.surface);
333
368
  const executionSessionId = clean(options.executionSessionId);
369
+ const scope = normalizeTaskScope(options);
334
370
  let cancelled = 0;
335
371
  for (const task of [...tasks.values()]) {
336
372
  if (wanted && task.surface !== wanted) continue;
@@ -341,7 +377,7 @@ export function cancelBackgroundTasks(options = {}) {
341
377
  // Delegated work executes in its bound session; before binding it still
342
378
  // belongs to the registering session. Notification ownership is separate.
343
379
  if ((clean(task.meta?.sessionId) || task.ownerSessionId) !== executionSessionId) continue;
344
- } else if (!taskMatchesScope(task, options, { includeUnattributed: false })) continue;
380
+ } else if (!taskMatchesScope(task, scope, { includeUnattributed: false })) continue;
345
381
  if (TERMINAL_STATUSES.has(task.status)) {
346
382
  if (options.notify === false) silenceCancelledNotification(task);
347
383
  continue;
@@ -413,6 +449,7 @@ export function completeBackgroundTask(
413
449
  if (error != null) task.error = presentErrorText(error, { surface: task.surface });
414
450
  if (resultType) task.resultType = resultType;
415
451
  if (instruction) task.notificationInstruction = compactText(instruction, 1_000);
452
+ emitSessionActivityChange(task.ownerSessionId || '');
416
453
  if (notify) {
417
454
  task.meta = sanitizeTaskMeta(task.meta);
418
455
  notifyTaskCompletion(task, instruction);
@@ -522,6 +559,9 @@ export function renderBackgroundTaskNotification(task) {
522
559
  : resultTextForTask(task);
523
560
  if (task.surface === 'shell') {
524
561
  const parsed = parseTaskNotification(result);
562
+ // The completion envelope already carries the result (exit code,
563
+ // duration, output or its summary): deliver it as is.
564
+ if (parsed?.result) return result;
525
565
  return renderShellCompletionNotice({
526
566
  jobId: task.taskId,
527
567
  status: task.status,
@@ -1,4 +1,4 @@
1
- import { availableParallelism } from 'node:os';
1
+ import { availableParallelism, totalmem } from 'node:os';
2
2
  import { createOwnerFairGate } from './owner-fair-gate.mjs';
3
3
  import { currentToolExecutionOwner } from './tool-execution-owner.mjs';
4
4
  import { positiveInt } from './numbers.mjs';
@@ -9,8 +9,9 @@ import { positiveInt } from './numbers.mjs';
9
9
  // singleton semaphore CAN bound the number of concurrent child processes (rg,
10
10
  // mixdog-graph, …) across ALL agents/workers in this daemon.
11
11
  //
12
- // Default: Windows search scales conservatively from 1..4 with CPU count;
13
- // code-graph remains 1. Other platforms are unbounded. Windows measurements
12
+ // Default: Windows search and code-graph-query scale 2..8 with CPU count;
13
+ // code-graph builds 1..4 with cores and memory. Other platforms bound search
14
+ // only. Windows measurements
14
15
  // showed both extremes are poor: cap 1 creates multi-session queue latency,
15
16
  // while unbounded multithreaded rg amplifies Defender/process-scan and disk
16
17
  // contention. Separate lanes isolate short search from long graph builds.
@@ -28,7 +29,8 @@ function resolveDefaultChildSpawnLaneMaxInflight(
28
29
  laneName,
29
30
  env = process.env,
30
31
  platform = process.platform,
31
- parallelism = availableParallelism()
32
+ parallelism = availableParallelism(),
33
+ memoryBytes = totalmem()
32
34
  ) {
33
35
  const lane = _laneName(laneName);
34
36
  const laneOverride = positiveInt(env[`MIXDOG_CHILD_SPAWN_${_laneEnvKey(lane)}_MAX_INFLIGHT`]);
@@ -45,13 +47,19 @@ function resolveDefaultChildSpawnLaneMaxInflight(
45
47
  // Slots are held only across the spawn itself, so a small cap smooths the
46
48
  // AV convoy without limiting how many commands run concurrently.
47
49
  if (lane === 'process-spawn') return Math.max(2, Math.min(4, Math.floor(cpus / 6) || 2));
48
- // One cold graph build saturates memory/disk; allow a second only on big hosts.
49
- if (lane === 'code-graph') return cpus >= 12 ? 2 : 1;
50
- if (lane !== 'search') return 1;
51
- // Multiple rg processes beat one under multi-session load, but each process
52
- // is itself threaded and Defender amplifies excess fan-out. Burst stress
53
- // measured cap-4 starvation (find p50 15s at 8 concurrent sessions), so
54
- // scale with cores and cap at eight: 4→2, 8→3, 12→4, 18→6, 24+→8.
50
+ // Graph builds (Worker thread + native walk/parse) hold a slot for seconds
51
+ // and are memory-heavy: one per 4 cores and per 4 GiB, 1..4. Builds of one
52
+ // repo are already single-flight (build.mjs); distinct repos are independent.
53
+ if (lane === 'code-graph') {
54
+ const memGb = Math.floor((Number(memoryBytes) || 0) / 1024 ** 3);
55
+ return Math.max(1, Math.min(4, Math.floor(cpus / 4), Math.floor(memGb / 4)));
56
+ }
57
+ if (lane !== 'search' && lane !== 'code-graph-query') return 1;
58
+ // Short read-only walks: rg, and the graph binary's `--manifest` that
59
+ // validates a cached graph. Multiple processes beat one under multi-session
60
+ // load, but each is itself threaded and Defender amplifies excess fan-out.
61
+ // Burst stress measured cap-4 starvation (find p50 15s at 8 concurrent
62
+ // sessions), so scale with cores and cap at eight: 4→2, 8→3, 12→4, 18→6, 24+→8.
55
63
  return Math.max(2, Math.min(8, Math.ceil(cpus / 3)));
56
64
  }
57
65
 
@@ -87,11 +95,13 @@ function _laneSetting(name, suffix, fallback) {
87
95
  // ── Lanes ────────────────────────────────────────────────────────────────
88
96
  // Independent optional limits and queues:
89
97
  // 'search' — short-lived rg processes.
90
- // 'code-graph' — long-lived graph workers / native graph binaries. A cold
91
- // graph build can hold its slot for seconds-to-minutes; a
92
- // single shared lane would let one build
93
- // stall every rg spawn behind it. Separate lanes remove that
94
- // starvation class while keeping each spawn family bounded.
98
+ // 'code-graph' — long-lived graph build workers. A cold build can hold its
99
+ // slot for seconds-to-minutes; a single shared lane would let
100
+ // one build stall every rg spawn behind it. Separate lanes
101
+ // remove that starvation class while keeping each spawn
102
+ // family bounded.
103
+ // 'code-graph-query' — the short read-only manifest walk that validates
104
+ // a cached graph, so queries never wait behind builds.
95
105
  function _makeLane(name) {
96
106
  const limit = resolveDefaultChildSpawnLaneMaxInflight(name);
97
107
  const queueMax = _laneSetting(name, 'MAX_QUEUE', 1024);