mixdog 0.9.1 → 0.9.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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -0,0 +1,227 @@
1
+ // Context-window sizing, compaction target math, and session context-meta
2
+ // resolution. Extracted verbatim from manager.mjs (behavior-preserving).
3
+ import { getModelMetadataSync } from '../../providers/model-catalog.mjs';
4
+ import { resolveCompactTriggerTokens } from '../context-utils.mjs';
5
+ import { normalizeCompactType, DEFAULT_COMPACT_TYPE } from '../compact.mjs';
6
+
7
+ // Known context windows for the current-generation models this plugin
8
+ // routes to. Anything not listed falls through to guessContextWindow() —
9
+ // local llama/mistral/phi default to 8192, everything else 128000. Keep
10
+ // this map trimmed to live models; older generations slow down reads
11
+ // without buying anything.
12
+ const CONTEXT_WINDOWS = {
13
+ // OpenAI GPT-5.x family (openai / openai-oauth)
14
+ 'gpt-5.5': 272000,
15
+ 'gpt-5.4': 272000,
16
+ 'gpt-5.4-mini': 272000,
17
+ 'gpt-5.4-nano': 272000,
18
+ // Anthropic Claude 4.x
19
+ 'claude-opus-4-8': 1000000,
20
+ 'claude-opus-4-7': 1000000,
21
+ 'claude-sonnet-4-6': 1000000,
22
+ 'claude-haiku-4-5-20251001': 200000,
23
+ // Google Gemini 3.x
24
+ 'gemini-3.1-pro': 1000000,
25
+ 'gemini-3-pro': 1000000,
26
+ 'gemini-3.5-flash': 1000000,
27
+ 'gemini-3-flash': 1000000,
28
+ // xAI Grok (catalog polyfill mirror — model-catalog PRICING_OVERRIDES)
29
+ 'grok-build-0.1': 256000,
30
+ 'grok-4.20': 1000000,
31
+ };
32
+ // Family-pattern fallback used only when both the provider catalog and the
33
+ // exact-id table miss (cold metadata, before the LiteLLM/models.dev catalog
34
+ // warms). Keep these aligned with the catalog so /context, gateway, and the
35
+ // runtime agree on the boundary the first time a model is routed. Local models
36
+ // (llama/mistral/phi/qwen/gemma) stay small so an unknown local id never claims
37
+ // a giant window.
38
+ export function guessContextWindow(model) {
39
+ if (CONTEXT_WINDOWS[model])
40
+ return CONTEXT_WINDOWS[model];
41
+ const m = String(model || '').toLowerCase();
42
+ // Local/self-hosted families — never inflate an unknown local id.
43
+ if (m.includes('llama') || m.includes('mistral') || m.includes('mixtral')
44
+ || m.includes('phi') || m.includes('qwen') || m.includes('gemma')
45
+ || m.includes('deepseek-r1') || m.includes('codellama'))
46
+ return 8192;
47
+ // Current hosted families by name pattern.
48
+ if (m.startsWith('claude-opus') || m.startsWith('claude-sonnet')) return 1000000;
49
+ if (m.startsWith('claude-haiku') || m.startsWith('claude-')) return 200000;
50
+ if (m.startsWith('gemini-3') || m.startsWith('gemini-2')) return 1000000;
51
+ if (m.startsWith('gpt-5')) return 272000;
52
+ if (m.startsWith('grok-build')) return 256000;
53
+ if (m.startsWith('grok-')) return 1000000;
54
+ if (m.startsWith('deepseek-v')) return 1000000;
55
+ return 128000;
56
+ }
57
+ export function positiveContextWindow(value) {
58
+ const n = Number(value);
59
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
60
+ }
61
+ export function envFlag(name, fallback = false) {
62
+ const v = process.env[name];
63
+ if (v === undefined) return fallback;
64
+ return !['0', 'false', 'off', 'no'].includes(String(v).trim().toLowerCase());
65
+ }
66
+ function boundedPercent(value, fallback = null) {
67
+ const n = Number(value);
68
+ if (Number.isFinite(n) && n > 0 && n <= 100) return n;
69
+ return fallback;
70
+ }
71
+ function providerNameOf(provider) {
72
+ if (typeof provider === 'string') return provider.toLowerCase();
73
+ return String(provider?.name || provider?.id || '').toLowerCase();
74
+ }
75
+ // Carry the percent/ratio-named buffer config from a compaction config object
76
+ // onto session.compaction so the shared compact-policy parser honors configured
77
+ // buffer
78
+ // percent/ratio. Only finite positive values are copied; absent fields stay
79
+ // undefined so the default-ratio fallback still applies.
80
+ export function preserveBufferConfigFields(cfg = {}) {
81
+ const out = {};
82
+ for (const key of ['bufferPercent', 'bufferPct', 'bufferRatio', 'bufferFraction']) {
83
+ const n = Number(cfg?.[key]);
84
+ if (Number.isFinite(n) && n > 0) out[key] = n;
85
+ }
86
+ return out;
87
+ }
88
+ const COMPACT_TARGET_RATIO = 0.02;
89
+ const COMPACT_TARGET_MIN_TOKENS = 4_000;
90
+ const COMPACT_TARGET_MAX_TOKENS = 16_000;
91
+ function compactTargetRatio() {
92
+ const raw = process.env.MIXDOG_AGENT_COMPACT_TARGET_PERCENT
93
+ ?? process.env.MIXDOG_COMPACT_TARGET_PERCENT
94
+ ?? COMPACT_TARGET_RATIO;
95
+ const n = Number(raw);
96
+ if (!Number.isFinite(n) || n <= 0) return COMPACT_TARGET_RATIO;
97
+ return n > 1 ? n / 100 : n;
98
+ }
99
+ function compactTargetTokensForBoundary(boundaryTokens) {
100
+ const boundary = positiveContextWindow(boundaryTokens);
101
+ if (!boundary) return null;
102
+ const explicit = positiveContextWindow(
103
+ process.env.MIXDOG_AGENT_COMPACT_TARGET_TOKENS
104
+ ?? process.env.MIXDOG_COMPACT_TARGET_TOKENS,
105
+ );
106
+ if (explicit) return Math.max(1, Math.min(boundary, explicit));
107
+ const minTarget = Math.min(boundary, positiveContextWindow(process.env.MIXDOG_COMPACT_TARGET_MIN_TOKENS) || COMPACT_TARGET_MIN_TOKENS);
108
+ const maxTarget = Math.min(boundary, positiveContextWindow(process.env.MIXDOG_COMPACT_TARGET_MAX_TOKENS) || COMPACT_TARGET_MAX_TOKENS);
109
+ const byRatio = Math.max(1, Math.floor(boundary * compactTargetRatio()));
110
+ return Math.max(1, Math.min(boundary, maxTarget, Math.max(minTarget, byRatio)));
111
+ }
112
+ function defaultEffectiveContextWindowPercent(provider) {
113
+ // Gateway/statusline route metadata reserves a universal 10% headroom from
114
+ // the raw catalog window. Keep session compaction on the same effective
115
+ // capacity so /context, the TUI statusline, and gateway telemetry agree.
116
+ return 90;
117
+ }
118
+ const PROVIDER_SYNTHETIC_CONTEXT_DEFAULT = 1_000_000;
119
+ function providerRawContextWindow(info, catalogInfo) {
120
+ if (!info || typeof info !== 'object') return null;
121
+ const fromApiFields = positiveContextWindow(info.context_window)
122
+ || positiveContextWindow(info.max_context_window);
123
+ if (fromApiFields) return fromApiFields;
124
+ const fromCache = positiveContextWindow(info.contextWindow)
125
+ || positiveContextWindow(info.maxContextWindow);
126
+ if (!fromCache) return null;
127
+ const catalogWindow = positiveContextWindow(catalogInfo?.contextWindow)
128
+ || positiveContextWindow(catalogInfo?.maxContextWindow)
129
+ || positiveContextWindow(catalogInfo?.context_window)
130
+ || positiveContextWindow(catalogInfo?.max_context_window);
131
+ if (fromCache === PROVIDER_SYNTHETIC_CONTEXT_DEFAULT
132
+ && catalogWindow
133
+ && fromCache !== catalogWindow) {
134
+ return null;
135
+ }
136
+ return fromCache;
137
+ }
138
+ export function resolveSessionContextMeta(provider, model, seed = {}) {
139
+ const info = typeof provider?.getCachedModelInfo === 'function'
140
+ ? provider.getCachedModelInfo(model)
141
+ : null;
142
+ const catalogInfo = getModelMetadataSync(model, providerNameOf(provider));
143
+ const rawContextWindow = providerRawContextWindow(info, catalogInfo)
144
+ || positiveContextWindow(catalogInfo?.contextWindow)
145
+ || positiveContextWindow(catalogInfo?.maxContextWindow)
146
+ || positiveContextWindow(catalogInfo?.context_window)
147
+ || positiveContextWindow(catalogInfo?.max_context_window)
148
+ || positiveContextWindow(seed.rawContextWindow)
149
+ || positiveContextWindow(seed.raw_context_window)
150
+ || positiveContextWindow(seed.contextWindow)
151
+ || guessContextWindow(model);
152
+ const effectiveContextWindowPercent = boundedPercent(
153
+ seed.effectiveContextWindowPercent
154
+ ?? seed.effective_context_window_percent
155
+ ?? info?.effectiveContextWindowPercent
156
+ ?? info?.effective_context_window_percent
157
+ ?? catalogInfo?.effectiveContextWindowPercent
158
+ ?? catalogInfo?.effective_context_window_percent,
159
+ defaultEffectiveContextWindowPercent(provider),
160
+ );
161
+ const pct = boundedPercent(effectiveContextWindowPercent, 100);
162
+ const contextWindow = Math.max(1, Math.floor(rawContextWindow * pct / 100));
163
+ const compactBoundaryTokens = contextWindow;
164
+ const rawCompactLimit = positiveContextWindow(
165
+ seed.autoCompactTokenLimit
166
+ ?? seed.auto_compact_token_limit
167
+ ?? info?.autoCompactTokenLimit
168
+ ?? info?.auto_compact_token_limit
169
+ ?? catalogInfo?.autoCompactTokenLimit
170
+ ?? catalogInfo?.auto_compact_token_limit,
171
+ );
172
+ // Legacy-data migration: old implementations derived autoCompactTokenLimit
173
+ // from the full effective/raw window and persisted it onto the session.
174
+ // A resumed session therefore re-seeds autoCompactTokenLimit == boundary
175
+ // (or the raw window), which compactTriggerForSession / loop policy used to
176
+ // honor as an explicit trigger, collapsing the compaction buffer to 0. Only
177
+ // accept an explicit limit that is STRICTLY BELOW the boundary; a value at
178
+ // or above the boundary is a derived full-window artifact and is dropped to
179
+ // null so the trigger falls back to the default boundary trigger.
180
+ const explicitCompactLimit = rawCompactLimit && rawCompactLimit < compactBoundaryTokens
181
+ ? rawCompactLimit
182
+ : null;
183
+ // Do NOT derive the auto-compact limit from the full effective window.
184
+ // Setting it to contextWindow makes autoTriggerTokens == boundary and the
185
+ // compaction buffer collapse to 0 (loop.mjs:708-713 / compactTriggerForSession),
186
+ // so auto-compact only fires when the context is already at the limit —
187
+ // at which point semantic compact fails ("result exceeds budget" /
188
+ // "summary cannot fit") and the turn can no longer be resumed.
189
+ // Leave it null unless the provider/catalog/seed supplies an explicit
190
+ // limit; the downstream buffer logic (default 10%, capped 25%) then
191
+ // triggers compaction with headroom, matching the reference auto-compact threshold.
192
+ const autoCompactTokenLimit = explicitCompactLimit || null;
193
+ return {
194
+ contextWindow,
195
+ rawContextWindow,
196
+ effectiveContextWindowPercent,
197
+ autoCompactTokenLimit: autoCompactTokenLimit || null,
198
+ compactBoundaryTokens,
199
+ };
200
+ }
201
+ export function compactTriggerForSession(session, boundaryTokens) {
202
+ return resolveCompactTriggerTokens(session, boundaryTokens);
203
+ }
204
+ export function compactTargetBudget(boundaryTokens, reserveTokens, _sourceTokens = null, _ratio = null) {
205
+ const boundary = positiveContextWindow(boundaryTokens);
206
+ if (!boundary) return null;
207
+ const reserve = Math.max(0, Number(reserveTokens) || 0);
208
+ const targetEffective = compactTargetTokensForBoundary(boundary) || boundary;
209
+ return Math.max(1, Math.min(boundary, targetEffective + reserve));
210
+ }
211
+ export function semanticCompactionEnabledForSession(session) {
212
+ const cfg = session?.compaction || {};
213
+ if (process.env.MIXDOG_AGENT_COMPACT_SEMANTIC !== undefined) return envFlag('MIXDOG_AGENT_COMPACT_SEMANTIC', true);
214
+ if (process.env.MIXDOG_COMPACT_SEMANTIC !== undefined) return envFlag('MIXDOG_COMPACT_SEMANTIC', true);
215
+ if (cfg.semantic === false || cfg.semantic === 'false' || cfg.semantic === 'off') return false;
216
+ if (cfg.semantic === true || cfg.semantic === 'true' || cfg.semantic === 'on' || cfg.semantic === 'auto') return true;
217
+ return true;
218
+ }
219
+ export function compactTypeForSession(session) {
220
+ const cfg = session?.compaction || {};
221
+ const configured = process.env.MIXDOG_AGENT_COMPACT_TYPE
222
+ ?? process.env.MIXDOG_COMPACT_TYPE
223
+ ?? cfg.type
224
+ ?? cfg.compactType
225
+ ?? cfg.compact_type;
226
+ return normalizeCompactType(configured, DEFAULT_COMPACT_TYPE);
227
+ }
@@ -0,0 +1,235 @@
1
+ // Steering / pending-message queue with sync buffered + atomic-file persistence.
2
+ // Extracted verbatim from manager.mjs (behavior-preserving).
3
+ import { join } from 'path';
4
+ import { resolvePluginData } from '../../../../shared/plugin-paths.mjs';
5
+ import { updateJsonAtomicSync } from '../../../../shared/atomic-file.mjs';
6
+ import { promptContentText, isInternalRuntimeNotificationText } from './prompt-utils.mjs';
7
+
8
+ const _sessionPendingMessages = new Map();
9
+ const PENDING_MESSAGES_FILE = 'session-pending-messages.json';
10
+ const PENDING_MESSAGES_MODE = 0o600;
11
+ const _pendingPersistBuffers = new Map();
12
+ let _pendingPersistImmediate = null;
13
+
14
+ function pendingMessagesPath() {
15
+ return join(resolvePluginData(), PENDING_MESSAGES_FILE);
16
+ }
17
+
18
+ function isValidPendingSessionId(sessionId) {
19
+ return typeof sessionId === 'string' && /^[A-Za-z0-9_-]+$/.test(sessionId);
20
+ }
21
+
22
+ function normalizePendingStore(raw) {
23
+ const sessions = raw && typeof raw === 'object' && raw.sessions && typeof raw.sessions === 'object'
24
+ ? raw.sessions
25
+ : {};
26
+ const out = { version: 1, updatedAt: Date.now(), sessions: {} };
27
+ for (const [sid, value] of Object.entries(sessions)) {
28
+ if (!isValidPendingSessionId(sid) || !Array.isArray(value)) continue;
29
+ const q = value
30
+ .map((entry) => {
31
+ if (typeof entry === 'string') return entry;
32
+ if (entry && typeof entry === 'object' && typeof entry.message === 'string') return entry.message;
33
+ return '';
34
+ })
35
+ .filter(Boolean);
36
+ if (q.length > 0) out.sessions[sid] = q;
37
+ }
38
+ return out;
39
+ }
40
+
41
+ function normalizePendingMessageEntry(entry) {
42
+ if (typeof entry === 'string') {
43
+ const text = entry.trim();
44
+ return text ? { content: text, text } : null;
45
+ }
46
+ if (Array.isArray(entry)) {
47
+ if (entry.length === 0) return null;
48
+ const text = promptContentText(entry).trim();
49
+ return { content: entry, text };
50
+ }
51
+ if (!entry || typeof entry !== 'object') return null;
52
+ const content = Object.prototype.hasOwnProperty.call(entry, 'content') ? entry.content : null;
53
+ if (content == null) return null;
54
+ const text = typeof entry.text === 'string' ? entry.text.trim() : promptContentText(content).trim();
55
+ if (Array.isArray(content)) return content.length > 0 ? { content, text } : null;
56
+ if (typeof content === 'string') {
57
+ const value = content.trim();
58
+ return value ? { content: value, text: text || value } : null;
59
+ }
60
+ const fallback = promptContentText(content).trim();
61
+ return fallback ? { content: fallback, text: text || fallback } : null;
62
+ }
63
+
64
+ function pendingMessageText(entry) {
65
+ const normalized = normalizePendingMessageEntry(entry);
66
+ return normalized ? String(normalized.text || promptContentText(normalized.content) || '').trim() : '';
67
+ }
68
+
69
+ function pendingMessageQueueEntry(entry) {
70
+ const normalized = normalizePendingMessageEntry(entry);
71
+ if (!normalized) return null;
72
+ if (typeof normalized.content === 'string' && normalized.content === normalized.text) return normalized.content;
73
+ return { content: normalized.content, text: normalized.text || promptContentText(normalized.content).trim() };
74
+ }
75
+
76
+ function persistPendingMessages(sessionId, messages) {
77
+ if (!isValidPendingSessionId(sessionId)) return 0;
78
+ const persistedMessages = (Array.isArray(messages) ? messages : [messages])
79
+ .map(pendingMessageText)
80
+ .filter(Boolean);
81
+ if (persistedMessages.length === 0) return 0;
82
+ let depth = 0;
83
+ try {
84
+ updateJsonAtomicSync(pendingMessagesPath(), (raw) => {
85
+ const next = normalizePendingStore(raw);
86
+ const q = Array.isArray(next.sessions[sessionId]) ? next.sessions[sessionId] : [];
87
+ q.push(...persistedMessages);
88
+ next.sessions[sessionId] = q;
89
+ next.updatedAt = Date.now();
90
+ depth = q.length;
91
+ return next;
92
+ }, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
93
+ } catch (err) {
94
+ try { process.stderr.write(`[session] pending-message persist failed sessionId=${sessionId}: ${err?.message || err}\n`); } catch {}
95
+ }
96
+ return depth;
97
+ }
98
+
99
+ function flushPendingMessagePersistsSync() {
100
+ if (_pendingPersistImmediate) {
101
+ try { clearImmediate(_pendingPersistImmediate); } catch {}
102
+ _pendingPersistImmediate = null;
103
+ }
104
+ if (_pendingPersistBuffers.size === 0) return;
105
+ const batches = [..._pendingPersistBuffers.entries()];
106
+ _pendingPersistBuffers.clear();
107
+ for (const [sid, messages] of batches) {
108
+ persistPendingMessages(sid, messages);
109
+ }
110
+ }
111
+
112
+ function schedulePendingMessagePersist(sessionId, message) {
113
+ if (!isValidPendingSessionId(sessionId)) return 0;
114
+ const persistedMessage = pendingMessageText(message);
115
+ if (!persistedMessage) return 0;
116
+ const q = _pendingPersistBuffers.get(sessionId) || [];
117
+ q.push(persistedMessage);
118
+ _pendingPersistBuffers.set(sessionId, q);
119
+ if (!_pendingPersistImmediate) {
120
+ _pendingPersistImmediate = setImmediate(() => {
121
+ _pendingPersistImmediate = null;
122
+ flushPendingMessagePersistsSync();
123
+ });
124
+ }
125
+ return q.length;
126
+ }
127
+
128
+ function takeBufferedPendingMessages(sessionId) {
129
+ if (!isValidPendingSessionId(sessionId)) return [];
130
+ const buffered = _pendingPersistBuffers.get(sessionId);
131
+ if (!buffered || buffered.length === 0) return [];
132
+ _pendingPersistBuffers.delete(sessionId);
133
+ return buffered.slice();
134
+ }
135
+
136
+ function drainPersistedPendingMessages(sessionId) {
137
+ if (!isValidPendingSessionId(sessionId)) return [];
138
+ let drained = [];
139
+ try {
140
+ updateJsonAtomicSync(pendingMessagesPath(), (raw) => {
141
+ const next = normalizePendingStore(raw);
142
+ const q = Array.isArray(next.sessions[sessionId]) ? next.sessions[sessionId] : [];
143
+ drained = q.filter((m) => typeof m === 'string' && m.length > 0);
144
+ if (drained.length === 0) return undefined;
145
+ delete next.sessions[sessionId];
146
+ next.updatedAt = Date.now();
147
+ return next;
148
+ }, { compact: true, lock: true, mode: PENDING_MESSAGES_MODE, fsync: false });
149
+ } catch (err) {
150
+ try { process.stderr.write(`[session] pending-message drain failed sessionId=${sessionId}: ${err?.message || err}\n`); } catch {}
151
+ }
152
+ return drained;
153
+ }
154
+
155
+ function modelVisiblePendingMessages(messages) {
156
+ return (Array.isArray(messages) ? messages : [])
157
+ .map(pendingMessageQueueEntry)
158
+ .filter(Boolean)
159
+ .filter((message) => !isInternalRuntimeNotificationText(
160
+ message && typeof message === 'object' && Object.prototype.hasOwnProperty.call(message, 'content')
161
+ ? message.content
162
+ : message,
163
+ ));
164
+ }
165
+
166
+ export function _mergePendingMessageEntries(entries) {
167
+ const normalized = (Array.isArray(entries) ? entries : [])
168
+ .map(normalizePendingMessageEntry)
169
+ .filter(Boolean);
170
+ if (normalized.length === 0) return null;
171
+ const displayText = normalized.map((entry) => entry.text || promptContentText(entry.content))
172
+ .filter((text) => String(text || '').trim())
173
+ .join('\n');
174
+ if (normalized.every((entry) => typeof entry.content === 'string')) {
175
+ return {
176
+ content: normalized.map((entry) => entry.content).filter(Boolean).join('\n'),
177
+ text: displayText,
178
+ count: normalized.length,
179
+ };
180
+ }
181
+ const parts = [];
182
+ for (const entry of normalized) {
183
+ if (typeof entry.content === 'string') {
184
+ if (entry.content.trim()) parts.push({ type: 'text', text: entry.content });
185
+ } else if (Array.isArray(entry.content)) {
186
+ parts.push(...entry.content);
187
+ } else {
188
+ const text = promptContentText(entry.content);
189
+ if (text.trim()) parts.push({ type: 'text', text });
190
+ }
191
+ parts.push({ type: 'text', text: '\n' });
192
+ }
193
+ while (parts.length && parts[parts.length - 1]?.type === 'text' && parts[parts.length - 1]?.text === '\n') parts.pop();
194
+ return { content: parts, text: displayText || promptContentText(parts), count: normalized.length };
195
+ }
196
+
197
+ export function enqueuePendingMessage(sessionId, message) {
198
+ const entry = pendingMessageQueueEntry(message);
199
+ if (!sessionId || !entry) return 0;
200
+ let q = _sessionPendingMessages.get(sessionId);
201
+ if (!q) { q = []; _sessionPendingMessages.set(sessionId, q); }
202
+ q.push(entry);
203
+ const bufferedDepth = schedulePendingMessagePersist(sessionId, entry);
204
+ return Math.max(q.length, bufferedDepth || 0);
205
+ }
206
+
207
+ export function drainPendingMessages(sessionId) {
208
+ const q = _sessionPendingMessages.get(sessionId);
209
+ const memory = q && q.length > 0 ? q.slice() : [];
210
+ _sessionPendingMessages.delete(sessionId);
211
+ const persisted = [...takeBufferedPendingMessages(sessionId), ...drainPersistedPendingMessages(sessionId)];
212
+ const memoryVisible = modelVisiblePendingMessages(memory);
213
+ const persistedVisible = modelVisiblePendingMessages(persisted);
214
+ if (memoryVisible.length === 0) return persistedVisible;
215
+ if (persistedVisible.length === 0) return memoryVisible;
216
+ const persistedTexts = persistedVisible.map(pendingMessageText);
217
+ const prefixMatches = memoryVisible.every((m, i) => persistedTexts[i] === pendingMessageText(m));
218
+ if (prefixMatches) return [...memoryVisible, ...persistedVisible.slice(memoryVisible.length)];
219
+ const out = persistedVisible.slice();
220
+ const seen = new Set(persistedTexts);
221
+ for (const m of memoryVisible) {
222
+ const text = pendingMessageText(m);
223
+ if (!text || seen.has(text)) continue;
224
+ out.push(m);
225
+ seen.add(text);
226
+ }
227
+ return out;
228
+ }
229
+
230
+ // Cleanup hook for closeSession — drop the in-memory queue and buffered-persist
231
+ // entry so both Maps do not accumulate one entry per closed session.
232
+ export function _dropPendingMessageState(id) {
233
+ try { _sessionPendingMessages.delete(id); } catch { /* ignore */ }
234
+ try { _pendingPersistBuffers.delete(id); } catch { /* ignore */ }
235
+ }
@@ -0,0 +1,137 @@
1
+ // Prompt content + temporal helpers, extracted verbatim from manager.mjs
2
+ // (behavior-preserving). Pure string/date utilities with no session state.
3
+ import { isInternalRuntimeNotificationText as contractIsInternalRuntimeNotificationText } from '../../../../shared/tool-execution-contract.mjs';
4
+
5
+ export function promptContentText(content) {
6
+ if (typeof content === 'string') return content;
7
+ if (Array.isArray(content)) {
8
+ return content.map((part) => {
9
+ if (typeof part === 'string') return part;
10
+ if (part?.type === 'text') return part.text || '';
11
+ if (part?.type === 'image') return '[Image]';
12
+ return part?.text || '';
13
+ }).filter(Boolean).join('\n');
14
+ }
15
+ return String(content ?? '');
16
+ }
17
+
18
+ export function hasModelVisiblePromptContent(prompt) {
19
+ return !!promptContentText(prompt).trim();
20
+ }
21
+
22
+ export function promptContentBytes(content) {
23
+ try {
24
+ if (typeof content === 'string') return Buffer.byteLength(content, 'utf8');
25
+ return Buffer.byteLength(JSON.stringify(content), 'utf8');
26
+ } catch {
27
+ return Buffer.byteLength(promptContentText(content), 'utf8');
28
+ }
29
+ }
30
+
31
+ export function prefixUserTurnContent(content, contextBlock) {
32
+ if (!contextBlock) return content;
33
+ if (Array.isArray(content)) {
34
+ return [{ type: 'text', text: `${contextBlock}# Task\n` }, ...content];
35
+ }
36
+ return `${contextBlock}# Task\n${content}`;
37
+ }
38
+
39
+ export function prefixSessionStartContent(content, sessionBlock) {
40
+ if (!sessionBlock) return content;
41
+ if (Array.isArray(content)) {
42
+ return [{ type: 'text', text: `${sessionBlock}\n\n` }, ...content];
43
+ }
44
+ return `${sessionBlock}\n\n${content}`;
45
+ }
46
+
47
+ function localIsoDate(date = new Date()) {
48
+ const year = date.getFullYear();
49
+ const month = String(date.getMonth() + 1).padStart(2, '0');
50
+ const day = String(date.getDate()).padStart(2, '0');
51
+ return `${year}-${month}-${day}`;
52
+ }
53
+
54
+ function localDateTimeWithZone(date = new Date()) {
55
+ const datePart = localIsoDate(date);
56
+ const hh = String(date.getHours()).padStart(2, '0');
57
+ const mm = String(date.getMinutes()).padStart(2, '0');
58
+ const ss = String(date.getSeconds()).padStart(2, '0');
59
+ let zone = '';
60
+ try { zone = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch {}
61
+ return zone ? `${datePart} ${hh}:${mm}:${ss} ${zone}` : `${datePart} ${hh}:${mm}:${ss}`;
62
+ }
63
+
64
+ function temporalPromptText(content) {
65
+ const text = promptContentText(content)
66
+ .replace(/\s+/g, ' ')
67
+ .trim()
68
+ .toLowerCase();
69
+ return text;
70
+ }
71
+
72
+ function promptNeedsDateReminder(content) {
73
+ const text = temporalPromptText(content);
74
+ if (!text) return false;
75
+ return /(?:오늘|내일|어제|모레|그저께|요즘|최근|방금|아까|현재\s*(?:날짜|시간|시각)|지금\s*(?:몇\s*시|시간|날짜|요일)|몇\s*월\s*몇\s*일|몇\s*시|무슨\s*요일|요일|날짜|이번\s*(?:주|달|월|년)|지난\s*(?:주|달|월|년)|다음\s*(?:주|달|월|년)|올해|작년|내년|today|tomorrow|yesterday|recently|current\s+(?:date|time)|what\s+(?:date|time)|which\s+day|weekday|this\s+(?:week|month|year)|last\s+(?:week|month|year)|next\s+(?:week|month|year))/i.test(text);
76
+ }
77
+
78
+ function promptNeedsTimeReminder(content) {
79
+ const text = temporalPromptText(content);
80
+ if (!text) return false;
81
+ return /(?:현재\s*(?:시간|시각)|지금\s*(?:몇\s*시|시간)|몇\s*시|시각|시간|current\s+time|what\s+time|time\s+is\s+it)/i.test(text);
82
+ }
83
+
84
+ export function buildCurrentTimeBlock(content) {
85
+ const needsTime = promptNeedsTimeReminder(content);
86
+ if (!needsTime && !promptNeedsDateReminder(content)) return '';
87
+ return localDateTimeWithZone(new Date());
88
+ }
89
+
90
+ function sessionModelDisplay(model) {
91
+ const text = String(model || '').trim();
92
+ if (!text) return '';
93
+ return text
94
+ .replace(/-\d{4}-\d{2}-\d{2}$/, '')
95
+ .replace(/^gpt-/i, 'GPT-')
96
+ .replace(/(?:^|-)([a-z])/g, (m) => m.toUpperCase());
97
+ }
98
+
99
+ export function buildSessionStartBlock(session, cwd) {
100
+ if (!session || session.owner === 'agent') return '';
101
+ const lines = ['# Session'];
102
+ const effectiveCwd = String(cwd || session.cwd || '').trim();
103
+ if (effectiveCwd) lines.push(`Cwd: ${effectiveCwd}`);
104
+ const modelBits = [
105
+ sessionModelDisplay(session.model),
106
+ session.effort ? String(session.effort).trim().toUpperCase() : '',
107
+ session.fast === true ? 'FAST' : '',
108
+ ].filter(Boolean);
109
+ if (modelBits.length) lines.push(`Model: ${modelBits.join(' · ')}`);
110
+ const workflowName = String(session.workflow?.name || session.workflow?.id || '').trim();
111
+ if (workflowName) lines.push(`Workflow: ${workflowName}`);
112
+ return lines.length > 1 ? lines.join('\n') : '';
113
+ }
114
+
115
+ export function isReferenceFilesMessage(message) {
116
+ return message?.role === 'user'
117
+ && typeof message.content === 'string'
118
+ && /^Reference files:\s*/i.test(message.content.trimStart());
119
+ }
120
+
121
+ export function isProtectedContextUserMessage(message) {
122
+ return message?.role === 'user'
123
+ && typeof message.content === 'string'
124
+ && message.content.trimStart().startsWith('<system-reminder>');
125
+ }
126
+
127
+ export function hasUserConversationMessage(messages) {
128
+ return (Array.isArray(messages) ? messages : []).some((message) => (
129
+ message?.role === 'user'
130
+ && !isProtectedContextUserMessage(message)
131
+ && !isReferenceFilesMessage(message)
132
+ ));
133
+ }
134
+
135
+ export function isInternalRuntimeNotificationText(content) {
136
+ return contractIsInternalRuntimeNotificationText(promptContentText(content));
137
+ }