mixdog 1.3.1 → 1.3.3

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 (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -7,6 +7,8 @@
7
7
  import { cleanSessionPreview, isSessionPreviewNoise, sessionMessageText } from '../runtime-core/session-text.mjs';
8
8
  import { plainPreviewText } from '../../../shared/plain-preview.mjs';
9
9
  import { isRoutineResultMessage, routineProvenance, routineResultNotice } from './routine-result.mjs';
10
+ import { agentIdOfHomeSession } from './agent-home.mjs';
11
+ import { roomIdOfSession } from './agent-room.mjs';
10
12
 
11
13
  export const LAST_MESSAGE_PREVIEW_MAX = 160;
12
14
 
@@ -28,13 +30,56 @@ export function lastMessageRawText(message) {
28
30
  return isRoutineResultMessage(message) ? routineResultNotice(message).text : sessionMessageText(message.content);
29
31
  }
30
32
 
31
- /** The message's last-message line, '' when it has none (tool-only, synthetic). */
33
+ /** The message's last-message line, '' when it has none (tool-only, synthetic,
34
+ * or the runtime's own task-notification envelopes). */
32
35
  export function lastMessageLine(message) {
33
36
  const raw = lastMessageRawText(message);
34
- if (raw === null || isSessionPreviewNoise(raw)) return '';
37
+ if (raw === null || isSessionPreviewNoise(raw) || raw.trimStart().startsWith('<task-notification>\n')) return '';
35
38
  return cleanSummaryPreview(plainPreviewText(raw), LAST_MESSAGE_PREVIEW_MAX);
36
39
  }
37
40
 
41
+ /** Most unseen messages a row can report: one past the pill's 99, so a full
42
+ * tail still reads as "99+". */
43
+ export const INCOMING_TAIL_MAX = 100;
44
+
45
+ /** Only a bot's home thread or a group room's log shows an unread count, so
46
+ * only their summaries carry the incoming tail. */
47
+ export function wantsIncomingTail(sessionId) {
48
+ return Boolean(agentIdOfHomeSession(sessionId) || roomIdOfSession(sessionId));
49
+ }
50
+
51
+ /** Whether `message` is of a kind that arrives as a chat message the user did
52
+ * not write: an assistant turn that is not a tool call, or a posted result
53
+ * (routine, task report, bot-to-bot). A user prompt, a tool-call turn and a
54
+ * tool result are not. Text is checked separately (lastMessageLine). */
55
+ export function isIncomingKind(message) {
56
+ if (isRoutineResultMessage(message)) return true;
57
+ if (message?.role !== 'assistant') return false;
58
+ const content = Array.isArray(message.content) ? message.content : [];
59
+ return !(message.toolCalls?.length > 0) && !content.some((part) => part?.type === 'tool_use');
60
+ }
61
+
62
+ /** The incoming messages with text among the last of a transcript, each as
63
+ * its 1-based place among the user/assistant messages (the numbering of
64
+ * `messageCount` and of the read cursor), oldest first, at most
65
+ * INCOMING_TAIL_MAX of them. */
66
+ export function incomingTailOf(messages) {
67
+ const tail = [];
68
+ let ordinal = 0;
69
+ for (const message of Array.isArray(messages) ? messages : []) {
70
+ if (message?.role !== 'user' && message?.role !== 'assistant') continue;
71
+ ordinal += 1;
72
+ if (isIncomingKind(message) && lastMessageLine(message)) tail.push(ordinal);
73
+ }
74
+ return tail.slice(-INCOMING_TAIL_MAX);
75
+ }
76
+
77
+ /** A stored incoming tail as a clean ascending list of whole places. */
78
+ export function normalizedIncomingTail(value) {
79
+ const places = (Array.isArray(value) ? value : []).filter((place) => Number.isSafeInteger(place) && place > 0);
80
+ return places.slice(-INCOMING_TAIL_MAX);
81
+ }
82
+
38
83
  /** `{ lastMessagePreview, lastMessageRoutine }` of a transcript. */
39
84
  export function lastMessageSummary(messages) {
40
85
  const list = Array.isArray(messages) ? messages : [];
@@ -20,6 +20,7 @@ import {
20
20
  indexListing,
21
21
  observeStamp,
22
22
  scanSessionFiles,
23
+ scanSessionFilesAsync,
23
24
  stampedDerivation,
24
25
  } from './store-summary-catalog.mjs';
25
26
  import { createStoredTranscriptCache, nextProjectionStamp } from './store-transcript-cache.mjs';
@@ -155,6 +156,30 @@ async function catalogDbListing(page) {
155
156
  return answer;
156
157
  }
157
158
 
159
+ // The running storage refresh a new caller may still join: only until its
160
+ // final synchronous settle begins, since a caller arriving after that asked
161
+ // after the answer was fixed (a scan that never yields settles before its own
162
+ // caller awaits it).
163
+ let storageRefreshFlight = null;
164
+
165
+ async function refreshFromStorageOnce(onSettle) {
166
+ const index = ordinaryCatalog.loadIndex();
167
+ return (await scanSessionFilesAsync(index.rowsById, true, { onSettle })) ?? indexListing(index) ?? [];
168
+ }
169
+
170
+ function storageRefresh() {
171
+ if (storageRefreshFlight?.joinable) return storageRefreshFlight.rows;
172
+ const flight = { joinable: true, rows: null };
173
+ storageRefreshFlight = flight;
174
+ flight.rows = refreshFromStorageOnce(() => {
175
+ flight.joinable = false;
176
+ }).finally(() => {
177
+ // A newer flight is never cleared by an older one's end.
178
+ if (storageRefreshFlight === flight) storageRefreshFlight = null;
179
+ });
180
+ return flight.rows;
181
+ }
182
+
158
183
  /**
159
184
  * listStoredSessionSummaries for async callers (the daemon's session views).
160
185
  * In the daemon the ordinary listing comes from the session catalog DB
@@ -163,7 +188,14 @@ async function catalogDbListing(page) {
163
188
  * JSON/file path.
164
189
  */
165
190
  export async function listStoredSessionSummariesAsync(options = {}) {
166
- if (options.refreshFromStorage === true || options.rebuildIfMissing === false) {
191
+ if (options.refreshFromStorage === true) {
192
+ // The same authoritative rescan as the sync path, in slices that yield.
193
+ // Concurrent refreshes share one scan while it can still see their
194
+ // request (it settles against storage when it ends); each caller gets its
195
+ // own rows.
196
+ return (await storageRefresh()).map((row) => ({ ...row }));
197
+ }
198
+ if (options.rebuildIfMissing === false) {
167
199
  return listStoredSessionSummaries(options);
168
200
  }
169
201
  const answer = await catalogDbListing(null);
@@ -0,0 +1,5 @@
1
+ export function isRootLeadSession(value: unknown): boolean;
2
+ export function sessionVisibility(value: unknown): 'ordinary' | 'agent-only';
3
+ export function isAgentOnlySession(value: unknown): boolean;
4
+ export function agentParentIds(value: unknown): string[];
5
+ export function agentDescendantIds(rows: readonly unknown[], rootId: unknown): string[];
@@ -49,3 +49,40 @@ export function sessionVisibility(value) {
49
49
  export function isAgentOnlySession(value) {
50
50
  return sessionVisibility(value) === SESSION_VISIBILITY_AGENT_ONLY;
51
51
  }
52
+
53
+ /** Every session a record links itself to as its owner or parent (both links,
54
+ * never itself): the links the descendant walk and the sub-agent retention
55
+ * trees follow. */
56
+ export function agentParentIds(value) {
57
+ const id = clean(value?.id);
58
+ return [...new Set([clean(value?.ownerSessionId), clean(value?.parentSessionId)])].filter(
59
+ (parent) => parent && parent !== id
60
+ );
61
+ }
62
+
63
+ /** The agent-only sessions under `rootId` at any depth, from session rows. A
64
+ * catalog row links a sub-agent to its root Lead (`ownerSessionId`) or, on a
65
+ * legacy row, to its parent, so the walk follows every link a row names. */
66
+ export function agentDescendantIds(rows, rootId) {
67
+ const root = clean(rootId);
68
+ const children = new Map();
69
+ for (const row of Array.isArray(rows) ? rows : []) {
70
+ if (!isAgentOnlySession(row)) continue;
71
+ const id = clean(row.id);
72
+ if (!id) continue;
73
+ for (const parent of agentParentIds(row)) {
74
+ if (!children.has(parent)) children.set(parent, []);
75
+ children.get(parent).push(id);
76
+ }
77
+ }
78
+ const found = new Set();
79
+ const queue = [root];
80
+ while (queue.length) {
81
+ for (const child of children.get(queue.pop()) || []) {
82
+ if (child === root || found.has(child)) continue;
83
+ found.add(child);
84
+ queue.push(child);
85
+ }
86
+ }
87
+ return [...found];
88
+ }
@@ -722,4 +722,5 @@ export { fenceSessionWrites } from './store/live-state.mjs';
722
722
  // session-store entry point.
723
723
  export { listStoredSessions, listStoredSessionSummaries, getStoredSessionsRaw } from './store/listing.mjs';
724
724
  export { sweepStaleSessions, sweepStaleSessionsCooperative } from './store/sweep/stale-sweep.mjs';
725
+ export { applySubAgentRetention } from './store/sweep/sub-agent-retention.mjs';
725
726
  export { _savePending } from './store/pending-saves.mjs';
@@ -0,0 +1,125 @@
1
+ // Tail reminders: short per-model/per-language nudges appended to the very end
2
+ // of an outgoing request (after the last tool result), fresh on every request.
3
+ // They are never stored in session history or transcripts, and the Anthropic
4
+ // builders place their last cache breakpoint BEFORE them, so the cached prefix
5
+ // stays byte-identical from one request to the next.
6
+ //
7
+ // HOW TO ADD A RULE (a new per-model/per-language fix needs nothing else):
8
+ // 1. Append {id, when, text} to TAIL_REMINDER_RULES. `when` holds any of
9
+ // provider / model / language / role, each a list of accepted values; ALL
10
+ // given tags must match, an omitted tag matches anything.
11
+ // 2. `text` may use `{language}` (the language block's name, e.g. "Korean").
12
+ // 3. Add a positive and a negative case to tail-reminder-rules.test.mjs.
13
+ // Values: provider = provider id or family ('anthropic'); model = normalized
14
+ // family id ('claude-opus-5-5', route prefix/dots/date folded); language = id
15
+ // ('ko'); role = 'lead' | 'bot' | 'worker'. Unknown tags throw at load.
16
+ import { AGENT_MODE } from './agent-mode.mjs';
17
+ import { _buildLeadLanguage } from './manager/rules-cache.mjs';
18
+ import { TAIL_REMINDER_META } from '../providers/lib/tail-reminder-split.mjs';
19
+
20
+ export const TAIL_REMINDER_TAGS = Object.freeze(['provider', 'model', 'language', 'role']);
21
+ const ROLES = ['lead', 'bot', 'worker'];
22
+
23
+ const PROVIDER_FAMILIES = Object.freeze({
24
+ 'anthropic-oauth': 'anthropic',
25
+ anthropic: 'anthropic',
26
+ });
27
+
28
+ export const TAIL_REMINDER_RULES = Object.freeze([
29
+ // Claude Opus 5.5 keeps answering Korean users in English in long agentic
30
+ // turns, where many English tool results sit between the user message and
31
+ // the final answer.
32
+ {
33
+ id: 'opus-5-5-korean',
34
+ when: { model: ['claude-opus-5-5'], language: ['ko'], role: ['lead', 'bot'] },
35
+ text: 'Write all user-facing text in {language}.',
36
+ },
37
+ ]);
38
+
39
+ export function validateTailReminderRules(rules) {
40
+ if (!Array.isArray(rules)) throw new Error('tail reminder rules must be an array');
41
+ const ids = new Set();
42
+ for (const rule of rules) {
43
+ if (!rule?.id || ids.has(rule.id)) throw new Error(`tail reminder rule: missing or duplicate id "${rule?.id}"`);
44
+ ids.add(rule.id);
45
+ for (const key of Object.keys(rule)) {
46
+ if (!['id', 'when', 'text'].includes(key))
47
+ throw new Error(`tail reminder rule ${rule.id}: unknown field "${key}"`);
48
+ }
49
+ if (typeof rule.text !== 'string' || !rule.text.trim())
50
+ throw new Error(`tail reminder rule ${rule.id}: text required`);
51
+ for (const [tag, values] of Object.entries(rule.when || {})) {
52
+ if (!TAIL_REMINDER_TAGS.includes(tag)) throw new Error(`tail reminder rule ${rule.id}: unknown tag "${tag}"`);
53
+ if (!Array.isArray(values) || !values.length || values.some((v) => typeof v !== 'string' || !v)) {
54
+ throw new Error(`tail reminder rule ${rule.id}: "${tag}" must be a non-empty string list`);
55
+ }
56
+ }
57
+ for (const role of rule.when?.role || []) {
58
+ if (!ROLES.includes(role)) throw new Error(`tail reminder rule ${rule.id}: unknown role "${role}"`);
59
+ }
60
+ }
61
+ return rules;
62
+ }
63
+ validateTailReminderRules(TAIL_REMINDER_RULES);
64
+
65
+ /** `anthropic/claude-opus-5.5`, `claude-opus-5-5-20260101`, `us.anthropic.claude-opus-5-5-v1:0`,
66
+ * `claude-opus-5-5-thinking` → `claude-opus-5-5`. */
67
+ export function modelFamilyId(model) {
68
+ let id = String(model || '')
69
+ .trim()
70
+ .toLowerCase()
71
+ .replace(/\./g, '-');
72
+ const at = id.indexOf('claude-');
73
+ if (at >= 0) id = id.slice(at);
74
+ else id = id.slice(id.lastIndexOf('/') + 1);
75
+ // `@date`, Bedrock `-v1:0`, a gateway `:variant` (`:thinking`), `-v1`,
76
+ // `-YYYYMMDD`, and the `-thinking` alias of the same model.
77
+ return id
78
+ .replace(/@.*$/, '')
79
+ .replace(/-v\d+:\d+$/, '')
80
+ .replace(/:[^/]*$/, '')
81
+ .replace(/-v\d+$/, '')
82
+ .replace(/-\d{8}$/, '')
83
+ .replace(/-thinking$/, '');
84
+ }
85
+
86
+ /** The session's role: 'worker' (agent-owned), 'bot' (agent mode) or 'lead'. */
87
+ export function tailReminderRole(session) {
88
+ if (session?.owner === 'agent') return 'worker';
89
+ return session?.sessionMode === AGENT_MODE ? 'bot' : 'lead';
90
+ }
91
+
92
+ // All given conditions must hold (AND); an omitted condition matches anything.
93
+ function ruleMatches({ when = {} }, { provider, model, languageId, role }) {
94
+ const family = PROVIDER_FAMILIES[provider];
95
+ if (when.provider && !when.provider.some((p) => p === provider || (family && p === family))) return false;
96
+ if (when.model && !when.model.includes(modelFamilyId(model))) return false;
97
+ if (when.language && !when.language.includes(languageId)) return false;
98
+ if (when.role && !when.role.includes(role)) return false;
99
+ return true;
100
+ }
101
+
102
+ /**
103
+ * The joined text of every matching rule wrapped as one system-reminder, or ''.
104
+ * context = {provider, model, language: {id, name}|null, role}.
105
+ */
106
+ export function matchTailReminder(context, rules = TAIL_REMINDER_RULES) {
107
+ const language = context.language || null;
108
+ const facts = { ...context, languageId: language?.id };
109
+ const texts = rules
110
+ .filter((rule) => ruleMatches(rule, facts))
111
+ .map((rule) => rule.text.replaceAll('{language}', language?.name || ''));
112
+ return texts.length ? `<system-reminder>${texts.join('\n')}</system-reminder>` : '';
113
+ }
114
+
115
+ /** `messages` plus the tail reminder for this request (a copy; the input is untouched). */
116
+ export function withTailReminder(messages, { session, provider, model }, language = _buildLeadLanguage()) {
117
+ const text = matchTailReminder({
118
+ provider: session?.provider || provider?.name || '',
119
+ model,
120
+ language,
121
+ role: tailReminderRole(session),
122
+ });
123
+ if (!text) return messages;
124
+ return [...messages, { role: 'user', content: text, meta: { source: TAIL_REMINDER_META } }];
125
+ }
@@ -11,6 +11,7 @@ import {
11
11
  import { isOffloadedToolResultText, maybeOffloadToolResultBatch } from '../tool-result-offload.mjs';
12
12
  import { setReadCached, setScopedToolCached } from '../read-dedup.mjs';
13
13
  import { _stripMcpPrefix, _isReadTool, _isScopedCacheableTool, _isShellTool } from '../loop/tool-classify.mjs';
14
+ import { takeAgentTaskStarts } from '../../../../shared/agent-task-starts.mjs';
14
15
  import { crossTurnSignature, isEditProgressTool } from '../loop/completion-guards.mjs';
15
16
  import { isEagerDispatchable, isToolCallDedupEligible, parseNativeToolSearchPayload } from '../loop/tool-helpers.mjs';
16
17
  import { restoreToolCallBodyForId } from '../loop/stored-tool-args.mjs';
@@ -307,6 +308,9 @@ function completedToolResult(completed, result, postprocessStartedAt) {
307
308
  ...(completed.readWindow ? { readWindow: completed.readWindow } : {}),
308
309
  ...(uiDiff !== null ? { uiDiff } : {}),
309
310
  ...toolMetaPatchFromMessage(completed.result),
311
+ // Every agent call's result names the jobs it started (none: []), so a
312
+ // task session's report follows exactly its own agents' results.
313
+ ...(_stripMcpPrefix(call.name) === 'agent' ? { agentTaskIds: takeAgentTaskStarts(call.id) } : {}),
310
314
  };
311
315
  }
312
316
 
@@ -10,11 +10,11 @@ import {
10
10
  readdirSync,
11
11
  rmdirSync,
12
12
  unlinkSync,
13
- writeFileSync,
14
13
  } from 'node:fs';
15
- import { lstat, open, readdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';
14
+ import { lstat, open, readdir, readFile, stat, unlink } from 'node:fs/promises';
16
15
  import { createHash } from 'node:crypto';
17
16
  import { join } from 'node:path';
17
+ import { holdsNoData, writeFileAtomicAsync, writeFileAtomicSync } from '../../../shared/atomic-file.mjs';
18
18
  import { getPluginData } from '../config.mjs';
19
19
  import { countSplitLines, normalizeOutputPath } from '../tools/builtin/path-utils.mjs';
20
20
  import { classifyResultKind } from './result-classification.mjs';
@@ -236,28 +236,76 @@ export function persistToolResultArtifactSync({ sessionId, toolCallId, channel =
236
236
  }
237
237
  if (!meta) return null;
238
238
  try {
239
- writeFileSync(meta.path, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
239
+ const state = existingArtifactStateSync(meta);
240
+ if (state === ARTIFACT_INTACT) return meta;
241
+ if (state === ARTIFACT_FOREIGN) return null;
242
+ // Whole-file publication (fsynced temp, then link or rename): a crash never
243
+ // leaves a torn artifact. Data-free damage is replaced whole.
244
+ if (state === ARTIFACT_DAMAGED) {
245
+ writeFileAtomicSync(meta.path, content, { mode: 0o600 });
246
+ return meta;
247
+ }
248
+ if (writeFileAtomicSync(meta.path, content, { mode: 0o600, createOnly: true })) return meta;
249
+ // Lost the create race: the winner's file is whole; verify it like any other.
250
+ return existingArtifactStateSync(meta) === ARTIFACT_INTACT ? meta : null;
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+
256
+ // What sits at an artifact's content-addressed path: nothing, the verified
257
+ // bytes, a file a crash left without data (empty or NUL — replaced whole), or
258
+ // anything else (mismatched bytes, a link, a directory, an unreadable file),
259
+ // which is evidence that is never touched or trusted.
260
+ const ARTIFACT_ABSENT = 'absent';
261
+ const ARTIFACT_INTACT = 'intact';
262
+ const ARTIFACT_DAMAGED = 'damaged';
263
+ const ARTIFACT_FOREIGN = 'foreign';
264
+
265
+ function artifactVerdict(meta, info, readBytes) {
266
+ if (!info.isFile()) return ARTIFACT_FOREIGN;
267
+ if (!artifactShapeMatches(meta, info)) return info.size === 0 ? ARTIFACT_DAMAGED : ARTIFACT_FOREIGN;
268
+ const bytes = readBytes();
269
+ if (createHash('sha256').update(bytes).digest('hex') === meta.sha256) return ARTIFACT_INTACT;
270
+ return holdsNoData(bytes) ? ARTIFACT_DAMAGED : ARTIFACT_FOREIGN;
271
+ }
272
+
273
+ function existingArtifactStateSync(meta) {
274
+ let fd;
275
+ try {
276
+ lstatSync(meta.path);
277
+ fd = openSync(meta.path, ARTIFACT_READ_FLAGS);
278
+ const info = fstatSync(fd);
279
+ return artifactVerdict(meta, info, () => readFileSync(fd));
240
280
  } catch (error) {
241
- if (error?.code !== 'EEXIST') return null;
242
- let fd;
281
+ return error?.code === 'ENOENT' ? ARTIFACT_ABSENT : ARTIFACT_FOREIGN;
282
+ } finally {
243
283
  try {
244
- if (!artifactShapeMatches(meta, lstatSync(meta.path))) return null;
245
- fd = openSync(meta.path, ARTIFACT_READ_FLAGS);
246
- if (!artifactShapeMatches(meta, fstatSync(fd))) return null;
247
- if (createHash('sha256').update(readFileSync(fd)).digest('hex') !== meta.sha256) return null;
284
+ if (fd !== undefined) closeSync(fd);
248
285
  } catch {
249
- return null;
250
- } finally {
251
- try {
252
- if (fd !== undefined) closeSync(fd);
253
- } catch {
254
- // The artifact is already written and byte-verified above; a failed
255
- // descriptor close is cleanup, not a failed persist, and must never
256
- // replace the verified meta this function returns.
257
- }
286
+ // A failed descriptor close is cleanup, not a failed persist.
287
+ }
288
+ }
289
+ }
290
+
291
+ async function existingArtifactState(meta) {
292
+ let handle;
293
+ try {
294
+ await lstat(meta.path);
295
+ handle = await open(meta.path, ARTIFACT_READ_FLAGS);
296
+ const info = await handle.stat();
297
+ // The verdict needs the bytes only when the size matches: read them first.
298
+ const bytes = info.isFile() && artifactShapeMatches(meta, info) ? await handle.readFile() : null;
299
+ return artifactVerdict(meta, info, () => bytes);
300
+ } catch (error) {
301
+ return error?.code === 'ENOENT' ? ARTIFACT_ABSENT : ARTIFACT_FOREIGN;
302
+ } finally {
303
+ try {
304
+ await handle?.close();
305
+ } catch {
306
+ // A failed handle close is cleanup, not a failed persist.
258
307
  }
259
308
  }
260
- return meta;
261
309
  }
262
310
 
263
311
  async function persistToolResultArtifact({ sessionId, toolCallId, channel = 'result', content } = {}) {
@@ -269,32 +317,19 @@ async function persistToolResultArtifact({ sessionId, toolCallId, channel = 'res
269
317
  }
270
318
  if (!meta) return null;
271
319
  try {
272
- await writeFile(meta.path, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
273
- } catch (error) {
274
- if (error?.code !== 'EEXIST') return null;
275
- let handle;
276
- try {
277
- if (!artifactShapeMatches(meta, await lstat(meta.path))) return null;
278
- handle = await open(meta.path, ARTIFACT_READ_FLAGS);
279
- if (!artifactShapeMatches(meta, await handle.stat())) return null;
280
- if (
281
- createHash('sha256')
282
- .update(await handle.readFile())
283
- .digest('hex') !== meta.sha256
284
- )
285
- return null;
286
- } catch {
287
- return null;
288
- } finally {
289
- try {
290
- await handle?.close();
291
- } catch {
292
- // See persistToolResultArtifactSync: the verified artifact stands even
293
- // when releasing the handle fails, so cleanup never nulls the result.
294
- }
320
+ const state = await existingArtifactState(meta);
321
+ if (state === ARTIFACT_INTACT) return meta;
322
+ if (state === ARTIFACT_FOREIGN) return null;
323
+ // See persistToolResultArtifactSync.
324
+ if (state === ARTIFACT_DAMAGED) {
325
+ await writeFileAtomicAsync(meta.path, content, { mode: 0o600 });
326
+ return meta;
295
327
  }
328
+ if (await writeFileAtomicAsync(meta.path, content, { mode: 0o600, createOnly: true })) return meta;
329
+ return (await existingArtifactState(meta)) === ARTIFACT_INTACT ? meta : null;
330
+ } catch {
331
+ return null;
296
332
  }
297
- return meta;
298
333
  }
299
334
 
300
335
  export async function maybeOffloadToolResult(sessionId, toolCallId, toolName, result, options = {}) {
@@ -86,6 +86,7 @@ function restoredTranscriptMetadata(message) {
86
86
  ...transcriptRouteMetadataFields(value),
87
87
  ...(typeof value.sender === 'string' && value.sender ? { sender: value.sender } : {}),
88
88
  ...(typeof value.device === 'string' && value.device ? { device: value.device } : {}),
89
+ ...(typeof value.fromAgent === 'string' && value.fromAgent ? { fromAgent: value.fromAgent } : {}),
89
90
  ...(typeof value.checkpointId === 'string' && value.checkpointId ? { checkpointId: value.checkpointId } : {}),
90
91
  // The attachments this prompt carried, by content hash: a restored row
91
92
  // opens/downloads them by id from the attachment store.
@@ -112,8 +113,13 @@ function restoredTurnDoneItem(completion, metadata, nextId, finalAssistantId = '
112
113
  };
113
114
  }
114
115
 
116
+ // A stored message's plain text: string content as is, block content flattened.
117
+ function restoredContentText(message) {
118
+ return typeof message?.content === 'string' ? message.content : toolResultText(message?.content);
119
+ }
120
+
115
121
  function restoredAssistantTranscriptItems(message, nextId) {
116
- const text = (typeof message?.content === 'string' ? message.content : toolResultText(message?.content)).trim();
122
+ const text = restoredContentText(message).trim();
117
123
  if (!text) return [];
118
124
  const { completion, ...metadata } = restoredTranscriptMetadata(message);
119
125
  const items = [{ kind: 'assistant', id: nextId(), text, ...metadata }];
@@ -198,7 +204,7 @@ function attachRestoredToolResult(message, pendingByCallId) {
198
204
  const target = callId ? pendingByCallId.get(callId) : null;
199
205
  if (!target) return;
200
206
  pendingByCallId.delete(callId);
201
- const text = (typeof message?.content === 'string' ? message.content : toolResultText(message?.content)) || '';
207
+ const text = restoredContentText(message) || '';
202
208
  target.result = text;
203
209
  assignUiDiffFromMessage(target, message);
204
210
  assignToolMetaFromMessage(target, message);
@@ -339,7 +345,8 @@ function restoredMessageItemUpperBound(message) {
339
345
  const boundaries =
340
346
  (Array.isArray(message?.meta?.sessionInheritances) ? message.meta.sessionInheritances.length : 0) +
341
347
  (Array.isArray(message?.meta?.modelSwitches) ? message.meta.modelSwitches.length : 0) +
342
- (Array.isArray(message?.meta?.workflowSwitches) ? message.meta.workflowSwitches.length : 0);
348
+ (Array.isArray(message?.meta?.workflowSwitches) ? message.meta.workflowSwitches.length : 0) +
349
+ (Array.isArray(message?.meta?.orchestrationModeSwitches) ? message.meta.orchestrationModeSwitches.length : 0);
343
350
  const completionCount =
344
351
  Number(Boolean(message?.meta?.transcript?.completion)) +
345
352
  (Array.isArray(message?.meta?.transcript?.previousCompletions)
@@ -400,7 +407,7 @@ function restoredUserTranscriptItems(message, nextId, userText) {
400
407
  }
401
408
  // Imported prose renders verbatim: never a control row, card or display rewrite.
402
409
  if (isImportedMessage(message)) {
403
- const text = (typeof message.content === 'string' ? message.content : toolResultText(message.content)).trim();
410
+ const text = restoredContentText(message).trim();
404
411
  return text ? [{ kind: 'user', id: nextId(), text, ...restoredTranscriptMetadata(message) }] : [];
405
412
  }
406
413
  const text = userText.text(message?.content).trim();
@@ -463,7 +470,9 @@ export function modelSwitchItem(id, boundary) {
463
470
  };
464
471
  }
465
472
 
466
- /** The transcript row of a mid-conversation workflow switch (live and restored). */
473
+ /** The transcript row of a mid-conversation workflow switch (live and
474
+ * restored); `unavailable`: the chosen workflow was gone when it was due and
475
+ * the conversation kept `from`. */
467
476
  export function workflowSwitchItem(id, boundary) {
468
477
  return {
469
478
  kind: 'statusdone',
@@ -472,6 +481,21 @@ export function workflowSwitchItem(id, boundary) {
472
481
  label: 'Workflow switched',
473
482
  from: boundary.from,
474
483
  to: boundary.to,
484
+ ...(boundary.unavailable === true ? { unavailable: true } : {}),
485
+ at: boundary.at,
486
+ };
487
+ }
488
+
489
+ /** The transcript row of a mid-conversation orchestration (delegation) mode
490
+ * switch (live and restored); `from`/`to` are mode ids. */
491
+ export function orchestrationModeSwitchItem(id, boundary) {
492
+ return {
493
+ kind: 'statusdone',
494
+ id,
495
+ status: 'orchestration-mode-switched',
496
+ label: 'Delegation mode switched',
497
+ from: boundary.from,
498
+ to: boundary.to,
475
499
  at: boundary.at,
476
500
  };
477
501
  }
@@ -522,6 +546,12 @@ function restoreTranscriptRange(messages, start, sessionId, userText) {
522
546
  if (!boundary?.to) continue;
523
547
  items.push(workflowSwitchItem(restoredId(), boundary));
524
548
  }
549
+ for (const boundary of Array.isArray(message?.meta?.orchestrationModeSwitches)
550
+ ? message.meta.orchestrationModeSwitches
551
+ : []) {
552
+ if (!boundary?.to) continue;
553
+ items.push(orchestrationModeSwitchItem(restoredId(), boundary));
554
+ }
525
555
  if (message?.meta?.transcript?.previousCompletions?.length) {
526
556
  const events = items.slice(eventStart);
527
557
  if (events.every((item) => Number.isFinite(item.at))) {
@@ -11,6 +11,7 @@
11
11
  * moves a long block into the attachment store; it is read back from there.
12
12
  */
13
13
  import { isAttachmentReference, readAttachmentTextWithin } from '../../../../attachments/store.mjs';
14
+ import { isMediaReferencePart } from '../../../../attachments/attachment-reference.mjs';
14
15
  import { toolResultText } from './tool-result-text.mjs';
15
16
 
16
17
  const IMAGE_METADATA_LINE = /\[Image(?::| source:) [^\]\r\n]+\]\s*$/;
@@ -122,6 +123,21 @@ const PASTED_TEXT_MAX_BYTES = 1024 * 1024;
122
123
  * whose stored name does not carry the count (pastes stored before it did). */
123
124
  export const PASTED_LINE_READ_BUDGET_BYTES = 4 * 1024 * 1024;
124
125
 
126
+ /** A stored plain-text prompt part's text within the load's byte budget, else ''. */
127
+ function offloadedPromptText(part, budget) {
128
+ const allowance = Math.min(PASTED_TEXT_MAX_BYTES, budget.remaining);
129
+ if (allowance <= 0) return '';
130
+ try {
131
+ const text = readAttachmentTextWithin(part, allowance);
132
+ if (text == null) return '';
133
+ budget.remaining -= Buffer.byteLength(text, 'utf8');
134
+ return text;
135
+ } catch (error) {
136
+ if (error?.code !== 'ENOENT') budget.remaining -= allowance;
137
+ return '';
138
+ }
139
+ }
140
+
125
141
  /** Visible lines of a stored paste (as `pastedTextLineCount` counts them), or
126
142
  * null when the count would take a read past the load's budget. */
127
143
  function storedPasteLines(part, budget) {
@@ -166,7 +182,13 @@ function restoredUserParts(content, budget) {
166
182
  const images = [];
167
183
  let pendingMetadata = '';
168
184
  let storedTexts = 0;
185
+ // Plain text the intake moved into the store (a long prompt, a bot's message).
186
+ const offloaded = [];
187
+ let companionText = false;
169
188
  for (const part of content) {
189
+ // An attachment sent by reference is model-facing text; the bubble shows
190
+ // the attachment as the chip its message records.
191
+ if (isMediaReferencePart(part)) continue;
170
192
  const token = isAttachmentReference(part) ? storedTextToken(part, storedTexts + 1, budget) : '';
171
193
  if (token) {
172
194
  storedTexts += 1;
@@ -185,10 +207,28 @@ function restoredUserParts(content, budget) {
185
207
  continue;
186
208
  }
187
209
  const text = isAttachmentReference(part) ? referencedFeedbackText(part) : toolResultText([part]);
188
- if (text) texts.push(text);
210
+ if (text) {
211
+ texts.push(text);
212
+ if (!IMAGE_METADATA_LINE.test(text)) companionText = true;
213
+ } else if (isAttachmentReference(part) && part?.type === 'text' && !part.textKind) {
214
+ // Keeps its place among the parts until it is known whether to read it.
215
+ texts.push(part);
216
+ offloaded.push(part);
217
+ }
189
218
  pendingMetadata = IMAGE_METADATA_LINE.test(text) ? text.trimEnd().split(/\r?\n/).pop().trim() : '';
190
219
  }
191
- return { texts, images };
220
+ // A prompt whose only real text was moved out restores it in place (bounded,
221
+ // within the load's read budget); image labels and chips are not companion
222
+ // text. Beside real text such parts stay out, as before.
223
+ const resolved = [];
224
+ for (const entry of texts) {
225
+ if (typeof entry === 'string') resolved.push(entry);
226
+ else if (!companionText) {
227
+ const text = offloadedPromptText(entry, budget);
228
+ if (text) resolved.push(text);
229
+ }
230
+ }
231
+ return { texts: resolved, images };
192
232
  }
193
233
 
194
234
  /**