@jsonstudio/llms 0.6.2172 → 0.6.2979

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 (362) hide show
  1. package/README.md +2 -0
  2. package/dist/conversion/compat/antigravity-session-signature.js +35 -1
  3. package/dist/conversion/compat/profiles/chat-gemini-cli.json +0 -7
  4. package/dist/conversion/compat/profiles/chat-gemini.json +0 -6
  5. package/dist/conversion/hub/core/detour-registry.d.ts +2 -0
  6. package/dist/conversion/hub/core/hub-context.d.ts +3 -1
  7. package/dist/conversion/hub/core/index.d.ts +1 -0
  8. package/dist/conversion/hub/core/stage-driver.js +2 -0
  9. package/dist/conversion/hub/format-adapters/anthropic-format-adapter.js +15 -4
  10. package/dist/conversion/hub/format-adapters/chat-format-adapter.js +15 -4
  11. package/dist/conversion/hub/format-adapters/gemini-format-adapter.js +15 -4
  12. package/dist/conversion/hub/format-adapters/responses-format-adapter.js +15 -4
  13. package/dist/conversion/hub/hub-feature.js +3 -2
  14. package/dist/conversion/hub/node-support.js +9 -4
  15. package/dist/conversion/hub/operation-table/operation-table-runner.js +10 -6
  16. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +2 -2
  17. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +10 -10
  18. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +4 -8
  19. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +19 -65
  20. package/dist/conversion/hub/ops/operations.js +2 -121
  21. package/dist/conversion/hub/pipeline/compat/compat-engine.js +6 -0
  22. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.d.ts +1 -1
  23. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +33 -1042
  24. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +2 -0
  25. package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +2 -0
  26. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +14 -17
  27. package/dist/conversion/hub/pipeline/compat/native-adapter-context.d.ts +3 -0
  28. package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +39 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline.js +115 -262
  30. package/dist/conversion/hub/pipeline/session-identifiers.js +6 -196
  31. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +1 -2
  32. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +37 -1
  33. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +12 -86
  34. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/semantic-lift.d.ts +14 -0
  35. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/semantic-lift.js +24 -0
  36. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/archive/shell-like-tool-call-normalization-fallback.d.ts +2 -0
  37. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/archive/shell-like-tool-call-normalization-fallback.js +157 -0
  38. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.d.ts +16 -0
  39. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.js +29 -0
  40. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +3 -1
  41. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +2 -15
  42. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +8 -595
  43. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.d.ts +8 -0
  44. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.js +28 -0
  45. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-diagnostics.d.ts +2 -0
  46. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-diagnostics.js +4 -0
  47. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-snapshot.d.ts +10 -0
  48. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-snapshot.js +12 -0
  49. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.d.ts +3 -0
  50. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.js +30 -0
  51. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +9 -129
  52. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -4
  53. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +9 -26
  54. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +32 -14
  55. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +2 -2
  56. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +48 -8
  57. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -3
  58. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +10 -198
  59. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/stream-json-sniffer.d.ts +3 -0
  60. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/stream-json-sniffer.js +81 -0
  61. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +1 -2
  62. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +36 -1
  63. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +3 -1
  64. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/chat-process-semantics-bridge.d.ts +6 -0
  65. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/chat-process-semantics-bridge.js +17 -0
  66. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.d.ts +9 -0
  67. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +28 -0
  68. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -2
  69. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +14 -102
  70. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +3 -1
  71. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +12 -10
  72. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +6 -5
  73. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +11 -1
  74. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +10 -32
  75. package/dist/conversion/hub/pipeline/stages/utils.js +17 -1
  76. package/dist/conversion/hub/pipeline/target-utils.js +14 -91
  77. package/dist/conversion/hub/pipelines/inbound.js +3 -1
  78. package/dist/conversion/hub/pipelines/outbound.js +2 -0
  79. package/dist/conversion/hub/policy/policy-engine.js +9 -3
  80. package/dist/conversion/hub/policy/protocol-spec.js +20 -148
  81. package/dist/conversion/hub/process/chat-process-anthropic-alias.d.ts +2 -0
  82. package/dist/conversion/hub/process/chat-process-anthropic-alias.js +36 -0
  83. package/dist/conversion/hub/process/chat-process-clock-directive-parser.d.ts +5 -0
  84. package/dist/conversion/hub/process/chat-process-clock-directive-parser.js +48 -0
  85. package/dist/conversion/hub/process/chat-process-clock-directives.d.ts +24 -0
  86. package/dist/conversion/hub/process/chat-process-clock-directives.js +98 -0
  87. package/dist/conversion/hub/process/chat-process-clock-reminder-directives.d.ts +8 -0
  88. package/dist/conversion/hub/process/chat-process-clock-reminder-directives.js +42 -0
  89. package/dist/conversion/hub/process/chat-process-clock-reminder-finalize.d.ts +14 -0
  90. package/dist/conversion/hub/process/chat-process-clock-reminder-finalize.js +10 -0
  91. package/dist/conversion/hub/process/chat-process-clock-reminder-messages.d.ts +5 -0
  92. package/dist/conversion/hub/process/chat-process-clock-reminder-messages.js +10 -0
  93. package/dist/conversion/hub/process/chat-process-clock-reminder-orchestration.d.ts +30 -0
  94. package/dist/conversion/hub/process/chat-process-clock-reminder-orchestration.js +68 -0
  95. package/dist/conversion/hub/process/chat-process-clock-reminder-time-tag.d.ts +9 -0
  96. package/dist/conversion/hub/process/chat-process-clock-reminder-time-tag.js +18 -0
  97. package/dist/conversion/hub/process/chat-process-clock-reminders.d.ts +2 -0
  98. package/dist/conversion/hub/process/chat-process-clock-reminders.js +104 -0
  99. package/dist/conversion/hub/process/chat-process-clock-tool-schemas.d.ts +3 -0
  100. package/dist/conversion/hub/process/chat-process-clock-tool-schemas.js +233 -0
  101. package/dist/conversion/hub/process/chat-process-clock-tools.d.ts +6 -0
  102. package/dist/conversion/hub/process/chat-process-clock-tools.js +41 -0
  103. package/dist/conversion/hub/process/chat-process-continue-execution.d.ts +11 -0
  104. package/dist/conversion/hub/process/chat-process-continue-execution.js +82 -0
  105. package/dist/conversion/hub/process/chat-process-governance-context.d.ts +15 -0
  106. package/dist/conversion/hub/process/chat-process-governance-context.js +7 -0
  107. package/dist/conversion/hub/process/chat-process-governance-finalize.d.ts +16 -0
  108. package/dist/conversion/hub/process/chat-process-governance-finalize.js +11 -0
  109. package/dist/conversion/hub/process/chat-process-governance-orchestration.d.ts +9 -0
  110. package/dist/conversion/hub/process/chat-process-governance-orchestration.js +47 -0
  111. package/dist/conversion/hub/process/chat-process-governed-control-ops.d.ts +8 -0
  112. package/dist/conversion/hub/process/chat-process-governed-control-ops.js +5 -0
  113. package/dist/conversion/hub/process/chat-process-governed-filter-call.d.ts +12 -0
  114. package/dist/conversion/hub/process/chat-process-governed-filter-call.js +18 -0
  115. package/dist/conversion/hub/process/chat-process-governed-merge.d.ts +8 -0
  116. package/dist/conversion/hub/process/chat-process-governed-merge.js +11 -0
  117. package/dist/conversion/hub/process/chat-process-media.d.ts +3 -0
  118. package/dist/conversion/hub/process/chat-process-media.js +18 -0
  119. package/dist/conversion/hub/process/chat-process-node-result.d.ts +23 -0
  120. package/dist/conversion/hub/process/chat-process-node-result.js +24 -0
  121. package/dist/conversion/hub/process/chat-process-pending-tool-sync.d.ts +14 -0
  122. package/dist/conversion/hub/process/chat-process-pending-tool-sync.js +52 -0
  123. package/dist/conversion/hub/process/chat-process-post-governed-normalization.d.ts +8 -0
  124. package/dist/conversion/hub/process/chat-process-post-governed-normalization.js +16 -0
  125. package/dist/conversion/hub/process/chat-process-review.d.ts +2 -0
  126. package/dist/conversion/hub/process/chat-process-review.js +8 -0
  127. package/dist/conversion/hub/process/chat-process-servertool-orchestration.d.ts +8 -0
  128. package/dist/conversion/hub/process/chat-process-servertool-orchestration.js +22 -0
  129. package/dist/conversion/hub/process/chat-process-tool-normalization.d.ts +2 -0
  130. package/dist/conversion/hub/process/chat-process-tool-normalization.js +4 -0
  131. package/dist/conversion/hub/process/chat-process-web-search-intent.d.ts +12 -0
  132. package/dist/conversion/hub/process/chat-process-web-search-intent.js +13 -0
  133. package/dist/conversion/hub/process/chat-process-web-search-tool-schema.d.ts +3 -0
  134. package/dist/conversion/hub/process/chat-process-web-search-tool-schema.js +4 -0
  135. package/dist/conversion/hub/process/chat-process-web-search.d.ts +8 -0
  136. package/dist/conversion/hub/process/chat-process-web-search.js +26 -0
  137. package/dist/conversion/hub/process/chat-process.d.ts +2 -19
  138. package/dist/conversion/hub/process/chat-process.js +12 -1701
  139. package/dist/conversion/hub/process/client-inject-readiness.d.ts +1 -0
  140. package/dist/conversion/hub/process/client-inject-readiness.js +4 -0
  141. package/dist/conversion/hub/registry.js +5 -2
  142. package/dist/conversion/hub/response/chat-response-utils.js +5 -86
  143. package/dist/conversion/hub/response/provider-response.d.ts +9 -0
  144. package/dist/conversion/hub/response/provider-response.js +6 -21
  145. package/dist/conversion/hub/response/response-mappers.js +2 -26
  146. package/dist/conversion/hub/response/response-runtime.js +2 -93
  147. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -0
  148. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -0
  149. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -0
  150. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -0
  151. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -0
  152. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -0
  153. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -0
  154. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -0
  155. package/dist/conversion/hub/snapshot-recorder.js +10 -3
  156. package/dist/conversion/hub/standardized-bridge.js +11 -288
  157. package/dist/conversion/hub/tool-governance/engine.js +5 -0
  158. package/dist/conversion/hub/tool-governance/rules.js +10 -10
  159. package/dist/conversion/hub/tool-session-compat.d.ts +2 -2
  160. package/dist/conversion/hub/tool-session-compat.js +17 -231
  161. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +5 -3
  162. package/dist/conversion/responses/responses-host-policy.js +2 -12
  163. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +6 -82
  164. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -0
  165. package/dist/conversion/responses/responses-openai-bridge.js +21 -54
  166. package/dist/conversion/shared/anthropic-message-utils.js +151 -13
  167. package/dist/conversion/shared/args-mapping.js +2 -146
  168. package/dist/conversion/shared/bridge-actions.js +203 -718
  169. package/dist/conversion/shared/bridge-id-utils.js +5 -71
  170. package/dist/conversion/shared/bridge-instructions.js +2 -1
  171. package/dist/conversion/shared/bridge-message-types.d.ts +2 -0
  172. package/dist/conversion/shared/bridge-message-utils.js +1 -2
  173. package/dist/conversion/shared/bridge-metadata.d.ts +1 -0
  174. package/dist/conversion/shared/bridge-metadata.js +4 -0
  175. package/dist/conversion/shared/bridge-policies.js +5 -189
  176. package/dist/conversion/shared/chat-envelope-validator.js +2 -126
  177. package/dist/conversion/shared/chat-output-normalizer.js +2 -54
  178. package/dist/conversion/shared/compaction-detect.js +2 -57
  179. package/dist/conversion/shared/gemini-tool-utils.js +9 -524
  180. package/dist/conversion/shared/jsonish.js +3 -160
  181. package/dist/conversion/shared/mcp-injection.js +3 -169
  182. package/dist/conversion/shared/media.js +2 -7
  183. package/dist/conversion/shared/metadata-passthrough.js +9 -46
  184. package/dist/conversion/shared/openai-finalizer.js +2 -1
  185. package/dist/conversion/shared/openai-message-normalize.js +11 -283
  186. package/dist/conversion/shared/output-content-normalizer.js +9 -112
  187. package/dist/conversion/shared/payload-budget.js +2 -85
  188. package/dist/conversion/shared/protocol-state.js +11 -7
  189. package/dist/conversion/shared/reasoning-mapping.js +2 -6
  190. package/dist/conversion/shared/reasoning-normalizer.js +4 -1
  191. package/dist/conversion/shared/reasoning-tool-normalizer.js +14 -126
  192. package/dist/conversion/shared/reasoning-tool-parser.js +4 -87
  193. package/dist/conversion/shared/reasoning-utils.js +2 -6
  194. package/dist/conversion/shared/responses-conversation-store.js +4 -82
  195. package/dist/conversion/shared/responses-output-builder.js +11 -47
  196. package/dist/conversion/shared/responses-reasoning-registry.js +7 -1
  197. package/dist/conversion/shared/responses-request-adapter.d.ts +7 -1
  198. package/dist/conversion/shared/responses-request-adapter.js +14 -1
  199. package/dist/conversion/shared/responses-response-utils.js +6 -7
  200. package/dist/conversion/shared/responses-tool-utils.d.ts +1 -0
  201. package/dist/conversion/shared/responses-tool-utils.js +90 -14
  202. package/dist/conversion/shared/runtime-metadata.js +13 -5
  203. package/dist/conversion/shared/streaming-text-extractor.js +2 -7
  204. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
  205. package/dist/conversion/shared/text-markup-normalizer/normalize.js +43 -17
  206. package/dist/conversion/shared/text-markup-normalizer.d.ts +1 -0
  207. package/dist/conversion/shared/text-markup-normalizer.js +1 -0
  208. package/dist/conversion/shared/thought-signature-validator.js +3 -2
  209. package/dist/conversion/shared/tool-argument-repairer.js +2 -2
  210. package/dist/conversion/shared/tool-call-id-manager.js +5 -7
  211. package/dist/conversion/shared/tool-call-utils.js +3 -45
  212. package/dist/conversion/shared/tool-canonicalizer.js +25 -29
  213. package/dist/conversion/shared/tool-filter-pipeline.js +4 -99
  214. package/dist/conversion/shared/tool-governor.d.ts +6 -0
  215. package/dist/conversion/shared/tool-governor.js +43 -125
  216. package/dist/conversion/shared/tool-harvester.js +2 -8
  217. package/dist/conversion/shared/tool-mapping.js +2 -5
  218. package/dist/conversion/shared/tooling.d.ts +0 -4
  219. package/dist/conversion/shared/tooling.js +18 -0
  220. package/dist/native/router_hotpath_napi.node +0 -0
  221. package/dist/router/virtual-router/engine/provider-key/parse.d.ts +1 -6
  222. package/dist/router/virtual-router/engine/provider-key/parse.js +1 -43
  223. package/dist/router/virtual-router/engine/routing-state/store.js +48 -12
  224. package/dist/router/virtual-router/engine-logging.js +4 -3
  225. package/dist/router/virtual-router/engine-selection/alias-selection.js +45 -83
  226. package/dist/router/virtual-router/engine-selection/key-parsing.js +9 -23
  227. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-directive-parser.d.ts +20 -0
  228. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-directive-parser.js +163 -0
  229. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-directives.d.ts +7 -0
  230. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-directives.js +103 -0
  231. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-orchestration-semantics.d.ts +10 -0
  232. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-orchestration-semantics.js +110 -0
  233. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-semantics.d.ts +8 -0
  234. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-semantics.js +281 -0
  235. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-time-tag-semantics.d.ts +1 -0
  236. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-time-tag-semantics.js +25 -0
  237. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminders-semantics.d.ts +4 -0
  238. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminders-semantics.js +44 -0
  239. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-tool-schema-semantics.d.ts +2 -0
  240. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-tool-schema-semantics.js +62 -0
  241. package/dist/router/virtual-router/engine-selection/native-chat-process-governance-semantics.d.ts +40 -0
  242. package/dist/router/virtual-router/engine-selection/native-chat-process-governance-semantics.js +484 -0
  243. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.d.ts +9 -0
  244. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.js +64 -0
  245. package/dist/router/virtual-router/engine-selection/native-chat-process-node-result-semantics.d.ts +5 -0
  246. package/dist/router/virtual-router/engine-selection/native-chat-process-node-result-semantics.js +163 -0
  247. package/dist/router/virtual-router/engine-selection/native-chat-process-post-governed-normalization-semantics.d.ts +1 -0
  248. package/dist/router/virtual-router/engine-selection/native-chat-process-post-governed-normalization-semantics.js +49 -0
  249. package/dist/router/virtual-router/engine-selection/native-chat-process-servertool-orchestration-semantics.d.ts +30 -0
  250. package/dist/router/virtual-router/engine-selection/native-chat-process-servertool-orchestration-semantics.js +446 -0
  251. package/dist/router/virtual-router/engine-selection/native-chat-process-web-search-intent-semantics.d.ts +1 -0
  252. package/dist/router/virtual-router/engine-selection/native-chat-process-web-search-intent-semantics.js +49 -0
  253. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +134 -0
  254. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +729 -0
  255. package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.d.ts +62 -0
  256. package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.js +338 -0
  257. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +18 -0
  258. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +317 -0
  259. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.d.ts +22 -0
  260. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.js +426 -0
  261. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-orchestration-semantics.d.ts +57 -0
  262. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-orchestration-semantics.js +705 -0
  263. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +46 -0
  264. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +503 -0
  265. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +146 -0
  266. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js +570 -0
  267. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +25 -0
  268. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.js +148 -0
  269. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +25 -0
  270. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +637 -0
  271. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-session-identifiers-semantics.d.ts +11 -0
  272. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-session-identifiers-semantics.js +207 -0
  273. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-target-semantics.d.ts +3 -0
  274. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-target-semantics.js +128 -0
  275. package/dist/router/virtual-router/engine-selection/native-router-hotpath-analysis.d.ts +57 -0
  276. package/dist/router/virtual-router/engine-selection/native-router-hotpath-analysis.js +217 -0
  277. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.d.ts +5 -0
  278. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +284 -0
  279. package/dist/router/virtual-router/engine-selection/native-router-hotpath-policy.d.ts +5 -0
  280. package/dist/router/virtual-router/engine-selection/native-router-hotpath-policy.js +18 -0
  281. package/dist/router/virtual-router/engine-selection/native-router-hotpath-quota-buckets.d.ts +25 -0
  282. package/dist/router/virtual-router/engine-selection/native-router-hotpath-quota-buckets.js +85 -0
  283. package/dist/router/virtual-router/engine-selection/native-router-hotpath.d.ts +59 -0
  284. package/dist/router/virtual-router/engine-selection/native-router-hotpath.js +117 -0
  285. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +76 -0
  286. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +1166 -0
  287. package/dist/router/virtual-router/engine-selection/native-virtual-router-alias-selection-semantics.d.ts +16 -0
  288. package/dist/router/virtual-router/engine-selection/native-virtual-router-alias-selection-semantics.js +96 -0
  289. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-actions-semantics.d.ts +6 -0
  290. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-actions-semantics.js +85 -0
  291. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-semantics.d.ts +9 -0
  292. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-semantics.js +70 -0
  293. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-state-semantics.d.ts +2 -0
  294. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-state-semantics.js +76 -0
  295. package/dist/router/virtual-router/engine-selection/route-utils.js +1 -1
  296. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-session-lease.d.ts +10 -0
  297. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-session-lease.js +231 -0
  298. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-target-split.d.ts +4 -0
  299. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-target-split.js +43 -0
  300. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.d.ts +27 -0
  301. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +116 -0
  302. package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +1 -1
  303. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +29 -129
  304. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -265
  305. package/dist/router/virtual-router/engine.js +258 -249
  306. package/dist/router/virtual-router/features.js +2 -2
  307. package/dist/router/virtual-router/routing-instructions.d.ts +5 -7
  308. package/dist/router/virtual-router/routing-instructions.js +93 -66
  309. package/dist/router/virtual-router/routing-stop-message-actions.js +91 -112
  310. package/dist/router/virtual-router/routing-stop-message-parser.js +9 -132
  311. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +1 -0
  312. package/dist/router/virtual-router/routing-stop-message-state-codec.js +58 -71
  313. package/dist/router/virtual-router/sticky-session-store.js +4 -2
  314. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  315. package/dist/router/virtual-router/stop-message-file-resolver.js +10 -0
  316. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  317. package/dist/router/virtual-router/stop-message-state-sync.js +3 -7
  318. package/dist/router/virtual-router/token-counter.js +0 -9
  319. package/dist/router/virtual-router/types.d.ts +9 -7
  320. package/dist/servertool/clock/config.js +23 -51
  321. package/dist/servertool/clock/io.js +1 -0
  322. package/dist/servertool/clock/session-scope.d.ts +2 -2
  323. package/dist/servertool/clock/session-scope.js +5 -47
  324. package/dist/servertool/engine.d.ts +9 -0
  325. package/dist/servertool/engine.js +196 -79
  326. package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +2 -2
  327. package/dist/servertool/handlers/clock.js +1 -1
  328. package/dist/servertool/handlers/continue-execution.js +8 -4
  329. package/dist/servertool/handlers/followup-request-builder.js +18 -1
  330. package/dist/servertool/handlers/gemini-empty-reply-continue.js +7 -1
  331. package/dist/servertool/handlers/review.js +180 -0
  332. package/dist/servertool/handlers/stop-message-auto/blocked-report.js +59 -1
  333. package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -2
  334. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +397 -89
  335. package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +5 -15
  336. package/dist/servertool/handlers/stop-message-auto/routing-state.js +29 -55
  337. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +6 -0
  338. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +35 -61
  339. package/dist/servertool/handlers/stop-message-auto.js +392 -76
  340. package/dist/servertool/server-side-tools.d.ts +1 -0
  341. package/dist/servertool/server-side-tools.js +90 -52
  342. package/dist/servertool/types.d.ts +17 -0
  343. package/dist/tools/apply-patch/patch-text/normalize.js +11 -0
  344. package/dist/tools/exec-command/validator.d.ts +4 -1
  345. package/dist/tools/exec-command/validator.js +87 -3
  346. package/dist/tools/tool-registry.d.ts +7 -1
  347. package/dist/tools/tool-registry.js +3 -2
  348. package/package.json +115 -7
  349. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +0 -18
  350. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +0 -398
  351. package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +0 -9
  352. package/dist/servertool/handlers/stop-message-stage-policy/decision.js +0 -127
  353. package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +0 -2
  354. package/dist/servertool/handlers/stop-message-stage-policy/observation.js +0 -179
  355. package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +0 -4
  356. package/dist/servertool/handlers/stop-message-stage-policy/templates.js +0 -96
  357. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +0 -9
  358. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +0 -89
  359. package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +0 -59
  360. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +0 -3
  361. package/dist/servertool/handlers/stop-message-stage-policy.js +0 -2
  362. /package/dist/servertool/handlers/{stop-message-stage-policy/types.js → review.d.ts} +0 -0
