mixdog 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -0,0 +1,128 @@
1
+ /**
2
+ * JSONL lines of a foreign conversation → normalized plain-text turns, one
3
+ * line at a time: each parsed record is reduced to its kept plain text and
4
+ * dropped. Tolerates a BOM, CRLF line ends and individual malformed lines
5
+ * (reported as the `malformed` warning). Pure, no IO.
6
+ */
7
+ import { createClaudeReducer } from './claude.mjs';
8
+ import { createCodexReducer } from './codex.mjs';
9
+ import { IMPORT_WARNING_MALFORMED, conversationTitle, normalizeTurns } from './turns.mjs';
10
+
11
+ export const IMPORT_SOURCES = Object.freeze(['codex', 'claude']);
12
+ /** A line or converted transcript beyond the import limits fails the file. */
13
+ export const IMPORT_ERROR_TOO_LARGE = 'tooLarge';
14
+ export const IMPORT_MAX_LINE_BYTES = 32 * 1024 * 1024;
15
+ /** Bytes read from one source file before it fails as `tooLarge`. */
16
+ export const IMPORT_MAX_FILE_BYTES = 256 * 1024 * 1024;
17
+ /** Normalized transcript text (the final turns, separators and stub included). */
18
+ export const IMPORT_MAX_TRANSCRIPT_CHARS = 4 * 1024 * 1024;
19
+ /** Entries a reducer may retain (Claude chain links, Codex turns). */
20
+ export const IMPORT_MAX_RETAINED_ENTRIES = 200_000;
21
+ /** Bookkeeping a reducer retains besides kept text (ids, links, timestamps). */
22
+ export const IMPORT_MAX_METADATA_BYTES = 16 * 1024 * 1024;
23
+
24
+ export function isImportSource(value) {
25
+ return IMPORT_SOURCES.includes(value);
26
+ }
27
+
28
+ const transcriptChars = (turns) => turns.reduce((total, turn) => total + turn.text.length, 0);
29
+
30
+ /**
31
+ * Incremental converter. `pushLine(line)` returns false once the conversion
32
+ * is over a limit (`tooLarge`): kept text over `maxTranscriptChars`, retained
33
+ * entries over `maxRetainedEntries` or their bookkeeping over
34
+ * `maxMetadataBytes`; `markTooLarge()` records a limit enforced by the reader
35
+ * (an over-long line or file). `result()` may be called repeatedly and also
36
+ * holds the normalized turns to `maxTranscriptChars`.
37
+ * `errorCode` is null for an importable result, else one of `empty` |
38
+ * `malformed` | `unsupported` | `tooLarge` (whose turns are not returned).
39
+ */
40
+ export function createConversationConverter(
41
+ source,
42
+ {
43
+ maxTranscriptChars = IMPORT_MAX_TRANSCRIPT_CHARS,
44
+ maxRetainedEntries = IMPORT_MAX_RETAINED_ENTRIES,
45
+ maxMetadataBytes = IMPORT_MAX_METADATA_BYTES,
46
+ } = {}
47
+ ) {
48
+ if (!isImportSource(source)) throw new TypeError('source must be codex or claude.');
49
+ const reducer = source === 'codex' ? createCodexReducer() : createClaudeReducer();
50
+ let lines = 0;
51
+ let records = 0;
52
+ let malformed = 0;
53
+ let tooLarge = false;
54
+ return {
55
+ markTooLarge() {
56
+ tooLarge = true;
57
+ },
58
+ pushLine(raw) {
59
+ if (tooLarge) return false;
60
+ let line = String(raw);
61
+ if (lines++ === 0 && line.charCodeAt(0) === 0xfeff) line = line.slice(1);
62
+ if (line.endsWith('\r')) line = line.slice(0, -1);
63
+ if (!line.trim()) return true;
64
+ let value;
65
+ try {
66
+ value = JSON.parse(line);
67
+ } catch {
68
+ malformed += 1;
69
+ return true;
70
+ }
71
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
72
+ malformed += 1;
73
+ return true;
74
+ }
75
+ records += 1;
76
+ reducer.push(value);
77
+ if (
78
+ reducer.chars > maxTranscriptChars ||
79
+ reducer.entries > maxRetainedEntries ||
80
+ reducer.metadataBytes > maxMetadataBytes
81
+ ) {
82
+ tooLarge = true;
83
+ }
84
+ return !tooLarge;
85
+ },
86
+ result() {
87
+ const parsed = reducer.result();
88
+ const turns = normalizeTurns(parsed.rawTurns);
89
+ // Joins and the user stub add text the reducer never counted.
90
+ if (transcriptChars(turns) > maxTranscriptChars) tooLarge = true;
91
+ const warnings = new Set(parsed.warnings);
92
+ if (malformed) warnings.add(IMPORT_WARNING_MALFORMED);
93
+ let errorCode = null;
94
+ if (tooLarge) errorCode = IMPORT_ERROR_TOO_LARGE;
95
+ else if (!records) errorCode = malformed ? 'malformed' : 'empty';
96
+ else if (!parsed.recognized) errorCode = 'unsupported';
97
+ else if (!turns.length) errorCode = 'empty';
98
+ return {
99
+ source,
100
+ errorCode,
101
+ foreignSessionId: parsed.foreignSessionId,
102
+ cwd: parsed.cwd,
103
+ turns: tooLarge ? [] : turns,
104
+ title: conversationTitle(turns),
105
+ messageCount: tooLarge ? 0 : turns.length,
106
+ warnings: [...warnings],
107
+ };
108
+ },
109
+ };
110
+ }
111
+
112
+ /** Convert an in-memory JSONL body; `complete:false` drops a trailing partial line. */
113
+ export function convertConversationText(source, text, { complete = true, ...limits } = {}) {
114
+ const converter = createConversationConverter(source, limits);
115
+ let body = String(text ?? '');
116
+ if (!complete) {
117
+ const end = body.lastIndexOf('\n');
118
+ body = end < 0 ? '' : body.slice(0, end);
119
+ }
120
+ let start = 0;
121
+ while (start < body.length) {
122
+ const newline = body.indexOf('\n', start);
123
+ const end = newline < 0 ? body.length : newline;
124
+ if (!converter.pushLine(body.slice(start, end))) break;
125
+ start = end + 1;
126
+ }
127
+ return converter.result();
128
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Materialize an imported conversation as an ordinary Lead session: the normal
3
+ * createSession initialization (route, tools, context limits, cache config,
4
+ * system messages, BP3 fields) followed by the converted plain-text turns,
5
+ * with `importOrigin` provenance. Persisted durably before returning, then
6
+ * dropped from the same-process live cache.
7
+ */
8
+ import { createSession } from '../manager/session-lifecycle.mjs';
9
+ import { deleteSession, evictLiveSession, saveSessionAsync } from '../store.mjs';
10
+ import { newTranscriptMessageId } from '../../../../shared/transcript-metadata.mjs';
11
+ import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
12
+ import { IMPORTED_MESSAGE_SOURCE } from '../imported-message.mjs';
13
+ import { ingestSessionTurn } from '../../../../session-search/session-search-ingest.mjs';
14
+
15
+ export const IMPORT_ORIGIN_VERSION = 1;
16
+
17
+ /** Dedupe identity: the foreign session id, else the canonical source path. */
18
+ export function importOriginKey(source, foreignSessionId, canonicalPath) {
19
+ return foreignSessionId ? `${source}:${foreignSessionId}` : `${source}:path:${canonicalPath}`;
20
+ }
21
+
22
+ export function buildImportOrigin({ source, sourcePath, foreignSessionId = null, sourceCwd = null, importedAt }) {
23
+ return {
24
+ version: IMPORT_ORIGIN_VERSION,
25
+ source,
26
+ sourcePath,
27
+ foreignSessionId: foreignSessionId || null,
28
+ sourceCwd: sourceCwd || null,
29
+ importedAt,
30
+ key: importOriginKey(source, foreignSessionId, sourcePath),
31
+ };
32
+ }
33
+
34
+ // Lead identity every imported session carries (what a normal Lead session has).
35
+ const LEAD_FIELDS = Object.freeze({ owner: 'cli', agent: 'lead', lane: 'cli', sourceType: 'lead', sourceName: 'main' });
36
+ // Ownership, live binding and placement the importing runtime must not lend.
37
+ const NOT_INHERITED = [
38
+ 'id',
39
+ 'parentSessionId',
40
+ 'ownerSessionId',
41
+ 'visibility',
42
+ 'agentTag',
43
+ 'taskType',
44
+ 'permission',
45
+ 'permissionMode',
46
+ 'clientHostPid',
47
+ 'mcpScopeId',
48
+ 'approvalMode',
49
+ 'scopeKey',
50
+ 'cwd',
51
+ 'desktopSession',
52
+ ];
53
+
54
+ /** Lead createSession options → options of a fresh, unbound imported session. */
55
+ export function importedSessionOptions(leadOptions, { cwd = null, desktopSession }) {
56
+ const options = { ...leadOptions };
57
+ for (const key of NOT_INHERITED) delete options[key];
58
+ Object.assign(options, LEAD_FIELDS);
59
+ if (cwd) options.cwd = cwd;
60
+ options.desktopSession = desktopSession;
61
+ return options;
62
+ }
63
+
64
+ /** Normalized turns → plain user/assistant transcript messages. `meta.source`
65
+ * marks them imported prose: restore and the model wire never read their text
66
+ * as runtime control content (reminders, task envelopes, summaries). */
67
+ export function importedTranscriptMessages(turns, importedAt) {
68
+ return turns.map((turn) => ({
69
+ role: turn.role,
70
+ content: turn.text,
71
+ meta: {
72
+ source: IMPORTED_MESSAGE_SOURCE,
73
+ transcript: { messageId: newTranscriptMessageId(), at: turn.at || importedAt },
74
+ },
75
+ }));
76
+ }
77
+
78
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
79
+
80
+ // createSession's own debounced save keeps the id pinned until it settles.
81
+ async function evictWhenSettled(id, timeoutMs) {
82
+ const deadline = Date.now() + timeoutMs;
83
+ while (!evictLiveSession(id)) {
84
+ if (Date.now() >= deadline) return false;
85
+ await sleep(25);
86
+ }
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Create, fill and persist one imported session. Save failures reject (the
92
+ * half-made record is removed); once saved it is handed to content-search
93
+ * ingestion (the same fire-and-forget ingest a finished turn gets) so it is
94
+ * searchable right away. The live-cache eviction afterwards is bookkeeping only.
95
+ */
96
+ export async function createImportedSession({
97
+ leadOptions,
98
+ conversation,
99
+ origin,
100
+ cwd = null,
101
+ desktopSession,
102
+ createSessionFn = createSession,
103
+ ingestFn = ingestSessionTurn,
104
+ evictTimeoutMs = 5_000,
105
+ }) {
106
+ const session = createSessionFn(importedSessionOptions(leadOptions, { cwd, desktopSession }));
107
+ try {
108
+ // Append after the system layer createSession seeded; never replace it.
109
+ session.messages.push(...importedTranscriptMessages(conversation.turns, origin.importedAt));
110
+ session.importOrigin = origin;
111
+ if (conversation.title) session.title = conversation.title;
112
+ session.updatedAt = Date.now();
113
+ await saveSessionAsync(session);
114
+ } catch (error) {
115
+ try {
116
+ deleteSession(session.id);
117
+ } catch (cleanupError) {
118
+ writeStderrBestEffort(
119
+ `[conversation-import] cleanup of ${session.id} failed: ${cleanupError?.message || cleanupError}\n`
120
+ );
121
+ }
122
+ throw error;
123
+ }
124
+ ingestFn(session);
125
+ await evictWhenSettled(session.id, evictTimeoutMs);
126
+ return session;
127
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shared shape of an imported conversation: plain-text user/assistant turns,
3
+ * the markers that stand in for dropped content, and the title rule. Codex and
4
+ * Claude Code parsers both emit raw turns `{ role, parts: [{ text, note }], at }`
5
+ * and normalize them here. Pure, leaf, no IO.
6
+ */
7
+
8
+ /** Language-neutral warning codes attached to an imported conversation. */
9
+ export const IMPORT_WARNING_IMAGE_OMITTED = 'imageOmitted';
10
+ export const IMPORT_WARNING_MALFORMED = 'malformed';
11
+ export const IMPORT_WARNING_CWD_MISSING = 'cwdMissing';
12
+
13
+ /** Stands in for an image the importer does not carry over. */
14
+ export const IMAGE_OMITTED_TEXT = '[image omitted]';
15
+ /** Leads a conversation whose first kept turn is the assistant's. */
16
+ export const IMPORTED_USER_STUB_TEXT = '[Imported conversation continues from an earlier assistant reply.]';
17
+
18
+ const TITLE_MAX_CHARS = 100;
19
+
20
+ /** Plain-text note for a tool call: its name only, never its arguments. */
21
+ export function toolNote(name) {
22
+ const clean = String(name || '')
23
+ .replace(/\s+/g, ' ')
24
+ .trim();
25
+ return { text: `[ran tool: ${clean || 'unknown'}]`, note: true };
26
+ }
27
+
28
+ export function textPart(text) {
29
+ return { text: String(text), note: false };
30
+ }
31
+
32
+ /** Whole-text match of one XML-style envelope element (`<tag>…</tag>`). */
33
+ export function isWholeElement(text, tags) {
34
+ const trimmed = String(text || '').trim();
35
+ return tags.some(
36
+ (tag) => (trimmed.startsWith(`<${tag}>`) || trimmed.startsWith(`<${tag} `)) && trimmed.endsWith(`</${tag}>`)
37
+ );
38
+ }
39
+
40
+ export function timestampMs(value) {
41
+ const ms = typeof value === 'number' ? value : Date.parse(String(value || ''));
42
+ return Number.isFinite(ms) && ms > 0 ? ms : null;
43
+ }
44
+
45
+ // Consecutive tool notes stay one per line; every other boundary is a blank line.
46
+ function joinParts(parts) {
47
+ let text = '';
48
+ let previous = null;
49
+ for (const part of parts) {
50
+ const piece = String(part.text || '').trim();
51
+ if (!piece) continue;
52
+ if (previous) text += previous.note && part.note ? '\n' : '\n\n';
53
+ text += piece;
54
+ previous = part;
55
+ }
56
+ return { text, last: previous };
57
+ }
58
+
59
+ /**
60
+ * Raw turns → normalized turns `{ role, text, at, stub? }`: empty turns dropped,
61
+ * consecutive same-role turns merged, and a short user stub inserted when the
62
+ * conversation would otherwise begin with the assistant.
63
+ */
64
+ export function normalizeTurns(rawTurns) {
65
+ const merged = [];
66
+ for (const raw of rawTurns) {
67
+ if (raw?.role !== 'user' && raw?.role !== 'assistant') continue;
68
+ const parts = (Array.isArray(raw.parts) ? raw.parts : []).filter((part) => String(part?.text || '').trim());
69
+ if (!parts.length) continue;
70
+ const previous = merged[merged.length - 1];
71
+ if (previous && previous.role === raw.role) previous.parts.push(...parts);
72
+ else merged.push({ role: raw.role, parts: [...parts], at: timestampMs(raw.at) });
73
+ }
74
+ const turns = merged.map(({ role, parts, at }) => {
75
+ const turn = { role, text: joinParts(parts).text, at };
76
+ // A carried-over compaction summary never names the conversation.
77
+ if (parts.some((part) => part.summary)) turn.titleText = joinParts(parts.filter((part) => !part.summary)).text;
78
+ return turn;
79
+ });
80
+ if (turns[0]?.role === 'assistant') {
81
+ turns.unshift({ role: 'user', text: IMPORTED_USER_STUB_TEXT, at: turns[0].at, stub: true });
82
+ }
83
+ return turns;
84
+ }
85
+
86
+ const titleLines = (turn) =>
87
+ (turn.titleText ?? turn.text)
88
+ .split('\n')
89
+ .map((value) => value.replace(/\s+/g, ' ').trim())
90
+ .filter(Boolean);
91
+
92
+ /** First line of the first genuine user turn, whitespace-normalized, ≤100 chars
93
+ * (an image marker only when the turn has no text line). */
94
+ export function conversationTitle(turns) {
95
+ const first = turns.find((turn) => turn.role === 'user' && !turn.stub && titleLines(turn).length);
96
+ if (!first) return null;
97
+ const lines = titleLines(first);
98
+ const line = lines.find((value) => value !== IMAGE_OMITTED_TEXT) || lines[0];
99
+ if (!line) return null;
100
+ return Array.from(line).slice(0, TITLE_MAX_CHARS).join('').trim() || null;
101
+ }
@@ -1,6 +1,10 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { selectedSkillName } from '../../../shared/skill-selection.mjs';
3
+ import { mentionedSkillNames, skillMentionCandidates } from '../../../shared/skill-mentions.mjs';
4
+ import { isAttachmentReference } from '../../../attachments/store.mjs';
5
+ import { collectPromptSkillsCached } from '../context/collect.mjs';
3
6
  import { executeTool } from './loop/tool-exec.mjs';
7
+ import { preDispatchDenyForSession } from './loop/pre-dispatch-deny.mjs';
4
8
  import { parseNativeToolSearchPayload } from './loop/tool-helpers.mjs';
5
9
  import { normalizeToolEnvelope } from './tool-envelope.mjs';
6
10
  import { throwIfAborted } from '../../../shared/abort-race.mjs';
@@ -20,19 +24,91 @@ function nativeDefinitionsPresent(native, messages) {
20
24
  );
21
25
  }
22
26
 
27
+ // At most this many skills are prepared for one user turn; further selections
28
+ // stay prose.
29
+ const MAX_EXPLICIT_SKILLS = 5;
30
+ const LOADED_SKILL_RE = /^Loaded (?:built-in )?skill: /;
31
+
32
+ // The user's own typed text: plain strings and text parts, never attachment
33
+ // references (file contents the user attached, not words they typed).
34
+ function userPromptText(prompt) {
35
+ if (typeof prompt === 'string') return prompt;
36
+ if (!Array.isArray(prompt)) return '';
37
+ return prompt
38
+ .map((part) => {
39
+ if (typeof part === 'string') return part;
40
+ if (part?.type === 'text' && typeof part.text === 'string' && !isAttachmentReference(part)) return part.text;
41
+ return '';
42
+ })
43
+ .join('\n');
44
+ }
45
+
46
+ /** The skills a prompt explicitly selects: the leading `Skill: "name"` header,
47
+ * then every inline `$name`, each only when it names a skill enabled and in
48
+ * scope for `cwd`, in first-mention order, deduplicated and capped. */
49
+ export function explicitSkillNames(prompt, cwd) {
50
+ const header = selectedSkillName(prompt);
51
+ const text = userPromptText(prompt);
52
+ if (!header && !skillMentionCandidates(text).length) return [];
53
+ const available = new Set(collectPromptSkillsCached(cwd).map((skill) => skill.name));
54
+ const isSkill = (name) => available.has(name);
55
+ const names = header && isSkill(header) ? [header] : [];
56
+ for (const name of mentionedSkillNames(text, isSkill)) {
57
+ if (!names.includes(name)) names.push(name);
58
+ }
59
+ return names.slice(0, MAX_EXPLICIT_SKILLS);
60
+ }
61
+
62
+ /** Stamps the skills a user turn actually loaded on its transcript metadata,
63
+ * where the live row, the persisted transcript and restore all read it.
64
+ * The stamped message REPLACES `message` in `messages` (returned): the
65
+ * session store ships reference-identical messages as an append-only delta,
66
+ * so an in-place edit of a user message an earlier save already sent would
67
+ * never reach disk. */
68
+ export function recordAppliedSkills(messages, message, names) {
69
+ if (!message || !Array.isArray(names) || !names.length) return message;
70
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta : {};
71
+ const transcript = meta.transcript && typeof meta.transcript === 'object' ? meta.transcript : {};
72
+ const stamped = { ...message, meta: { ...meta, transcript: { ...transcript, appliedSkills: [...names] } } };
73
+ const index = Array.isArray(messages) ? messages.indexOf(message) : -1;
74
+ if (index >= 0) messages[index] = stamped;
75
+ return stamped;
76
+ }
77
+
78
+ /** Tells the host which skills a user turn loaded; a host callback failure
79
+ * never fails the turn. */
80
+ export function notifySkillsApplied(onSkillsApplied, applied, message) {
81
+ if (!applied.length) return;
82
+ try {
83
+ onSkillsApplied?.(applied, { messageId: message?.meta?.transcript?.messageId || null });
84
+ } catch {
85
+ /* best-effort host callback */
86
+ }
87
+ }
88
+
23
89
  // Called before the first model request for a real user turn. This performs the
24
90
  // same policy-checked Skill operation, without asking the model to request it.
25
91
  // Native schemas still travel as a properly paired loader execution, never as
26
- // new eager definitions or an orphan tool_search_output.
92
+ // new eager definitions or an orphan tool_search_output. Returns the names of
93
+ // the skills that loaded successfully, in selection order.
94
+ // `selection`, when given (even empty), is the turn's TRUSTED selection input
95
+ // and the only text skills are selected from: a host whose model-visible
96
+ // prompt carries external data (webhook payloads) passes just the
97
+ // operator-authored part, so nothing inside the data can load a skill.
27
98
  export async function prepareExplicitSkills(
28
99
  prompt,
29
100
  messages,
30
101
  session,
31
- { cwd = session?.cwd, signal, execute = executeTool } = {}
102
+ { cwd = session?.cwd, signal, execute = executeTool, selection } = {}
32
103
  ) {
33
104
  throwIfAborted(signal);
34
- const name = selectedSkillName(prompt);
35
- if (name) {
105
+ const applied = [];
106
+ // A session that cannot call Skill (schema allowlist, denied tools, read-only
107
+ // surface — the dispatcher's own gate) has nothing to select: preparing would
108
+ // only push a refused-tool reminder into history on every matching turn.
109
+ if (preDispatchDenyForSession(session, { name: 'Skill' })) return applied;
110
+ const source = selection === undefined || selection === null ? prompt : selection;
111
+ for (const name of explicitSkillNames(source, cwd)) {
36
112
  const id = `skill-selection-${randomUUID()}`;
37
113
  const args = { name };
38
114
  const raw = await execute('Skill', args, cwd, session?.id, session, { signal, toolCallId: id });
@@ -55,6 +131,8 @@ export async function prepareExplicitSkills(
55
131
  });
56
132
  }
57
133
  messages.push(...envelope.newMessages);
134
+ if (LOADED_SKILL_RE.test(String(summary))) applied.push(name);
135
+ throwIfAborted(signal);
58
136
  }
59
- throwIfAborted(signal);
137
+ return applied;
60
138
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Provenance of messages carried over from another app's conversation
3
+ * (conversation-import). Their text is prose someone wrote elsewhere: it may
4
+ * look like a runtime reminder, a task envelope or a compaction summary, but
5
+ * restore and the model wire treat it as plain user/assistant text. Leaf, no IO.
6
+ */
7
+ export const IMPORTED_MESSAGE_SOURCE = 'import';
8
+
9
+ export function isImportedMessage(message) {
10
+ return message?.meta?.source === IMPORTED_MESSAGE_SOURCE;
11
+ }
@@ -2,6 +2,7 @@
2
2
  // (signature + stub) and the edit-progress classification.
3
3
  // Pure string/signature helpers; the loop body only wires state + messages.
4
4
  // No provider/manager coupling.
5
+ import { knownMcpToolIdentity } from '../../../../shared/tool-primitives.mjs';
5
6
 
6
7
  // Deterministic, key-sorted stringify for cross-turn call signatures (recursive,
7
8
  // unlike tool-classify's top-level-only _canonicalArgs).
@@ -43,7 +44,7 @@ const NON_PROGRESS_TOOLS = new Set(['Skill', 'recall', 'agent', 'task', 'cwd', '
43
44
  // not in the meta/non-progress set. apply_patch and shell/bash always count.
44
45
  export function isEditProgressTool(name, isEager) {
45
46
  if (isEager) return false;
46
- const bare = name?.startsWith('mcp__') ? name.split('__').pop() : name;
47
+ const bare = name?.startsWith('mcp__') ? knownMcpToolIdentity(name)?.tool || name.split('__').pop() : name;
47
48
  if (bare === 'apply_patch' || bare === 'shell' || bare === 'bash' || bare === 'bash_session') return true;
48
49
  return !NON_PROGRESS_TOOLS.has(bare);
49
50
  }
@@ -146,7 +146,7 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
146
146
  messages,
147
147
  opts,
148
148
  sessionId,
149
- onSkillPrompt: (content) => state.pendingSkillPrompts.push(content),
149
+ onSkillPrompt: (content, message) => state.pendingSkillPrompts.push({ content, message }),
150
150
  });
151
151
  // Bounded recovery for provider turns that returned no client tool calls
152
152
  // (max-output ladder, refusal retry, provider continuation, empty-turn
@@ -34,7 +34,7 @@ export function resumeContinuation(
34
34
  return false;
35
35
  }
36
36
  if (!segments.commitIntermediate(response)) return false;
37
- if (hasContent) segments.record(response.content);
37
+ if (hasContent) segments.record(response.content, { committed: true });
38
38
  state.providerContinuationCount += 1;
39
39
  state.continuationsSinceToolBatch += 1;
40
40
  state.emptyNudgeStreak = 0;
@@ -1,8 +1,2 @@
1
1
  // stderr diagnostics for the no-tool recovery ladders; never throws.
2
- export function writeLoopDiagnostic(line) {
3
- try {
4
- process.stderr.write(line);
5
- } catch {
6
- /* diagnostics only */
7
- }
8
- }
2
+ export { writeStderrBestEffort as writeLoopDiagnostic } from '../../../../../shared/best-effort-stderr.mjs';
@@ -15,7 +15,7 @@ export function resolveRefusal(response, hasContent, { state, segments, messages
15
15
  // A provider may emit harmless narration before its safety classifier
16
16
  // terminates the turn. Preserve that partial turn and its stop reason,
17
17
  // but never mistake the non-empty text for a successful completion.
18
- if (hasContent && segments.commitIntermediate(response)) segments.record(response.content);
18
+ if (hasContent && segments.commitIntermediate(response)) segments.record(response.content, { committed: true });
19
19
  messages.push({
20
20
  role: 'user',
21
21
  content: REFUSAL_RECOVERY_PROMPT,
@@ -21,14 +21,15 @@ export function createTurnSegments({ messages, opts, suppressMidTurnText }) {
21
21
  // live. A suppressed sub-agent session neither surfaces nor accumulates
22
22
  // text — except on the max-output ladder, whose parts must still rebuild
23
23
  // the complete answer the caller receives (keepWhenSuppressed).
24
- record(text, { keepWhenSuppressed = false } = {}) {
24
+ // `committed: true` marks text that is the just-committed message's own.
25
+ record(text, { keepWhenSuppressed = false, committed = false } = {}) {
25
26
  if (suppressMidTurnText) {
26
27
  if (keepWhenSuppressed) parts.push(text);
27
28
  return;
28
29
  }
29
30
  parts.push(text);
30
31
  try {
31
- opts.onAssistantText?.(text);
32
+ opts.onAssistantText?.(text, { committed });
32
33
  } catch {
33
34
  /* best-effort */
34
35
  }
@@ -34,8 +34,9 @@ export function createNoToolTurnResolver({
34
34
  refusalRetryUsed: false,
35
35
  emptyNudgeStreak: 0,
36
36
  // Structured provider continuation signals honored this turn. Diagnostic
37
- // only; the hard iteration cap remains the sole bound on how long a
38
- // provider may keep declaring "not done" inside one user turn.
37
+ // only: the loop has no iteration cap, so this count bounds nothing. A
38
+ // text-only continuation runaway is bounded by continuationsSinceToolBatch
39
+ // below; continuations interleaved with tool batches are not limited.
39
40
  providerContinuationCount: 0,
40
41
  // Continuations since the last executed tool batch — bounds the text-only
41
42
  // continuation runaway (see PROVIDER_CONTINUATION_NO_TOOL_LIMIT).
@@ -108,7 +109,8 @@ export function createNoToolTurnResolver({
108
109
  drainSteering('terminal', {
109
110
  maxPriority: 'next',
110
111
  beforeAppend: () => {
111
- if (segments.commitIntermediate(response) && hasContent) segments.record(response.content);
112
+ if (segments.commitIntermediate(response) && hasContent)
113
+ segments.record(response.content, { committed: true });
112
114
  },
113
115
  })
114
116
  ) {
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { traceCacheBreak } from '../../cache-break-trace.mjs';
7
7
  import { resolveLiveToolCwd } from './tool-exec.mjs';
8
- import { prepareExplicitSkills } from '../explicit-skills.mjs';
8
+ import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
9
9
  import { addUsage, usageDeltaEvent } from './usage.mjs';
10
10
  import { prepareProviderRequest } from './request-boundary.mjs';
11
11
  import { projectProviderRequest } from './request-projection.mjs';
@@ -44,10 +44,13 @@ export async function beginIteration(state) {
44
44
  // policy-checked selection before the next provider snapshot instead of
45
45
  // starting detached work from a drain callback.
46
46
  while (state.pendingSkillPrompts.length) {
47
- await prepareExplicitSkills(state.pendingSkillPrompts.shift(), messages, sessionRef, {
47
+ const { content, message: queued } = state.pendingSkillPrompts.shift();
48
+ const applied = await prepareExplicitSkills(content, messages, sessionRef, {
48
49
  cwd: state.cwd,
49
50
  signal: opts.signal,
50
51
  });
52
+ const message = recordAppliedSkills(messages, queued, applied);
53
+ notifySkillsApplied(opts.onSkillsApplied, applied, message);
51
54
  }
52
55
  const boundary = await prepareProviderRequest({
53
56
  provider,
@@ -263,8 +266,7 @@ function traceActualCacheMiss(state, sent, response, { previousGuardState, previ
263
266
  const ttlMs = MESSAGES_CACHE_TTL_MS[sessionRef.providerCacheOpts?.cacheStrategy?.messages];
264
267
  const idleMs = sent.sendStartedAt - previousSendAt;
265
268
  const requestPrefixChanged =
266
- !!previousGuardState &&
267
- previousGuardState.requestPrefixHash !== sent.prefixGuardCandidate?.requestPrefixHash;
269
+ !!previousGuardState && previousGuardState.requestPrefixHash !== sent.prefixGuardCandidate?.requestPrefixHash;
268
270
  if (
269
271
  baseline > 0 &&
270
272
  ttlMs &&
@@ -375,6 +377,7 @@ export function settleSendResult(state, round, sent) {
375
377
  model: response.model || model,
376
378
  usage: reportedUsage,
377
379
  sendTools: round.sendTools,
380
+ toolCalls: response.toolCalls?.length || 0,
378
381
  })
379
382
  )
380
383
  );