@@ -16,13 +16,24 @@ import { hydrateAntigravityAliasLeaseStoreIfNeeded, recordAntigravitySessionLeas
16
16
  import { buildMetadataInstructions, resolveRoutingMode } from './engine/routing-state/metadata.js';
17
17
  import { getRoutingInstructionState, persistRoutingInstructionState, resolveStopMessageScope } from './engine/routing-state/store.js';
18
18
  import { ensureStopMessageModeMaxRepeats } from './routing-stop-message-state-codec.js';
19
- import { validateStopMessageStageTemplatesCompleteness } from './stop-message-stage-template-files.js';
20
19
  import { extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from './engine/provider-key/parse.js';
21
20
  import { resolveSessionScope as resolveSessionScopeImpl, resolveStickyKey as resolveStickyKeyImpl } from './engine/routing-state/keys.js';
22
21
  import { RouteAnalytics } from './engine/route-analytics.js';
23
22
  import { StickySessionManager } from './engine/sticky-session-manager.js';
24
23
  import { CooldownManager } from './engine/cooldown-manager.js';
25
24
  const DEFAULT_STOP_MESSAGE_MAX_REPEATS = 10;
25
+ function isStopScopeTraceEnabled() {
26
+ const raw = String(process.env.ROUTECODEX_STOP_SCOPE_TRACE ?? process.env.RCC_STOP_SCOPE_TRACE ?? '')
27
+ .trim()
28
+ .toLowerCase();
29
+ return (raw === '1' ||
30
+ raw === 'true' ||
31
+ raw === 'on' ||
32
+ raw === 'yes' ||
33
+ raw === 'debug' ||
34
+ raw === 'trace' ||
35
+ raw === 'verbose');
36
+ }
26
37
  function normalizeStopMessageStageMode(value) {
27
38
  if (typeof value !== 'string') {
28
39
  return undefined;
@@ -33,31 +44,52 @@ function normalizeStopMessageStageMode(value) {
33
44
  }
34
45
  return undefined;
35
46
  }
36
- function resolveStopMessageStageModeAfterInstructions(instructions, currentMode) {
37
- let mode = normalizeStopMessageStageMode(currentMode);
38
- for (const instruction of instructions) {
39
- if (instruction.type === 'stopMessageClear') {
40
- mode = undefined;
41
- continue;
42
- }
43
- if (instruction.type === 'stopMessageMode') {
44
- const incomingMode = normalizeStopMessageStageMode(instruction.stopMessageStageMode);
45
- if (incomingMode) {
46
- mode = incomingMode;
47
- }
48
- continue;
49
- }
50
- if (instruction.type === 'stopMessageSet') {
51
- const incomingMode = normalizeStopMessageStageMode(instruction.stopMessageStageMode);
52
- if (incomingMode) {
53
- mode = incomingMode;
54
- }
55
- else if (mode === 'off') {
56
- mode = 'on';
57
- }
58
- }
47
+ function normalizeStopMessageAiMode(value) {
48
+ if (typeof value !== 'string') {
49
+ return undefined;
50
+ }
51
+ const normalized = value.trim().toLowerCase();
52
+ if (normalized === 'on' || normalized === 'off') {
53
+ return normalized;
54
+ }
55
+ return undefined;
56
+ }
57
+ function stripStopMessageFields(state) {
58
+ return {
59
+ ...state,
60
+ stopMessageSource: undefined,
61
+ stopMessageText: undefined,
62
+ stopMessageMaxRepeats: undefined,
63
+ stopMessageUsed: undefined,
64
+ stopMessageUpdatedAt: undefined,
65
+ stopMessageLastUsedAt: undefined,
66
+ stopMessageStageMode: undefined,
67
+ stopMessageAiMode: undefined,
68
+ stopMessageAiSeedPrompt: undefined,
69
+ stopMessageAiHistory: undefined
70
+ };
71
+ }
72
+ function stripClientInjectScopedFields(state) {
73
+ return {
74
+ ...stripStopMessageFields(state),
75
+ preCommandSource: undefined,
76
+ preCommandScriptPath: undefined,
77
+ preCommandUpdatedAt: undefined
78
+ };
79
+ }
80
+ function hasClientInjectScopedFields(state) {
81
+ if (!state) {
82
+ return false;
59
83
  }
60
- return mode;
84
+ return (typeof state.stopMessageText === 'string' ||
85
+ typeof state.stopMessageMaxRepeats === 'number' ||
86
+ typeof state.stopMessageUsed === 'number' ||
87
+ typeof state.stopMessageStageMode === 'string' ||
88
+ typeof state.stopMessageAiMode === 'string' ||
89
+ typeof state.stopMessageAiSeedPrompt === 'string' ||
90
+ Array.isArray(state.stopMessageAiHistory) ||
91
+ typeof state.preCommandScriptPath === 'string' ||
92
+ typeof state.preCommandUpdatedAt === 'number');
61
93
  }
62
94
  function hasRoutingInstructionMarker(messages) {
63
95
  for (const message of messages) {
@@ -68,7 +100,7 @@ function hasRoutingInstructionMarker(messages) {
68
100
  if (!content) {
69
101
  continue;
70
102
  }
71
- if (/<\*\*[^*]+\*\*>/.test(content)) {
103
+ if (/<\*\*[\s\S]*?\*\*>/.test(content)) {
72
104
  return true;
73
105
  }
74
106
  }
@@ -87,7 +119,7 @@ function hasLatestUserRoutingInstructionMarker(messages) {
87
119
  if (!content) {
88
120
  return false;
89
121
  }
90
- return /<\*\*[^*]+\*\*>/.test(content);
122
+ return /<\*\*[\s\S]*?\*\*>/.test(content);
91
123
  }
92
124
  return false;
93
125
  }
@@ -256,11 +288,11 @@ export class VirtualRouterEngine {
256
288
  const stickyKey = this.resolveStickyKey(metadata);
257
289
  const sessionScope = this.resolveSessionScope(metadata);
258
290
  const stopMessageScope = resolveStopMessageScope(metadata);
259
- // Routing instructions should be session/conversation-scoped when available (including /v1/responses),
260
- // while auto-sticky for Responses remains request-chain scoped via resolveStickyKey().
291
+ // Route sticky state remains session/request scoped for routing behavior,
292
+ // but stopMessage state is tmux/clockd scoped and merged separately below.
261
293
  const stateKey = sessionScope || stickyKey || 'default';
262
294
  const baseState = getRoutingInstructionState(stateKey, this.routingInstructionState, this.routingStateStore);
263
- let routingState = baseState;
295
+ let routingState = stripStopMessageFields(baseState);
264
296
  const metadataInstructions = buildMetadataInstructions(metadata);
265
297
  if (metadataInstructions.length > 0) {
266
298
  routingState = applyRoutingInstructions(metadataInstructions, routingState);
@@ -283,7 +315,10 @@ export class VirtualRouterEngine {
283
315
  }
284
316
  if (typeof sessionState.stopMessageText === 'string' ||
285
317
  typeof sessionState.stopMessageMaxRepeats === 'number' ||
286
- typeof sessionState.stopMessageStageMode === 'string') {
318
+ typeof sessionState.stopMessageStageMode === 'string' ||
319
+ typeof sessionState.stopMessageAiMode === 'string' ||
320
+ typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
321
+ Array.isArray(sessionState.stopMessageAiHistory)) {
287
322
  routingState = {
288
323
  ...routingState,
289
324
  stopMessageText: sessionState.stopMessageText,
@@ -291,11 +326,10 @@ export class VirtualRouterEngine {
291
326
  stopMessageUsed: sessionState.stopMessageUsed,
292
327
  stopMessageUpdatedAt: sessionState.stopMessageUpdatedAt,
293
328
  stopMessageLastUsedAt: sessionState.stopMessageLastUsedAt,
294
- stopMessageStage: sessionState.stopMessageStage,
295
329
  stopMessageStageMode: sessionState.stopMessageStageMode,
296
- stopMessageObservationHash: sessionState.stopMessageObservationHash,
297
- stopMessageObservationStableCount: sessionState.stopMessageObservationStableCount,
298
- stopMessageBdWorkState: sessionState.stopMessageBdWorkState
330
+ stopMessageAiMode: sessionState.stopMessageAiMode,
331
+ stopMessageAiSeedPrompt: sessionState.stopMessageAiSeedPrompt,
332
+ stopMessageAiHistory: sessionState.stopMessageAiHistory
299
333
  };
300
334
  }
301
335
  if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
@@ -308,6 +342,21 @@ export class VirtualRouterEngine {
308
342
  }
309
343
  }
310
344
  const parsedInstructions = parseRoutingInstructions(request.messages);
345
+ const markerDetected = hasRoutingInstructionMarker(request.messages);
346
+ if (markerDetected && isStopScopeTraceEnabled()) {
347
+ const parsedTypes = parsedInstructions.map((entry) => entry.type);
348
+ this.debug?.log?.(`[virtual-router][instruction_parse] requestId=${metadata.requestId || 'n/a'} marker=detected parsed=${parsedTypes.join(',') || 'none'}`);
349
+ }
350
+ else if (isStopScopeTraceEnabled()) {
351
+ const latestUserText = [...request.messages]
352
+ .reverse()
353
+ .find((message) => message?.role === 'user');
354
+ const latestText = latestUserText ? extractMessageText(latestUserText).trim() : '';
355
+ if (latestText && /stopmessage/i.test(latestText)) {
356
+ const preview = latestText.replace(/\s+/g, ' ').slice(0, 120);
357
+ this.debug?.log?.(`[virtual-router][instruction_parse] requestId=${metadata.requestId || 'n/a'} marker=missing contains_stopmessage=yes preview=${preview}`);
358
+ }
359
+ }
311
360
  const serverToolFollowup = isServerToolFollowupRequest(metadata);
312
361
  const latestUserHasMarker = hasLatestUserRoutingInstructionMarker(request.messages);
313
362
  let instructions = parsedInstructions;
@@ -322,205 +371,166 @@ export class VirtualRouterEngine {
322
371
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
323
372
  const hasStaleStopMessageInstruction = !latestUserHasMarker &&
324
373
  parsedInstructions.some((entry) => entry.type === 'stopMessageSet' ||
325
- entry.type === 'stopMessageMode' ||
326
- entry.type === 'stopMessageClear');
374
+ entry.type === 'stopMessageMode');
327
375
  if (hasStaleStopMessageInstruction) {
328
376
  const hasActiveStopState = typeof sessionState.stopMessageText === 'string' ||
329
377
  typeof sessionState.stopMessageMaxRepeats === 'number' ||
330
- typeof sessionState.stopMessageStageMode === 'string';
378
+ typeof sessionState.stopMessageStageMode === 'string' ||
379
+ typeof sessionState.stopMessageAiMode === 'string' ||
380
+ typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
381
+ Array.isArray(sessionState.stopMessageAiHistory);
331
382
  const hasStopLifecycleStamp = (typeof sessionState.stopMessageUpdatedAt === 'number' && Number.isFinite(sessionState.stopMessageUpdatedAt)) ||
332
383
  (typeof sessionState.stopMessageLastUsedAt === 'number' && Number.isFinite(sessionState.stopMessageLastUsedAt));
333
384
  if (hasActiveStopState || hasStopLifecycleStamp) {
334
385
  instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
335
- entry.type !== 'stopMessageMode' &&
336
- entry.type !== 'stopMessageClear');
337
- }
338
- }
339
- const hasGlobalClear = instructions.some((entry) => entry.type === 'clear');
340
- const hasStopMessageClear = hasGlobalClear || instructions.some((entry) => entry.type === 'stopMessageClear');
341
- const stopMessageSets = instructions.filter((entry) => entry.type === 'stopMessageSet');
342
- if (!hasStopMessageClear && stopMessageSets.length > 0) {
343
- const sessionText = typeof sessionState.stopMessageText === 'string' ? sessionState.stopMessageText.trim() : '';
344
- const sessionMax = typeof sessionState.stopMessageMaxRepeats === 'number' && Number.isFinite(sessionState.stopMessageMaxRepeats)
345
- ? Math.floor(sessionState.stopMessageMaxRepeats)
346
- : undefined;
347
- const sessionMode = normalizeStopMessageStageMode(sessionState.stopMessageStageMode);
348
- const allSame = stopMessageSets.every((entry) => {
349
- const entryText = typeof entry.stopMessageText === 'string' ? entry.stopMessageText.trim() : '';
350
- const entryMax = typeof entry.stopMessageMaxRepeats === 'number' && Number.isFinite(entry.stopMessageMaxRepeats)
351
- ? Math.floor(entry.stopMessageMaxRepeats)
352
- : undefined;
353
- const incomingMode = normalizeStopMessageStageMode(entry.stopMessageStageMode);
354
- const entryMode = incomingMode ?? (sessionMode === 'off' ? 'on' : sessionMode);
355
- return Boolean(entryText) && entryText === sessionText && entryMax === sessionMax && entryMode === sessionMode;
356
- });
357
- const used = typeof sessionState.stopMessageUsed === 'number' && Number.isFinite(sessionState.stopMessageUsed)
358
- ? Math.max(0, Math.floor(sessionState.stopMessageUsed))
359
- : 0;
360
- const hasLastUsedAt = typeof sessionState.stopMessageLastUsedAt === 'number' &&
361
- Number.isFinite(sessionState.stopMessageLastUsedAt);
362
- const alreadyArmed = used === 0 && !hasLastUsedAt;
363
- if (allSame && alreadyArmed) {
364
- instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet');
386
+ entry.type !== 'stopMessageMode');
365
387
  }
366
388
  }
367
389
  }
368
- // stopMessage must be session-scoped: require explicit sessionId in metadata.
369
- // This prevents global/default persistence and ensures the trigger matches the setting sessionId.
370
- if (instructions.length > 0) {
371
- const hasSessionScopedInstruction = instructions.some((entry) => entry.type === 'stopMessageSet' ||
390
+ // stopMessage/precommand require explicit client inject scope (tmux).
391
+ // When scope is missing, drop only these instructions and keep request passthrough.
392
+ if (instructions.length > 0 && !stopMessageScope) {
393
+ const blockedInstructionTypes = instructions
394
+ .filter((entry) => entry.type === 'stopMessageSet' ||
372
395
  entry.type === 'stopMessageMode' ||
373
396
  entry.type === 'stopMessageClear' ||
374
397
  entry.type === 'preCommandSet' ||
375
- entry.type === 'preCommandClear');
376
- if (hasSessionScopedInstruction && !stopMessageScope) {
377
- throw new VirtualRouterError('[stopMessage/precommand] requires sessionId (e.g. set x-session-id header or metadata.sessionId).', VirtualRouterErrorCode.CONFIG_ERROR, { requestId: metadata.requestId, entryEndpoint: metadata.entryEndpoint });
378
- }
379
- }
380
- if (stopMessageScope && instructions.length > 0) {
381
- const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
382
- const nextStopMessageMode = resolveStopMessageStageModeAfterInstructions(instructions, sessionState.stopMessageStageMode);
383
- if (nextStopMessageMode === 'on') {
384
- const templateCheck = validateStopMessageStageTemplatesCompleteness();
385
- if (!templateCheck.ok) {
386
- const detail = templateCheck.missing
387
- .map((entry) => `${entry.stage}:${entry.ref || '(no-ref)'}:${entry.error || 'invalid'}`)
388
- .join('; ');
389
- throw new VirtualRouterError(`[stopMessage] mode=on requires complete stage message files (${detail}).`, VirtualRouterErrorCode.CONFIG_ERROR, { requestId: metadata.requestId, entryEndpoint: metadata.entryEndpoint });
390
- }
398
+ entry.type === 'preCommandClear')
399
+ .map((entry) => entry.type);
400
+ if (blockedInstructionTypes.length > 0 && isStopScopeTraceEnabled()) {
401
+ this.debug?.log?.(`[virtual-router][stop_scope] requestId=${metadata.requestId || 'n/a'} stage=drop reason=missing_tmux_scope instructions=${blockedInstructionTypes.join(',')}`);
391
402
  }
403
+ instructions = instructions.filter((entry) => entry.type !== 'stopMessageSet' &&
404
+ entry.type !== 'stopMessageMode' &&
405
+ entry.type !== 'stopMessageClear' &&
406
+ entry.type !== 'preCommandSet' &&
407
+ entry.type !== 'preCommandClear');
392
408
  }
393
409
  if (hasRoutingInstructionMarker(request.messages)) {
394
410
  request.messages = cleanMessagesFromRoutingInstructions(request.messages);
395
411
  }
412
+ let appliedRoutingState = routingState;
396
413
  if (instructions.length > 0) {
397
- routingState = applyRoutingInstructions(instructions, routingState);
398
- this.routingInstructionState.set(stateKey, routingState);
399
- persistRoutingInstructionState(stateKey, routingState, this.routingStateStore);
400
- // 对 stopMessage 指令补充一份基于 session/conversation 的持久化状态,
401
- // 便于 server-side 工具通过 session:*/conversation:* scope 读取到相同配置。
402
- // stopMessage is strictly session-scoped (sessionId only). Persist it under the session scope
403
- // so servertool triggers always match the setting sessionId.
414
+ appliedRoutingState = applyRoutingInstructions(instructions, routingState);
415
+ const persistedBaseState = stopMessageScope
416
+ ? stripClientInjectScopedFields(appliedRoutingState)
417
+ : appliedRoutingState;
418
+ routingState = persistedBaseState;
419
+ this.routingInstructionState.set(stateKey, persistedBaseState);
420
+ persistRoutingInstructionState(stateKey, persistedBaseState, this.routingStateStore);
421
+ // Persist stopMessage under tmux/clockd scope so client injection and trigger matching
422
+ // use the same scope and never fall back to generic session keys.
404
423
  if (stopMessageScope) {
405
424
  const hasStopMessageSet = instructions.some((entry) => entry.type === 'stopMessageSet');
406
425
  const hasStopMessageMode = instructions.some((entry) => entry.type === 'stopMessageMode');
407
426
  const hasGlobalClear = instructions.some((entry) => entry.type === 'clear');
408
427
  const hasStopMessageClear = hasGlobalClear || instructions.some((entry) => entry.type === 'stopMessageClear');
409
428
  if (hasStopMessageSet || hasStopMessageMode || hasStopMessageClear) {
429
+ const activeInstructionTypes = instructions
430
+ .filter((entry) => entry.type === 'stopMessageSet' ||
431
+ entry.type === 'stopMessageMode' ||
432
+ entry.type === 'stopMessageClear')
433
+ .map((entry) => entry.type);
434
+ if (isStopScopeTraceEnabled()) {
435
+ this.debug?.log?.(`[virtual-router][stop_scope] requestId=${metadata.requestId || 'n/a'} stage=apply scope=${stopMessageScope} instructions=${activeInstructionTypes.join(',') || 'none'}`);
436
+ }
410
437
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
411
438
  let nextSessionState = {
412
439
  ...sessionState
413
440
  };
414
441
  let shouldPersistSessionState = false;
442
+ const hasStopMessageStateChanged = () => {
443
+ return (nextSessionState.stopMessageText !== sessionState.stopMessageText ||
444
+ nextSessionState.stopMessageMaxRepeats !== sessionState.stopMessageMaxRepeats ||
445
+ nextSessionState.stopMessageUsed !== sessionState.stopMessageUsed ||
446
+ nextSessionState.stopMessageUpdatedAt !== sessionState.stopMessageUpdatedAt ||
447
+ nextSessionState.stopMessageLastUsedAt !== sessionState.stopMessageLastUsedAt ||
448
+ nextSessionState.stopMessageSource !== sessionState.stopMessageSource ||
449
+ nextSessionState.stopMessageStageMode !== sessionState.stopMessageStageMode ||
450
+ nextSessionState.stopMessageAiMode !== sessionState.stopMessageAiMode ||
451
+ nextSessionState.stopMessageAiSeedPrompt !== sessionState.stopMessageAiSeedPrompt ||
452
+ JSON.stringify(nextSessionState.stopMessageAiHistory || []) !==
453
+ JSON.stringify(sessionState.stopMessageAiHistory || []));
454
+ };
415
455
  if (hasStopMessageClear) {
416
- const clearedAt = Date.now();
417
- nextSessionState.stopMessageText = undefined;
418
- nextSessionState.stopMessageMaxRepeats = undefined;
419
- nextSessionState.stopMessageUsed = undefined;
420
- nextSessionState.stopMessageUpdatedAt = clearedAt;
421
- nextSessionState.stopMessageLastUsedAt = clearedAt;
422
- nextSessionState.stopMessageSource = undefined;
423
- nextSessionState.stopMessageStage = undefined;
424
- nextSessionState.stopMessageStageMode = undefined;
425
- nextSessionState.stopMessageObservationHash = undefined;
426
- nextSessionState.stopMessageObservationStableCount = undefined;
427
- nextSessionState.stopMessageBdWorkState = undefined;
428
- shouldPersistSessionState = true;
429
- }
430
- else if (hasStopMessageSet) {
431
- const text = typeof routingState.stopMessageText === 'string' ? routingState.stopMessageText : '';
432
- const maxRepeats = routingState.stopMessageMaxRepeats;
433
- const sameText = typeof sessionState.stopMessageText === 'string' &&
434
- sessionState.stopMessageText.trim() === text.trim();
435
- const sameMax = typeof sessionState.stopMessageMaxRepeats === 'number' &&
436
- typeof maxRepeats === 'number' &&
437
- Math.floor(sessionState.stopMessageMaxRepeats) === Math.floor(maxRepeats);
438
- const currentMode = normalizeStopMessageStageMode(sessionState.stopMessageStageMode);
439
- const nextMode = normalizeStopMessageStageMode(routingState.stopMessageStageMode) ?? currentMode;
440
- const isSameMode = currentMode === nextMode;
441
- const isSameInstruction = Boolean(text) && sameText && sameMax && isSameMode;
442
- const used = typeof sessionState.stopMessageUsed === 'number' && Number.isFinite(sessionState.stopMessageUsed)
443
- ? Math.max(0, Math.floor(sessionState.stopMessageUsed))
444
- : 0;
445
- const hasLastUsedAt = typeof sessionState.stopMessageLastUsedAt === 'number' &&
446
- Number.isFinite(sessionState.stopMessageLastUsedAt);
447
- const shouldRearm = !isSameInstruction || used > 0 || hasLastUsedAt;
448
- nextSessionState.stopMessageText = text || undefined;
449
- nextSessionState.stopMessageMaxRepeats = maxRepeats;
450
- nextSessionState.stopMessageSource = 'explicit';
451
- nextSessionState.stopMessageStageMode = nextMode;
452
- if (shouldRearm) {
453
- nextSessionState.stopMessageUsed = 0;
454
- nextSessionState.stopMessageUpdatedAt =
455
- typeof routingState.stopMessageUpdatedAt === 'number'
456
- ? routingState.stopMessageUpdatedAt
457
- : Date.now();
458
- nextSessionState.stopMessageStage = undefined;
459
- nextSessionState.stopMessageObservationHash = undefined;
460
- nextSessionState.stopMessageObservationStableCount = 0;
461
- nextSessionState.stopMessageBdWorkState = undefined;
462
- nextSessionState.stopMessageLastUsedAt = undefined;
456
+ if (hasGlobalClear) {
457
+ // <**clear**> is a hard reset: clear all session-scoped routing state
458
+ // and let persistRoutingInstructionState delete persistence markers.
459
+ nextSessionState = {
460
+ forcedTarget: undefined,
461
+ stickyTarget: undefined,
462
+ preferTarget: undefined,
463
+ allowedProviders: new Set(),
464
+ disabledProviders: new Set(),
465
+ disabledKeys: new Map(),
466
+ disabledModels: new Map(),
467
+ stopMessageSource: undefined,
468
+ stopMessageText: undefined,
469
+ stopMessageMaxRepeats: undefined,
470
+ stopMessageUsed: undefined,
471
+ stopMessageUpdatedAt: undefined,
472
+ stopMessageLastUsedAt: undefined,
473
+ stopMessageStageMode: undefined,
474
+ stopMessageAiMode: undefined,
475
+ stopMessageAiSeedPrompt: undefined,
476
+ stopMessageAiHistory: undefined,
477
+ preCommandSource: undefined,
478
+ preCommandScriptPath: undefined,
479
+ preCommandUpdatedAt: undefined
480
+ };
463
481
  shouldPersistSessionState = true;
464
482
  }
465
- }
466
- else if (hasStopMessageMode) {
467
- const mode = normalizeStopMessageStageMode(routingState.stopMessageStageMode);
468
- const modeMaxRepeats = typeof routingState.stopMessageMaxRepeats === 'number' && Number.isFinite(routingState.stopMessageMaxRepeats)
469
- ? Math.floor(routingState.stopMessageMaxRepeats)
470
- : 0;
471
- if (mode === 'off') {
472
- const changed = typeof nextSessionState.stopMessageText === 'string' ||
473
- typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
474
- typeof nextSessionState.stopMessageUsed === 'number' ||
475
- typeof nextSessionState.stopMessageSource === 'string' ||
476
- typeof nextSessionState.stopMessageStage === 'string' ||
477
- typeof nextSessionState.stopMessageObservationHash === 'string' ||
478
- typeof nextSessionState.stopMessageObservationStableCount === 'number' ||
479
- typeof nextSessionState.stopMessageBdWorkState === 'string' ||
480
- normalizeStopMessageStageMode(nextSessionState.stopMessageStageMode) !== 'off';
483
+ else {
481
484
  nextSessionState.stopMessageText = undefined;
482
485
  nextSessionState.stopMessageMaxRepeats = undefined;
483
486
  nextSessionState.stopMessageUsed = undefined;
484
- nextSessionState.stopMessageSource = undefined;
485
- nextSessionState.stopMessageStage = undefined;
486
- nextSessionState.stopMessageStageMode = 'off';
487
- nextSessionState.stopMessageObservationHash = undefined;
488
- nextSessionState.stopMessageObservationStableCount = undefined;
489
- nextSessionState.stopMessageBdWorkState = undefined;
490
- nextSessionState.stopMessageUpdatedAt = Date.now();
487
+ nextSessionState.stopMessageUpdatedAt = undefined;
491
488
  nextSessionState.stopMessageLastUsedAt = undefined;
492
- shouldPersistSessionState = changed;
493
- }
494
- else if (mode) {
495
- if (normalizeStopMessageStageMode(nextSessionState.stopMessageStageMode) !== mode) {
496
- nextSessionState.stopMessageStageMode = mode;
497
- shouldPersistSessionState = true;
498
- }
499
- const fallbackMaxRepeats = mode === 'on' || mode === 'auto'
500
- ? DEFAULT_STOP_MESSAGE_MAX_REPEATS
501
- : 0;
502
- const normalizedMaxRepeats = modeMaxRepeats > 0 ? modeMaxRepeats : fallbackMaxRepeats;
503
- if (normalizedMaxRepeats > 0 &&
504
- (typeof nextSessionState.stopMessageMaxRepeats !== 'number' ||
505
- Math.floor(nextSessionState.stopMessageMaxRepeats) !== normalizedMaxRepeats)) {
506
- nextSessionState.stopMessageMaxRepeats = normalizedMaxRepeats;
507
- shouldPersistSessionState = true;
508
- }
509
- // Explicit mode marker from latest user turn should re-arm lifecycle counters,
510
- // even when mode/max are unchanged (e.g. <**stopMessage:on,10**>).
511
- if (mode === 'on' || mode === 'auto') {
512
- nextSessionState.stopMessageUsed = 0;
513
- nextSessionState.stopMessageUpdatedAt = Date.now();
514
- nextSessionState.stopMessageLastUsedAt = undefined;
515
- nextSessionState.stopMessageStage = undefined;
516
- nextSessionState.stopMessageSource = 'explicit';
517
- nextSessionState.stopMessageObservationHash = undefined;
518
- nextSessionState.stopMessageObservationStableCount = 0;
519
- nextSessionState.stopMessageBdWorkState = undefined;
520
- shouldPersistSessionState = true;
521
- }
489
+ nextSessionState.stopMessageSource = undefined;
490
+ nextSessionState.stopMessageStageMode = undefined;
491
+ nextSessionState.stopMessageAiMode = undefined;
492
+ nextSessionState.stopMessageAiSeedPrompt = undefined;
493
+ nextSessionState.stopMessageAiHistory = undefined;
494
+ shouldPersistSessionState = true;
522
495
  }
523
496
  }
497
+ else if (hasStopMessageSet || hasStopMessageMode) {
498
+ nextSessionState.stopMessageText =
499
+ typeof appliedRoutingState.stopMessageText === 'string' && appliedRoutingState.stopMessageText.trim()
500
+ ? appliedRoutingState.stopMessageText
501
+ : undefined;
502
+ nextSessionState.stopMessageMaxRepeats =
503
+ typeof appliedRoutingState.stopMessageMaxRepeats === 'number' && Number.isFinite(appliedRoutingState.stopMessageMaxRepeats)
504
+ ? Math.floor(appliedRoutingState.stopMessageMaxRepeats)
505
+ : undefined;
506
+ nextSessionState.stopMessageUsed =
507
+ typeof appliedRoutingState.stopMessageUsed === 'number' && Number.isFinite(appliedRoutingState.stopMessageUsed)
508
+ ? Math.max(0, Math.floor(appliedRoutingState.stopMessageUsed))
509
+ : undefined;
510
+ nextSessionState.stopMessageUpdatedAt =
511
+ typeof appliedRoutingState.stopMessageUpdatedAt === 'number' && Number.isFinite(appliedRoutingState.stopMessageUpdatedAt)
512
+ ? appliedRoutingState.stopMessageUpdatedAt
513
+ : undefined;
514
+ nextSessionState.stopMessageLastUsedAt =
515
+ typeof appliedRoutingState.stopMessageLastUsedAt === 'number' && Number.isFinite(appliedRoutingState.stopMessageLastUsedAt)
516
+ ? appliedRoutingState.stopMessageLastUsedAt
517
+ : undefined;
518
+ nextSessionState.stopMessageSource =
519
+ typeof appliedRoutingState.stopMessageSource === 'string' && appliedRoutingState.stopMessageSource.trim()
520
+ ? appliedRoutingState.stopMessageSource.trim()
521
+ : undefined;
522
+ nextSessionState.stopMessageStageMode = normalizeStopMessageStageMode(appliedRoutingState.stopMessageStageMode);
523
+ nextSessionState.stopMessageAiMode = normalizeStopMessageAiMode(appliedRoutingState.stopMessageAiMode);
524
+ nextSessionState.stopMessageAiSeedPrompt =
525
+ typeof appliedRoutingState.stopMessageAiSeedPrompt === 'string' &&
526
+ appliedRoutingState.stopMessageAiSeedPrompt.trim()
527
+ ? appliedRoutingState.stopMessageAiSeedPrompt.trim()
528
+ : undefined;
529
+ nextSessionState.stopMessageAiHistory = Array.isArray(appliedRoutingState.stopMessageAiHistory)
530
+ ? appliedRoutingState.stopMessageAiHistory
531
+ : undefined;
532
+ shouldPersistSessionState = hasStopMessageStateChanged();
533
+ }
524
534
  if (shouldPersistSessionState) {
525
535
  this.routingInstructionState.set(stopMessageScope, nextSessionState);
526
536
  persistRoutingInstructionState(stopMessageScope, nextSessionState, this.routingStateStore);
@@ -531,17 +541,19 @@ export class VirtualRouterEngine {
531
541
  // 日志展示使用 session scope 的 stopMessage 状态,避免每次解析重复刷新时间/次数。
532
542
  if (typeof nextSessionState.stopMessageText === 'string' ||
533
543
  typeof nextSessionState.stopMessageMaxRepeats === 'number' ||
534
- typeof nextSessionState.stopMessageStageMode === 'string') {
544
+ typeof nextSessionState.stopMessageStageMode === 'string' ||
545
+ typeof nextSessionState.stopMessageAiMode === 'string' ||
546
+ typeof nextSessionState.stopMessageAiSeedPrompt === 'string' ||
547
+ Array.isArray(nextSessionState.stopMessageAiHistory)) {
535
548
  routingState.stopMessageText = nextSessionState.stopMessageText;
536
549
  routingState.stopMessageMaxRepeats = nextSessionState.stopMessageMaxRepeats;
537
550
  routingState.stopMessageUsed = nextSessionState.stopMessageUsed;
538
551
  routingState.stopMessageUpdatedAt = nextSessionState.stopMessageUpdatedAt;
539
552
  routingState.stopMessageLastUsedAt = nextSessionState.stopMessageLastUsedAt;
540
- routingState.stopMessageStage = nextSessionState.stopMessageStage;
541
553
  routingState.stopMessageStageMode = nextSessionState.stopMessageStageMode;
542
- routingState.stopMessageObservationHash = nextSessionState.stopMessageObservationHash;
543
- routingState.stopMessageObservationStableCount = nextSessionState.stopMessageObservationStableCount;
544
- routingState.stopMessageBdWorkState = nextSessionState.stopMessageBdWorkState;
554
+ routingState.stopMessageAiMode = nextSessionState.stopMessageAiMode;
555
+ routingState.stopMessageAiSeedPrompt = nextSessionState.stopMessageAiSeedPrompt;
556
+ routingState.stopMessageAiHistory = nextSessionState.stopMessageAiHistory;
545
557
  }
546
558
  }
547
559
  }
@@ -565,7 +577,7 @@ export class VirtualRouterEngine {
565
577
  nextSessionState.preCommandUpdatedAt = Date.now();
566
578
  }
567
579
  if (hasPreCommandSet) {
568
- const scriptPath = typeof routingState.preCommandScriptPath === 'string' ? routingState.preCommandScriptPath.trim() : '';
580
+ const scriptPath = typeof appliedRoutingState.preCommandScriptPath === 'string' ? appliedRoutingState.preCommandScriptPath.trim() : '';
569
581
  if (scriptPath) {
570
582
  if (sessionState.preCommandScriptPath !== scriptPath) {
571
583
  changed = true;
@@ -573,8 +585,8 @@ export class VirtualRouterEngine {
573
585
  nextSessionState.preCommandScriptPath = scriptPath;
574
586
  nextSessionState.preCommandSource = 'explicit';
575
587
  nextSessionState.preCommandUpdatedAt =
576
- typeof routingState.preCommandUpdatedAt === 'number' && Number.isFinite(routingState.preCommandUpdatedAt)
577
- ? routingState.preCommandUpdatedAt
588
+ typeof appliedRoutingState.preCommandUpdatedAt === 'number' && Number.isFinite(appliedRoutingState.preCommandUpdatedAt)
589
+ ? appliedRoutingState.preCommandUpdatedAt
578
590
  : Date.now();
579
591
  }
580
592
  }
@@ -591,17 +603,19 @@ export class VirtualRouterEngine {
591
603
  const sessionState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
592
604
  if (typeof sessionState.stopMessageText === 'string' ||
593
605
  typeof sessionState.stopMessageMaxRepeats === 'number' ||
594
- typeof sessionState.stopMessageStageMode === 'string') {
606
+ typeof sessionState.stopMessageStageMode === 'string' ||
607
+ typeof sessionState.stopMessageAiMode === 'string' ||
608
+ typeof sessionState.stopMessageAiSeedPrompt === 'string' ||
609
+ Array.isArray(sessionState.stopMessageAiHistory)) {
595
610
  routingState.stopMessageText = sessionState.stopMessageText;
596
611
  routingState.stopMessageMaxRepeats = sessionState.stopMessageMaxRepeats;
597
612
  routingState.stopMessageUsed = sessionState.stopMessageUsed;
598
613
  routingState.stopMessageUpdatedAt = sessionState.stopMessageUpdatedAt;
599
614
  routingState.stopMessageLastUsedAt = sessionState.stopMessageLastUsedAt;
600
- routingState.stopMessageStage = sessionState.stopMessageStage;
601
615
  routingState.stopMessageStageMode = sessionState.stopMessageStageMode;
602
- routingState.stopMessageObservationHash = sessionState.stopMessageObservationHash;
603
- routingState.stopMessageObservationStableCount = sessionState.stopMessageObservationStableCount;
604
- routingState.stopMessageBdWorkState = sessionState.stopMessageBdWorkState;
616
+ routingState.stopMessageAiMode = sessionState.stopMessageAiMode;
617
+ routingState.stopMessageAiSeedPrompt = sessionState.stopMessageAiSeedPrompt;
618
+ routingState.stopMessageAiHistory = sessionState.stopMessageAiHistory;
605
619
  }
606
620
  if (typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()) {
607
621
  routingState.preCommandScriptPath = sessionState.preCommandScriptPath;
@@ -922,23 +936,26 @@ export class VirtualRouterEngine {
922
936
  const mode = typeof candidate.stopMessageStageMode === 'string'
923
937
  ? candidate.stopMessageStageMode.trim().toLowerCase()
924
938
  : '';
925
- const allowModeOnly = !text && maxRepeats > 0 && (mode === 'on' || mode === 'auto');
926
- return (Boolean(text) || allowModeOnly) && maxRepeats > 0;
939
+ if (mode === 'off') {
940
+ return false;
941
+ }
942
+ return Boolean(text) && maxRepeats > 0;
927
943
  };
928
- const sessionScope = this.resolveSessionScope(metadata);
929
- const sessionState = sessionScope
930
- ? getRoutingInstructionState(sessionScope, this.routingInstructionState, this.routingStateStore)
931
- : null;
932
- const stickyKey = this.resolveStickyKey(metadata);
933
- const stickyState = stickyKey
934
- ? getRoutingInstructionState(stickyKey, this.routingInstructionState, this.routingStateStore)
935
- : null;
936
- const effectiveState = hasArmedStopState(sessionState)
937
- ? sessionState
938
- : hasArmedStopState(stickyState)
939
- ? stickyState
940
- : null;
941
- if (!effectiveState) {
944
+ const stopMessageScope = resolveStopMessageScope(metadata);
945
+ if (!stopMessageScope) {
946
+ const sessionScope = this.resolveSessionScope(metadata);
947
+ if (sessionScope) {
948
+ const legacyState = getRoutingInstructionState(sessionScope, this.routingInstructionState, this.routingStateStore);
949
+ if (hasClientInjectScopedFields(legacyState)) {
950
+ const cleared = stripClientInjectScopedFields(legacyState);
951
+ this.routingInstructionState.set(sessionScope, cleared);
952
+ persistRoutingInstructionState(sessionScope, cleared, this.routingStateStore);
953
+ }
954
+ }
955
+ return null;
956
+ }
957
+ const effectiveState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
958
+ if (!hasArmedStopState(effectiveState)) {
942
959
  return null;
943
960
  }
944
961
  const text = typeof effectiveState.stopMessageText === 'string' ? effectiveState.stopMessageText.trim() : '';
@@ -952,8 +969,11 @@ export class VirtualRouterEngine {
952
969
  const stageModeNormalized = stageModeRaw === 'on' || stageModeRaw === 'off' || stageModeRaw === 'auto'
953
970
  ? stageModeRaw
954
971
  : undefined;
955
- const allowModeOnly = !text && maxRepeats > 0 && (stageModeNormalized === 'on' || stageModeNormalized === 'auto');
956
- if ((!text && !allowModeOnly) || maxRepeats <= 0) {
972
+ const aiModeRaw = typeof effectiveState.stopMessageAiMode === 'string'
973
+ ? effectiveState.stopMessageAiMode.trim().toLowerCase()
974
+ : '';
975
+ const aiModeNormalized = aiModeRaw === 'on' || aiModeRaw === 'off' ? aiModeRaw : undefined;
976
+ if (stageModeNormalized === 'off' || !text || maxRepeats <= 0) {
957
977
  return null;
958
978
  }
959
979
  return {
@@ -973,34 +993,23 @@ export class VirtualRouterEngine {
973
993
  Number.isFinite(effectiveState.stopMessageLastUsedAt)
974
994
  ? { stopMessageLastUsedAt: effectiveState.stopMessageLastUsedAt }
975
995
  : {}),
976
- ...(typeof effectiveState.stopMessageStage === 'string' && effectiveState.stopMessageStage.trim()
977
- ? { stopMessageStage: effectiveState.stopMessageStage.trim() }
978
- : {}),
979
996
  ...(stageModeNormalized ? { stopMessageStageMode: stageModeNormalized } : {}),
980
- ...(typeof effectiveState.stopMessageObservationHash === 'string' && effectiveState.stopMessageObservationHash.trim()
981
- ? { stopMessageObservationHash: effectiveState.stopMessageObservationHash.trim() }
997
+ ...(aiModeNormalized ? { stopMessageAiMode: aiModeNormalized } : {}),
998
+ ...(typeof effectiveState.stopMessageAiSeedPrompt === 'string' &&
999
+ effectiveState.stopMessageAiSeedPrompt.trim()
1000
+ ? { stopMessageAiSeedPrompt: effectiveState.stopMessageAiSeedPrompt.trim() }
982
1001
  : {}),
983
- ...(typeof effectiveState.stopMessageObservationStableCount === 'number' &&
984
- Number.isFinite(effectiveState.stopMessageObservationStableCount)
985
- ? { stopMessageObservationStableCount: Math.max(0, Math.floor(effectiveState.stopMessageObservationStableCount)) }
1002
+ ...(Array.isArray(effectiveState.stopMessageAiHistory)
1003
+ ? { stopMessageAiHistory: effectiveState.stopMessageAiHistory }
986
1004
  : {}),
987
- ...(typeof effectiveState.stopMessageBdWorkState === 'string' && effectiveState.stopMessageBdWorkState.trim()
988
- ? { stopMessageBdWorkState: effectiveState.stopMessageBdWorkState.trim() }
989
- : {})
990
1005
  };
991
1006
  }
992
1007
  getPreCommandState(metadata) {
993
- const sessionScope = this.resolveSessionScope(metadata);
994
- const sessionState = sessionScope
995
- ? getRoutingInstructionState(sessionScope, this.routingInstructionState, this.routingStateStore)
996
- : null;
997
- const stickyKey = this.resolveStickyKey(metadata);
998
- const stickyState = stickyKey
999
- ? getRoutingInstructionState(stickyKey, this.routingInstructionState, this.routingStateStore)
1000
- : null;
1001
- const effectiveState = sessionState && typeof sessionState.preCommandScriptPath === 'string' && sessionState.preCommandScriptPath.trim()
1002
- ? sessionState
1003
- : stickyState;
1008
+ const stopMessageScope = resolveStopMessageScope(metadata);
1009
+ if (!stopMessageScope) {
1010
+ return null;
1011
+ }
1012
+ const effectiveState = getRoutingInstructionState(stopMessageScope, this.routingInstructionState, this.routingStateStore);
1004
1013
  if (!effectiveState) {
1005
1014
  return null;
1006
1015
  }
@@ -1469,7 +1478,7 @@ export class VirtualRouterEngine {
1469
1478
  baseList.unshift('multimodal');
1470
1479
  }
1471
1480
  }
1472
- if (hasVisionTargets) {
1481
+ if (hasVisionTargets && (!hasMultimodalTargets || forceVision)) {
1473
1482
  if (!baseList.includes('vision')) {
1474
1483
  baseList.push('vision');
1475
1484
  }