@namzu/cli 29.1.0 → 32.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/CHANGELOG.md +296 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +5 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/acp.d.ts +20 -2
  6. package/dist/commands/acp.d.ts.map +1 -1
  7. package/dist/commands/acp.js +42 -5
  8. package/dist/commands/acp.js.map +1 -1
  9. package/dist/commands/drain.d.ts.map +1 -1
  10. package/dist/commands/drain.js +2 -1
  11. package/dist/commands/drain.js.map +1 -1
  12. package/dist/commands/exec-json.d.ts.map +1 -1
  13. package/dist/commands/exec-json.js +24 -1
  14. package/dist/commands/exec-json.js.map +1 -1
  15. package/dist/commands/exec.d.ts.map +1 -1
  16. package/dist/commands/exec.js +5 -1
  17. package/dist/commands/exec.js.map +1 -1
  18. package/dist/commands/prior-messages.d.ts.map +1 -1
  19. package/dist/commands/prior-messages.js +42 -2
  20. package/dist/commands/prior-messages.js.map +1 -1
  21. package/dist/commands/schedule.d.ts.map +1 -1
  22. package/dist/commands/schedule.js +7 -5
  23. package/dist/commands/schedule.js.map +1 -1
  24. package/dist/config/composer-triggers.d.ts +41 -0
  25. package/dist/config/composer-triggers.d.ts.map +1 -0
  26. package/dist/config/composer-triggers.js +80 -0
  27. package/dist/config/composer-triggers.js.map +1 -0
  28. package/dist/config/load.d.ts.map +1 -1
  29. package/dist/config/load.js +58 -0
  30. package/dist/config/load.js.map +1 -1
  31. package/dist/config/schema.d.ts +34 -0
  32. package/dist/config/schema.d.ts.map +1 -1
  33. package/dist/config/schema.js.map +1 -1
  34. package/dist/context/doctrine.d.ts +3 -3
  35. package/dist/context/doctrine.d.ts.map +1 -1
  36. package/dist/context/doctrine.js +3 -3
  37. package/dist/context/doctrine.js.map +1 -1
  38. package/dist/context/environment.d.ts +26 -3
  39. package/dist/context/environment.d.ts.map +1 -1
  40. package/dist/context/environment.js +68 -9
  41. package/dist/context/environment.js.map +1 -1
  42. package/dist/context/runtime-message.d.ts.map +1 -1
  43. package/dist/context/runtime-message.js +4 -0
  44. package/dist/context/runtime-message.js.map +1 -1
  45. package/dist/integrations/mcp/servers.d.ts +72 -5
  46. package/dist/integrations/mcp/servers.d.ts.map +1 -1
  47. package/dist/integrations/mcp/servers.js +144 -23
  48. package/dist/integrations/mcp/servers.js.map +1 -1
  49. package/dist/integrations/notifications/desktop.d.ts +2 -0
  50. package/dist/integrations/notifications/desktop.d.ts.map +1 -1
  51. package/dist/integrations/notifications/desktop.js +6 -5
  52. package/dist/integrations/notifications/desktop.js.map +1 -1
  53. package/dist/integrations/plugins/runtime.d.ts +10 -2
  54. package/dist/integrations/plugins/runtime.d.ts.map +1 -1
  55. package/dist/integrations/plugins/runtime.js +16 -29
  56. package/dist/integrations/plugins/runtime.js.map +1 -1
  57. package/dist/integrations/providers/harness-credentials.d.ts +9 -4
  58. package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
  59. package/dist/integrations/providers/harness-credentials.js +16 -8
  60. package/dist/integrations/providers/harness-credentials.js.map +1 -1
  61. package/dist/integrations/resident/session-step.d.ts.map +1 -1
  62. package/dist/integrations/resident/session-step.js +2 -1
  63. package/dist/integrations/resident/session-step.js.map +1 -1
  64. package/dist/integrations/sessions/store.d.ts.map +1 -1
  65. package/dist/integrations/sessions/store.js +20 -2
  66. package/dist/integrations/sessions/store.js.map +1 -1
  67. package/dist/integrations/subagents/NamzuCliAgent.d.ts +8 -0
  68. package/dist/integrations/subagents/NamzuCliAgent.d.ts.map +1 -0
  69. package/dist/integrations/subagents/NamzuCliAgent.js +9 -0
  70. package/dist/integrations/subagents/NamzuCliAgent.js.map +1 -0
  71. package/dist/integrations/subagents/runtime.d.ts +5 -4
  72. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  73. package/dist/integrations/subagents/runtime.js +46 -11
  74. package/dist/integrations/subagents/runtime.js.map +1 -1
  75. package/dist/integrations/web/open-url.d.ts +24 -0
  76. package/dist/integrations/web/open-url.d.ts.map +1 -0
  77. package/dist/integrations/web/open-url.js +73 -0
  78. package/dist/integrations/web/open-url.js.map +1 -0
  79. package/dist/integrations/web/search.d.ts.map +1 -1
  80. package/dist/integrations/web/search.js +8 -6
  81. package/dist/integrations/web/search.js.map +1 -1
  82. package/dist/permissions/rules.d.ts +5 -1
  83. package/dist/permissions/rules.d.ts.map +1 -1
  84. package/dist/permissions/rules.js +85 -3
  85. package/dist/permissions/rules.js.map +1 -1
  86. package/dist/schedule/build.d.ts +26 -2
  87. package/dist/schedule/build.d.ts.map +1 -1
  88. package/dist/schedule/build.js +139 -20
  89. package/dist/schedule/build.js.map +1 -1
  90. package/dist/schedule/changes.d.ts +6 -0
  91. package/dist/schedule/changes.d.ts.map +1 -1
  92. package/dist/schedule/changes.js +23 -0
  93. package/dist/schedule/changes.js.map +1 -1
  94. package/dist/schedule/commands/add.d.ts +1 -1
  95. package/dist/schedule/commands/add.d.ts.map +1 -1
  96. package/dist/schedule/commands/add.js +188 -54
  97. package/dist/schedule/commands/add.js.map +1 -1
  98. package/dist/schedule/commands/lifecycle.d.ts.map +1 -1
  99. package/dist/schedule/commands/lifecycle.js +2 -1
  100. package/dist/schedule/commands/lifecycle.js.map +1 -1
  101. package/dist/schedule/commands/list.d.ts +2 -0
  102. package/dist/schedule/commands/list.d.ts.map +1 -1
  103. package/dist/schedule/commands/list.js +69 -20
  104. package/dist/schedule/commands/list.js.map +1 -1
  105. package/dist/schedule/daemon/daemon.d.ts +1 -1
  106. package/dist/schedule/daemon/daemon.d.ts.map +1 -1
  107. package/dist/schedule/daemon/daemon.js +22 -5
  108. package/dist/schedule/daemon/daemon.js.map +1 -1
  109. package/dist/schedule/daemon/notify.d.ts +29 -2
  110. package/dist/schedule/daemon/notify.d.ts.map +1 -1
  111. package/dist/schedule/daemon/notify.js +71 -2
  112. package/dist/schedule/daemon/notify.js.map +1 -1
  113. package/dist/schedule/env.d.ts +8 -4
  114. package/dist/schedule/env.d.ts.map +1 -1
  115. package/dist/schedule/env.js +13 -6
  116. package/dist/schedule/env.js.map +1 -1
  117. package/dist/schedule/fire/calls.d.ts +49 -0
  118. package/dist/schedule/fire/calls.d.ts.map +1 -0
  119. package/dist/schedule/fire/calls.js +114 -0
  120. package/dist/schedule/fire/calls.js.map +1 -0
  121. package/dist/schedule/fire/fire.d.ts +1 -1
  122. package/dist/schedule/fire/fire.d.ts.map +1 -1
  123. package/dist/schedule/fire/fire.js +209 -15
  124. package/dist/schedule/fire/fire.js.map +1 -1
  125. package/dist/schedule/fire/result.d.ts +1 -1
  126. package/dist/schedule/fire/result.d.ts.map +1 -1
  127. package/dist/schedule/fire/result.js +2 -1
  128. package/dist/schedule/fire/result.js.map +1 -1
  129. package/dist/schedule/fire/run-script.d.ts +46 -0
  130. package/dist/schedule/fire/run-script.d.ts.map +1 -0
  131. package/dist/schedule/fire/run-script.js +81 -0
  132. package/dist/schedule/fire/run-script.js.map +1 -0
  133. package/dist/schedule/fire/unattended-note.d.ts +8 -0
  134. package/dist/schedule/fire/unattended-note.d.ts.map +1 -1
  135. package/dist/schedule/fire/unattended-note.js +5 -0
  136. package/dist/schedule/fire/unattended-note.js.map +1 -1
  137. package/dist/schedule/fire/wake-gate.d.ts +39 -0
  138. package/dist/schedule/fire/wake-gate.d.ts.map +1 -0
  139. package/dist/schedule/fire/wake-gate.js +90 -0
  140. package/dist/schedule/fire/wake-gate.js.map +1 -0
  141. package/dist/schedule/floor.d.ts +28 -8
  142. package/dist/schedule/floor.d.ts.map +1 -1
  143. package/dist/schedule/floor.js +624 -114
  144. package/dist/schedule/floor.js.map +1 -1
  145. package/dist/schedule/policy.d.ts +40 -0
  146. package/dist/schedule/policy.d.ts.map +1 -1
  147. package/dist/schedule/policy.js +36 -0
  148. package/dist/schedule/policy.js.map +1 -1
  149. package/dist/schedule/script-check.d.ts +53 -0
  150. package/dist/schedule/script-check.d.ts.map +1 -0
  151. package/dist/schedule/script-check.js +145 -0
  152. package/dist/schedule/script-check.js.map +1 -0
  153. package/dist/schedule/script-shell.d.ts +3 -0
  154. package/dist/schedule/script-shell.d.ts.map +1 -0
  155. package/dist/schedule/script-shell.js +5 -0
  156. package/dist/schedule/script-shell.js.map +1 -0
  157. package/dist/schedule/service/index.d.ts +1 -1
  158. package/dist/schedule/service/index.d.ts.map +1 -1
  159. package/dist/schedule/service/index.js +20 -11
  160. package/dist/schedule/service/index.js.map +1 -1
  161. package/dist/schedule/service/manifest.js +1 -1
  162. package/dist/schedule/service/manifest.js.map +1 -1
  163. package/dist/schedule/service/windows-task.d.ts +7 -0
  164. package/dist/schedule/service/windows-task.d.ts.map +1 -1
  165. package/dist/schedule/service/windows-task.js +65 -0
  166. package/dist/schedule/service/windows-task.js.map +1 -1
  167. package/dist/schedule/service/wsl.d.ts +13 -1
  168. package/dist/schedule/service/wsl.d.ts.map +1 -1
  169. package/dist/schedule/service/wsl.js +16 -6
  170. package/dist/schedule/service/wsl.js.map +1 -1
  171. package/dist/schedule/store/atomic.d.ts +5 -2
  172. package/dist/schedule/store/atomic.d.ts.map +1 -1
  173. package/dist/schedule/store/atomic.js +7 -4
  174. package/dist/schedule/store/atomic.js.map +1 -1
  175. package/dist/schedule/store/claims.js +1 -1
  176. package/dist/schedule/store/claims.js.map +1 -1
  177. package/dist/schedule/store/history.d.ts +1 -1
  178. package/dist/schedule/store/history.d.ts.map +1 -1
  179. package/dist/schedule/store/history.js +4 -1
  180. package/dist/schedule/store/history.js.map +1 -1
  181. package/dist/schedule/store/jobs.d.ts +19 -3
  182. package/dist/schedule/store/jobs.d.ts.map +1 -1
  183. package/dist/schedule/store/jobs.js +83 -16
  184. package/dist/schedule/store/jobs.js.map +1 -1
  185. package/dist/schedule/store/state.js +1 -1
  186. package/dist/schedule/store/state.js.map +1 -1
  187. package/dist/schedule/types.d.ts +98 -6
  188. package/dist/schedule/types.d.ts.map +1 -1
  189. package/dist/schedule/types.js +25 -1
  190. package/dist/schedule/types.js.map +1 -1
  191. package/dist/schedule/visible-source.d.ts +18 -0
  192. package/dist/schedule/visible-source.d.ts.map +1 -0
  193. package/dist/schedule/visible-source.js +59 -0
  194. package/dist/schedule/visible-source.js.map +1 -0
  195. package/dist/skills/directory.d.ts +38 -0
  196. package/dist/skills/directory.d.ts.map +1 -0
  197. package/dist/skills/directory.js +51 -0
  198. package/dist/skills/directory.js.map +1 -0
  199. package/dist/test-support/toolset.d.ts +23 -0
  200. package/dist/test-support/toolset.d.ts.map +1 -0
  201. package/dist/test-support/toolset.js +27 -0
  202. package/dist/test-support/toolset.js.map +1 -0
  203. package/dist/tui/App.d.ts.map +1 -1
  204. package/dist/tui/App.js +343 -64
  205. package/dist/tui/App.js.map +1 -1
  206. package/dist/tui/ChoicePicker.d.ts +2 -0
  207. package/dist/tui/ChoicePicker.d.ts.map +1 -1
  208. package/dist/tui/ChoicePicker.js +10 -2
  209. package/dist/tui/ChoicePicker.js.map +1 -1
  210. package/dist/tui/Composer.d.ts +62 -3
  211. package/dist/tui/Composer.d.ts.map +1 -1
  212. package/dist/tui/Composer.js +162 -25
  213. package/dist/tui/Composer.js.map +1 -1
  214. package/dist/tui/ComposerFrame.d.ts +1 -1
  215. package/dist/tui/ComposerFrame.js +3 -3
  216. package/dist/tui/ComposerFrame.js.map +1 -1
  217. package/dist/tui/EffortSlider.d.ts +8 -9
  218. package/dist/tui/EffortSlider.d.ts.map +1 -1
  219. package/dist/tui/EffortSlider.js +15 -12
  220. package/dist/tui/EffortSlider.js.map +1 -1
  221. package/dist/tui/PermissionOverlay.d.ts +13 -1
  222. package/dist/tui/PermissionOverlay.d.ts.map +1 -1
  223. package/dist/tui/PermissionOverlay.js +26 -4
  224. package/dist/tui/PermissionOverlay.js.map +1 -1
  225. package/dist/tui/StatusBar.d.ts +12 -12
  226. package/dist/tui/StatusBar.d.ts.map +1 -1
  227. package/dist/tui/StatusBar.js +15 -15
  228. package/dist/tui/StatusBar.js.map +1 -1
  229. package/dist/tui/agent.d.ts +73 -12
  230. package/dist/tui/agent.d.ts.map +1 -1
  231. package/dist/tui/agent.js +430 -180
  232. package/dist/tui/agent.js.map +1 -1
  233. package/dist/tui/hypermode.d.ts +37 -0
  234. package/dist/tui/hypermode.d.ts.map +1 -0
  235. package/dist/tui/hypermode.js +58 -0
  236. package/dist/tui/hypermode.js.map +1 -0
  237. package/dist/tui/open-browser.d.ts +29 -1
  238. package/dist/tui/open-browser.d.ts.map +1 -1
  239. package/dist/tui/open-browser.js +83 -11
  240. package/dist/tui/open-browser.js.map +1 -1
  241. package/dist/tui/permission-review.d.ts +11 -2
  242. package/dist/tui/permission-review.d.ts.map +1 -1
  243. package/dist/tui/permission-review.js +176 -19
  244. package/dist/tui/permission-review.js.map +1 -1
  245. package/dist/tui/schedule/host-commands.d.ts.map +1 -1
  246. package/dist/tui/schedule/host-commands.js +95 -33
  247. package/dist/tui/schedule/host-commands.js.map +1 -1
  248. package/dist/tui/schedule/integration.d.ts +6 -2
  249. package/dist/tui/schedule/integration.d.ts.map +1 -1
  250. package/dist/tui/schedule/integration.js +3 -1
  251. package/dist/tui/schedule/integration.js.map +1 -1
  252. package/dist/tui/schedule/resume.d.ts.map +1 -1
  253. package/dist/tui/schedule/resume.js +10 -1
  254. package/dist/tui/schedule/resume.js.map +1 -1
  255. package/dist/tui/schedule/tool-host.d.ts +18 -1
  256. package/dist/tui/schedule/tool-host.d.ts.map +1 -1
  257. package/dist/tui/schedule/tool-host.js +384 -84
  258. package/dist/tui/schedule/tool-host.js.map +1 -1
  259. package/dist/tui/slashCommands.d.ts +23 -5
  260. package/dist/tui/slashCommands.d.ts.map +1 -1
  261. package/dist/tui/slashCommands.js +80 -31
  262. package/dist/tui/slashCommands.js.map +1 -1
  263. package/dist/tui/theme.d.ts +10 -4
  264. package/dist/tui/theme.d.ts.map +1 -1
  265. package/dist/tui/theme.js +4 -3
  266. package/dist/tui/theme.js.map +1 -1
  267. package/dist/tui/triggers/analyze.d.ts +57 -0
  268. package/dist/tui/triggers/analyze.d.ts.map +1 -0
  269. package/dist/tui/triggers/analyze.js +221 -0
  270. package/dist/tui/triggers/analyze.js.map +1 -0
  271. package/dist/tui/triggers/context-text.d.ts +21 -0
  272. package/dist/tui/triggers/context-text.d.ts.map +1 -0
  273. package/dist/tui/triggers/context-text.js +44 -0
  274. package/dist/tui/triggers/context-text.js.map +1 -0
  275. package/dist/tui/triggers/copy.d.ts +38 -0
  276. package/dist/tui/triggers/copy.d.ts.map +1 -0
  277. package/dist/tui/triggers/copy.js +163 -0
  278. package/dist/tui/triggers/copy.js.map +1 -0
  279. package/dist/tui/triggers/detect.d.ts +83 -0
  280. package/dist/tui/triggers/detect.d.ts.map +1 -0
  281. package/dist/tui/triggers/detect.js +295 -0
  282. package/dist/tui/triggers/detect.js.map +1 -0
  283. package/dist/tui/triggers/fold.d.ts +34 -0
  284. package/dist/tui/triggers/fold.d.ts.map +1 -0
  285. package/dist/tui/triggers/fold.js +44 -0
  286. package/dist/tui/triggers/fold.js.map +1 -0
  287. package/dist/tui/triggers/pattern.d.ts +66 -0
  288. package/dist/tui/triggers/pattern.d.ts.map +1 -0
  289. package/dist/tui/triggers/pattern.js +243 -0
  290. package/dist/tui/triggers/pattern.js.map +1 -0
  291. package/dist/tui/triggers/provenance.d.ts +58 -0
  292. package/dist/tui/triggers/provenance.d.ts.map +1 -0
  293. package/dist/tui/triggers/provenance.js +98 -0
  294. package/dist/tui/triggers/provenance.js.map +1 -0
  295. package/dist/tui/triggers/registry.d.ts +113 -0
  296. package/dist/tui/triggers/registry.d.ts.map +1 -0
  297. package/dist/tui/triggers/registry.js +164 -0
  298. package/dist/tui/triggers/registry.js.map +1 -0
  299. package/dist/tui/triggers/setting.d.ts +18 -0
  300. package/dist/tui/triggers/setting.d.ts.map +1 -0
  301. package/dist/tui/triggers/setting.js +61 -0
  302. package/dist/tui/triggers/setting.js.map +1 -0
  303. package/dist/tui/triggers/verbs.d.ts +29 -0
  304. package/dist/tui/triggers/verbs.d.ts.map +1 -0
  305. package/dist/tui/triggers/verbs.js +140 -0
  306. package/dist/tui/triggers/verbs.js.map +1 -0
  307. package/dist/tui/types.d.ts +3 -1
  308. package/dist/tui/types.d.ts.map +1 -1
  309. package/package.json +12 -12
  310. package/skills/README.md +5 -0
  311. package/skills/schedule-task/SKILL.md +123 -5
package/dist/tui/App.js CHANGED
@@ -19,7 +19,7 @@ import { ToolOutputViewer } from './ToolOutputViewer.js';
19
19
  import { join, relative } from 'node:path';
20
20
  import { DEFAULT_MAX_GOAL_ROUNDS, DiskMessageFeedbackStore, GoalRoundLimitError, HostCommandRegistry, SessionGoalActivation, StaleGoalError, TurnCancelled, asSessionId, createAssistantMessage, createUserMessage, generateSessionId, generateTurnId, isCompactionMessage, kernelHostCommands, SKILL_TOOL_NAME, } from '@namzu/sdk';
21
21
  import { Box, Text, useApp, useInput, useStdout, useWindowSize } from 'ink';
22
- import { useCallback, useEffect, useRef, useState } from 'react';
22
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
23
23
  import { pinTrustedProjectPath, resolveTrustedProjectContext, } from '../config/trusted-project-context.js';
24
24
  import { visibleProjectInstructionPath } from '../context/project-path.js';
25
25
  import { runtimeContextLabel } from '../context/runtime-message.js';
@@ -43,8 +43,15 @@ import { BrandHeader } from './BrandHeader.js';
43
43
  import { ChoicePicker } from './ChoicePicker.js';
44
44
  import { emptyPluginReport, pluginDetails, pluginOption, pluginStartupState } from './plugin-view.js';
45
45
  import { Composer, suggestionWindowSize, } from './Composer.js';
46
+ import { resolveComposerTriggers } from '../config/composer-triggers.js';
47
+ import { triggerContextTexts, triggerEffort } from './triggers/context-text.js';
48
+ import { transcriptTagLine } from './triggers/copy.js';
49
+ import { unavailable as triggerUnavailable } from './triggers/detect.js';
50
+ import { BUILTIN_TRIGGERS, compileRegistry, triggerDefinition, } from './triggers/registry.js';
51
+ import { describeComposerTriggers, setComposerTriggers } from './triggers/setting.js';
46
52
  import { ComposerFrame } from './ComposerFrame.js';
47
53
  import { EffortSlider, effortSliderLayout } from './EffortSlider.js';
54
+ import { HYPERMODE, HYPERMODE_SUMMARY, ORCHESTRATE_ALIAS_NOTICE, hypermodeEffort, hypermodeStopLabel, } from './hypermode.js';
48
55
  import { CopyPicker } from './CopyPicker.js';
49
56
  import { EditPromptPicker } from './EditPromptPicker.js';
50
57
  import { LiveActivity, formatElapsed } from './LiveActivity.js';
@@ -415,11 +422,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
415
422
  // A session setting layered above effort, not a level a provider publishes
416
423
  // — see docs/cli/slash-commands.md's /effort section. In-memory and
417
424
  // per-session like reasoningEffort above; no preferences file involved.
418
- const [orchestrateMode, setOrchestrateModeState] = useState(false);
419
- const orchestrateModeRef = useRef(false);
420
- const setOrchestrateMode = useCallback((next) => {
421
- orchestrateModeRef.current = next;
422
- setOrchestrateModeState(next);
425
+ const [hypermode, setHypermodeState] = useState(false);
426
+ const hypermodeRef = useRef(false);
427
+ const setHypermode = useCallback((next) => {
428
+ hypermodeRef.current = next;
429
+ setHypermodeState(next);
423
430
  }, []);
424
431
  const [activeSkills, setActiveSkills] = useState([]);
425
432
  // Read when the session comes up rather than on every keystroke: the
@@ -871,8 +878,11 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
871
878
  const browserProfileRef = useRef(undefined);
872
879
  /** The current session's browser, for notices written from event handlers. */
873
880
  const browserControlRef = useRef(undefined);
881
+ /** The current session, for the review screen to name a computer-use control by its ref. */
882
+ const reviewSessionRef = useRef(null);
874
883
  useEffect(() => {
875
884
  browserControlRef.current = session?.browser;
885
+ reviewSessionRef.current = session;
876
886
  }, [session]);
877
887
  useEffect(() => {
878
888
  const source = session?.subagents;
@@ -980,7 +990,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
980
990
  ask: (question, signal) => scheduleLiveRef.current?.ask(question, signal) ?? Promise.resolve({ kind: 'skip' }),
981
991
  askPermission: (request) => scheduleLiveRef.current?.askPermission(request) ??
982
992
  Promise.resolve({ kind: 'reject', feedback: 'Nobody can answer yet.' }),
983
- submit: (text) => scheduleLiveRef.current?.submit(text),
993
+ submit: (text, createdBy) => scheduleLiveRef.current?.submit(text, createdBy),
984
994
  });
985
995
  }
986
996
  /**
@@ -1294,6 +1304,46 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1294
1304
  const learningRef = useRef({ generation: -1, proposed: false, skillUsed: false });
1295
1305
  /** `/skills save off|on` in this session, over whatever the config files say. */
1296
1306
  const skillSuggestOverrideRef = useRef(undefined);
1307
+ /** `/config triggers on|off` in this session, over whatever the config files say. */
1308
+ const [composerTriggersOverride, setComposerTriggersOverride] = useState();
1309
+ const composerTriggerSettings = useMemo(() => resolveComposerTriggers(ctx.composerTriggers), [ctx.composerTriggers]);
1310
+ const composerTriggerRegistry = useMemo(() => compileRegistry(BUILTIN_TRIGGERS, {
1311
+ arming: composerTriggerSettings.arming,
1312
+ locales: composerTriggerSettings.languages,
1313
+ }), [composerTriggerSettings]);
1314
+ const composerTriggersOn = composerTriggersOverride ?? composerTriggerSettings.enabled;
1315
+ /**
1316
+ * Whether the model can run `skill-creator` here, which save-as-skill
1317
+ * needs. Read once for the tag row; read again when a turn starts and when
1318
+ * its follow-up would run, in case the skills changed meanwhile.
1319
+ */
1320
+ const readSkillCreator = useCallback(() => {
1321
+ try {
1322
+ const creator = discoverSkills({
1323
+ cwd: ctx.cwd,
1324
+ ...(ctx.skills ? { config: ctx.skills } : {}),
1325
+ }).find((skill) => skill.name === SKILL_CREATOR_SKILL);
1326
+ return Boolean(creator && !creator.problem && creator.invocation !== 'operator');
1327
+ }
1328
+ catch {
1329
+ return false;
1330
+ }
1331
+ }, [ctx.cwd, ctx.skills]);
1332
+ const skillCreatorAvailable = useMemo(() => readSkillCreator(), [readSkillCreator]);
1333
+ /** The session as the triggers see it right now, for a check at a turn's start or end. */
1334
+ const triggerContextNow = useCallback(() => ({
1335
+ permissionMode: permissionModeRef.current,
1336
+ sessionHypermode: hypermodeRef.current,
1337
+ effortMenu: (session?.reasoningEffortLevels?.length ?? 0) > 0,
1338
+ // The interactive session always mounts `Agent` and the `schedule`
1339
+ // tool; only scheduled runs withhold them, and those never read
1340
+ // composer triggers.
1341
+ agentTool: true,
1342
+ skillCreator: readSkillCreator(),
1343
+ scheduleTool: true,
1344
+ }), [readSkillCreator, session]);
1345
+ /** A turn's hand-off to its after-turn triggers; assigned below, where the queue lives. */
1346
+ const triggerFollowUpsRef = useRef(null);
1297
1347
  const conversationLearning = useCallback(() => {
1298
1348
  if (learningRef.current.generation !== conversationGenRef.current) {
1299
1349
  learningRef.current = {
@@ -1610,22 +1660,28 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1610
1660
  pushMessage('system', `Reasoning: ${effort} (this session).`);
1611
1661
  }, [hasUnsettledTurn, pushMessage, session, setReasoningEffort, state]);
1612
1662
  /**
1613
- * Turn the session's orchestrate mode on or off.
1663
+ * Turn the session's hypermode on or off.
1614
1664
  *
1615
1665
  * On: pins effort to the model's highest published level (last entry of
1616
1666
  * `reasoningEffortLevels`, which every provider publishes low-to-high) and
1617
- * strengthens delegation guidance for future turns via `SendOptions.orchestrate`
1667
+ * strengthens delegation guidance for future turns via `SendOptions.hypermode`
1618
1668
  * — see `applyReasoningEffort` above for the mirrored guard shape. When the
1619
1669
  * model publishes no exact menu, effort is left alone and the notice says
1620
1670
  * so explicitly rather than silently doing nothing.
1621
1671
  */
1622
- const applyOrchestrateMode = useCallback((enabled, selectedSession) => {
1672
+ /**
1673
+ * The effort in force before hypermode pinned the highest level, and the
1674
+ * level it pinned. Turning the mode off puts the effort back — unless the
1675
+ * operator chose another level meanwhile, which then stays.
1676
+ */
1677
+ const hypermodePinRef = useRef(null);
1678
+ const applyHypermode = useCallback((enabled, selectedSession) => {
1623
1679
  if (!session?.hasProvider) {
1624
- pushMessage('system', 'No active session — pick a provider before turning orchestrate mode on.');
1680
+ pushMessage('system', 'No active session — pick a provider before turning hypermode on.');
1625
1681
  return;
1626
1682
  }
1627
1683
  if (selectedSession !== undefined && selectedSession !== session) {
1628
- pushMessage('system', 'Orchestrate mode was not changed: the selected model is no longer active.');
1684
+ pushMessage('system', 'Hypermode was not changed: the selected model is no longer active.');
1629
1685
  return;
1630
1686
  }
1631
1687
  if (state !== 'idle' ||
@@ -1634,22 +1690,38 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
1634
1690
  (queuedRef.current.length > 0 && selectedSession !== session) ||
1635
1691
  permissionResolveRef.current !== null ||
1636
1692
  compactingRef.current) {
1637
- pushMessage('system', 'Orchestrate mode was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
1693
+ pushMessage('system', 'Hypermode was not changed: wait for the active turn, prompt, compaction, and queued work to settle.');
1694
+ return;
1695
+ }
1696
+ if (!enabled && !hypermodeRef.current) {
1697
+ pushMessage('system', 'Hypermode is off.');
1638
1698
  return;
1639
1699
  }
1640
- setOrchestrateMode(enabled);
1700
+ setHypermode(enabled);
1641
1701
  if (!enabled) {
1642
- pushMessage('system', 'Orchestrate mode is off.');
1702
+ const pin = hypermodePinRef.current;
1703
+ hypermodePinRef.current = null;
1704
+ if (pin && reasoningEffortRef.current === pin.pinned) {
1705
+ setReasoningEffort(pin.before);
1706
+ pushMessage('system', `Hypermode is off — effort back to ${pin.before ?? 'the provider default'}.`);
1707
+ return;
1708
+ }
1709
+ pushMessage('system', 'Hypermode is off.');
1643
1710
  return;
1644
1711
  }
1645
- const highest = highestReasoningEffort(session.reasoningEffortLevels);
1646
- if (highest !== undefined) {
1647
- setReasoningEffort(highest);
1648
- pushMessage('system', `Orchestrate mode is on — effort pinned to ${highest} for ${session.modelSummary ?? 'this model'}, and delegation guidance is strengthened for this session.`);
1712
+ const pinned = hypermodeEffort(session.reasoningEffortLevels);
1713
+ if (pinned !== undefined) {
1714
+ // Turning it on twice keeps the effort from before the first time.
1715
+ if (!hypermodePinRef.current)
1716
+ hypermodePinRef.current = { before: reasoningEffortRef.current, pinned };
1717
+ else
1718
+ hypermodePinRef.current = { ...hypermodePinRef.current, pinned };
1719
+ setReasoningEffort(pinned);
1720
+ pushMessage('system', `Hypermode is on — effort pinned to ${pinned} for ${session.modelSummary ?? 'this model'}, and delegation guidance is strengthened for this session.`);
1649
1721
  return;
1650
1722
  }
1651
- pushMessage('system', `Orchestrate mode is on — ${session.modelSummary ?? 'this model'} does not publish an exact effort menu, so effort was left as is. Delegation guidance is still strengthened for this session.`);
1652
- }, [hasUnsettledTurn, pushMessage, session, setOrchestrateMode, setReasoningEffort, state]);
1723
+ pushMessage('system', `Hypermode is on — ${session.modelSummary ?? 'this model'} does not publish an exact effort menu, so effort was left as is. Delegation guidance is still strengthened for this session.`);
1724
+ }, [hasUnsettledTurn, pushMessage, session, setHypermode, setReasoningEffort, state]);
1653
1725
  const stepReasoningEffort = useCallback((direction) => {
1654
1726
  const levels = session?.reasoningEffortLevels;
1655
1727
  if (!session?.hasProvider || levels === undefined || levels.length === 0) {
@@ -2211,8 +2283,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2211
2283
  removeStoredCredential(value);
2212
2284
  return;
2213
2285
  }
2214
- if (picker.kind === 'reasoning-effort' && value === ORCHESTRATE_MODE_VALUE) {
2215
- applyOrchestrateMode(!orchestrateModeRef.current, picker.selectedSession);
2286
+ if (picker.kind === 'reasoning-effort' && value === HYPERMODE_VALUE) {
2287
+ applyHypermode(!hypermodeRef.current, picker.selectedSession);
2216
2288
  return;
2217
2289
  }
2218
2290
  applyReasoningEffort(value, picker.selectedSession);
@@ -2220,7 +2292,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2220
2292
  activateSkill,
2221
2293
  advanceQueueContinuation,
2222
2294
  appLifetime.signal,
2223
- applyOrchestrateMode,
2295
+ applyHypermode,
2224
2296
  applyPermissionMode,
2225
2297
  applyReasoningEffort,
2226
2298
  archiveCurrentConversation,
@@ -2503,6 +2575,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2503
2575
  // question when a decision is genuinely theirs.
2504
2576
  askUser: true,
2505
2577
  allowModelSwitch: true,
2578
+ // The user is at this desktop, so the model may open a page for them.
2579
+ openUrl: true,
2506
2580
  });
2507
2581
  if (signal?.aborted) {
2508
2582
  void s.close();
@@ -2541,11 +2615,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2541
2615
  // A picker-owned provider/model change is one state transition. Clear the
2542
2616
  // old model's effort selection before publishing the replacement session
2543
2617
  // or releasing any paused queue. Failed and superseded candidates returned
2544
- // above, so they leave the current session selection untouched. Orchestrate
2618
+ // above, so they leave the current session selection untouched. Hypermode
2545
2619
  // mode survives the switch: re-pin to the new model's highest published
2546
2620
  // level instead of clearing, exactly as it pinned when first turned on.
2547
2621
  if (signal !== undefined) {
2548
- setReasoningEffort(orchestrateModeRef.current ? highestReasoningEffort(s.reasoningEffortLevels) : undefined);
2622
+ const repinned = hypermodeRef.current ? hypermodeEffort(s.reasoningEffortLevels) : undefined;
2623
+ // A new model starts at its own default; that is what turning
2624
+ // hypermode off returns to now.
2625
+ hypermodePinRef.current = repinned ? { before: undefined, pinned: repinned } : null;
2626
+ setReasoningEffort(repinned);
2549
2627
  }
2550
2628
  // Re-hydration (a provider switch via /model) builds a second session;
2551
2629
  // without this the first one's tool-server child processes stay alive
@@ -2554,9 +2632,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
2554
2632
  void previousSessionRef.current?.close();
2555
2633
  previousSessionRef.current = s;
2556
2634
  setSession(s);
2557
- // Orchestrate mode already re-pinned effort above; reopening this picker
2635
+ // Hypermode already re-pinned effort above; reopening this picker
2558
2636
  // would ask the operator to redo a choice the mode just made for them.
2559
- if (!orchestrateModeRef.current && options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
2637
+ if (!hypermodeRef.current && options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
2560
2638
  // Own input before publishing ready or releasing a paused queue. The
2561
2639
  // menu closes only after choosing an effort or keeping the new default.
2562
2640
  setSelectedChoice(0);
@@ -3279,7 +3357,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3279
3357
  // only on its own.
3280
3358
  const kept = [];
3281
3359
  for (const pending of permissionQueueRef.current.splice(0)) {
3282
- if (releasedByApproveAll(pending.permission.toolCalls))
3360
+ // Nor a question about sharing the screen, which only its own
3361
+ // answer settles.
3362
+ if (pending.permission.screenConsent !== true &&
3363
+ releasedByApproveAll(pending.permission.toolCalls))
3283
3364
  pending.resolve(decision);
3284
3365
  else
3285
3366
  kept.push(pending);
@@ -3895,7 +3976,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3895
3976
  feedback: permissionReviewRefusal(review.reason),
3896
3977
  });
3897
3978
  }
3898
- const summary = buildPermissionSummary(review.text);
3979
+ const summary = buildPermissionSummary(review.text, {
3980
+ describeUiRef: (ref) => reviewSessionRef.current?.describeComputerUseRef?.(ref),
3981
+ });
3899
3982
  const siteNotes = browserSiteNotes(req.toolCalls, browserControlRef.current?.status());
3900
3983
  return new Promise((resolve) => {
3901
3984
  if (permissionResolveRef.current) {
@@ -3951,6 +4034,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
3951
4034
  ...question.options.map((option) => ({
3952
4035
  label: option.label,
3953
4036
  description: option.description ?? '',
4037
+ // A badge, not label text: the SDK already took any
4038
+ // "(Recommended)" the model wrote out of the label.
4039
+ ...(option.recommended === true ? { recommended: true } : {}),
3954
4040
  })),
3955
4041
  ...(question.allowFreeText
3956
4042
  ? [{ label: 'Something else…', description: 'Answer in your own words' }]
@@ -4423,7 +4509,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4423
4509
  if (closing)
4424
4510
  pushMessage('system', closing, false, '✻', undefined, theme.text.muted);
4425
4511
  }
4426
- offerSkillSuggestion(st);
4512
+ // A save the operator asked for in words takes the place of
4513
+ // the proposal to save it.
4514
+ if (!triggerFollowUpsRef.current?.(st, st.completed ? 'completed' : 'stopped'))
4515
+ offerSkillSuggestion(st);
4427
4516
  break;
4428
4517
  }
4429
4518
  case 'paused':
@@ -4435,6 +4524,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4435
4524
  st.outcome = 'stopped';
4436
4525
  st.queuePauseOutcome = 'paused';
4437
4526
  st.notification = { kind: 'turn-settled', outcome: 'stopped' };
4527
+ triggerFollowUpsRef.current?.(st, 'paused');
4438
4528
  if (event.handoff) {
4439
4529
  // Nothing failed and nothing needs approving: a tool needs the
4440
4530
  // operator to do something first. Continuing is one key.
@@ -4455,6 +4545,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4455
4545
  case 'error':
4456
4546
  closeAssistant();
4457
4547
  st.outcome = event.message === 'aborted' ? 'cancelled' : 'failed';
4548
+ triggerFollowUpsRef.current?.(st, st.outcome);
4458
4549
  if (event.message !== 'aborted') {
4459
4550
  st.notification = { kind: 'turn-settled', outcome: 'failed' };
4460
4551
  pushMessage('system', describeTurnInterruption(event), false, '!');
@@ -4571,11 +4662,29 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4571
4662
  // Reserved before the turn begins, so everything that refers to this
4572
4663
  // turn — its goal-round authority above all — names it from the start.
4573
4664
  const turnId = generateTurnId();
4665
+ // The composer triggers the operator armed for this message, checked
4666
+ // again now: the mode or the session may have changed while it waited
4667
+ // in the queue. None of them changes the operator's words.
4668
+ const requestedTriggers = prompt.kind === 'human' ? (prompt.triggers ?? []) : [];
4669
+ const triggerContext = triggerContextNow();
4670
+ const turnTriggers = requestedTriggers.filter((id) => triggerUnavailable(id, triggerContext) === undefined);
4671
+ const pinnedEffort = triggerEffort(turnTriggers, session.reasoningEffortLevels);
4574
4672
  if (goalRound) {
4575
4673
  pushMessage('system', `Goal round ${goalRound.round} / ${goalRound.maxGoalRounds}`, false, '◎', [`Objective: ${goalRound.objective}`]);
4576
4674
  }
4577
4675
  else {
4578
4676
  pushMessage('user', text, false, undefined, undefined, undefined, humanPromptMeta(attached.length, attachments));
4677
+ const tagLine = transcriptTagLine(turnTriggers, {
4678
+ effort: pinnedEffort,
4679
+ steered: false,
4680
+ });
4681
+ if (tagLine)
4682
+ pushMessage('system', tagLine, false, '✦', undefined, theme.accent.trigger);
4683
+ for (const id of requestedTriggers) {
4684
+ const reason = triggerUnavailable(id, triggerContext);
4685
+ if (reason)
4686
+ pushMessage('system', `${triggerDefinition(id).label} · not applied: ${reason}`, false, '✧', undefined, theme.text.muted);
4687
+ }
4579
4688
  }
4580
4689
  // A plan is scoped to the request it was made for. Cleared here, at
4581
4690
  // the start of the next one, rather than when the previous turn
@@ -4607,6 +4716,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4607
4716
  startedInPlanMode: permissionModeRef.current === 'plan',
4608
4717
  }
4609
4718
  : {}),
4719
+ followUps: turnTriggers.filter((id) => triggerDefinition(id).scope === 'after-turn'),
4720
+ contextTexts: triggerContextTexts(turnTriggers),
4610
4721
  };
4611
4722
  liveStreamRef.current = st;
4612
4723
  // A skill activated with /skills <name> stays in the prompt of every
@@ -4782,7 +4893,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4782
4893
  activeTurnInboxRef.current = inbox;
4783
4894
  const turnPermissionMode = permissionModeRef.current;
4784
4895
  const turnReasoningEffort = reasoningEffortRef.current;
4785
- const turnOrchestrateMode = orchestrateModeRef.current;
4896
+ const turnHypermode = hypermodeRef.current;
4786
4897
  const turnLimits = resolveTurnGuards(ctxRef.current.limits, turnLimitsOverrideRef.current);
4787
4898
  // Always carry the guarded callback. `auto` and `strict` decide before
4788
4899
  // calling it in makeResumeHandler; retaining it is what lets a session
@@ -4830,8 +4941,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
4830
4941
  // while this turn runs, and the change governs what follows.
4831
4942
  currentPermissionMode: () => permissionModeRef.current,
4832
4943
  limits: turnLimits,
4833
- ...(turnReasoningEffort !== undefined ? { effort: turnReasoningEffort } : {}),
4834
- ...(turnOrchestrateMode ? { orchestrate: true } : {}),
4944
+ // A trigger that pins the highest effort does so for this
4945
+ // turn only; the session's own setting is untouched.
4946
+ ...((pinnedEffort ?? turnReasoningEffort) !== undefined
4947
+ ? { effort: pinnedEffort ?? turnReasoningEffort }
4948
+ : {}),
4949
+ ...(turnHypermode ? { hypermode: true } : {}),
4950
+ // Read at every iteration, so an after-turn trigger a steer
4951
+ // binds to this turn is said from the next one on.
4952
+ hostContext: () => st.contextTexts ?? [],
4835
4953
  ...(goalRound ? { goalRound } : {}),
4836
4954
  // The mode above decides whether this callback is consulted.
4837
4955
  onPermission: askPermission,
@@ -5045,6 +5163,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5045
5163
  sendTerminalNotification,
5046
5164
  session,
5047
5165
  setQueuePause,
5166
+ triggerContextNow,
5048
5167
  wakeGoalDriver,
5049
5168
  ]);
5050
5169
  const selectModelIntent = useCallback(async (query) => {
@@ -5166,7 +5285,31 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5166
5285
  pushMessage('system', `Could not save memory: ${err instanceof Error ? err.message : String(err)}`);
5167
5286
  }
5168
5287
  }, [ctx.cwd, pushMessage, session]);
5169
- const handleSubmit = useCallback((value, attachments, mode = 'submit') => {
5288
+ /**
5289
+ * Enter steered a message into the running turn. Its after-turn triggers
5290
+ * (save as skill) bind to that turn and are told to the model from its next
5291
+ * iteration; its turn triggers do not apply to a turn already running —
5292
+ * the tag row said so before Enter, and Tab would have queued it instead.
5293
+ */
5294
+ const bindSteeredTriggers = useCallback((ids) => {
5295
+ if (ids.length === 0)
5296
+ return;
5297
+ const live = liveStreamRef.current;
5298
+ const afterTurn = ids.filter((id) => triggerDefinition(id).scope === 'after-turn');
5299
+ const turnOnly = ids.filter((id) => triggerDefinition(id).scope === 'turn');
5300
+ if (live && afterTurn.length > 0) {
5301
+ live.followUps = [...new Set([...(live.followUps ?? []), ...afterTurn])];
5302
+ live.contextTexts = [
5303
+ ...new Set([...(live.contextTexts ?? []), ...triggerContextTexts(afterTurn)]),
5304
+ ];
5305
+ const line = transcriptTagLine(afterTurn, { effort: undefined, steered: true });
5306
+ if (line)
5307
+ pushMessage('system', line, false, '✦', undefined, theme.accent.trigger);
5308
+ }
5309
+ if (turnOnly.length > 0)
5310
+ pushMessage('system', `${turnOnly.map((id) => triggerDefinition(id).label).join(', ')} · not applied: Enter steered this message into the running turn. Tab queues a message as a new turn with it.`, false, '✧', undefined, theme.text.muted);
5311
+ }, [pushMessage]);
5312
+ const handleSubmit = useCallback((value, attachments, mode, meta) => {
5170
5313
  if (goalCommandInFlightRef.current) {
5171
5314
  pushMessage('system', 'A goal command is still reaching durable session state. Wait for its result before sending another command or prompt.');
5172
5315
  return;
@@ -5190,8 +5333,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5190
5333
  }
5191
5334
  // The operator moved on; a parked turn is still there for /resume.
5192
5335
  setHandoffPark(null);
5193
- setHistory((prev) => [...prev, value]);
5194
- const selectionIntent = !attachments?.length ? parseModelSelectionIntent(value) : undefined;
5336
+ // A loop is not something the operator typed here, so it is not a line
5337
+ // Up should bring back.
5338
+ const fromLoop = meta.source === 'operator-loop' || meta.source === 'model-loop';
5339
+ if (!fromLoop)
5340
+ setHistory((prev) => [...prev, value]);
5341
+ // What the model wrote is never the operator acting. A loop the model
5342
+ // made (`session_loop`) fires this path on a timer, for up to a week,
5343
+ // after one review of its creation — so its text must not reach the
5344
+ // host-side meanings of `/`, `!`, `#` or a model switch: `!` runs on
5345
+ // the host outside the sandbox with no review, and `#` writes memory
5346
+ // every later turn reads. It is sent as the prompt it looks like.
5347
+ const operatorText = meta.source !== 'model-loop';
5348
+ const selectionIntent = operatorText && !attachments?.length ? parseModelSelectionIntent(value) : undefined;
5195
5349
  if (selectionIntent) {
5196
5350
  void selectModelIntent(selectionIntent.query);
5197
5351
  return;
@@ -5199,12 +5353,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5199
5353
  // `#` remembers, `!` runs — neither is a prompt. Both are the
5200
5354
  // operator acting directly, the way other coding agents spell it,
5201
5355
  // and both leave a row the model reads on its next turn.
5202
- if (value.startsWith('#') && value.slice(1).trim().length > 0) {
5356
+ if (operatorText && value.startsWith('#') && value.slice(1).trim().length > 0) {
5203
5357
  const note = value.slice(1).trim();
5204
5358
  void rememberProjectNote(note);
5205
5359
  return;
5206
5360
  }
5207
- const escaped = shellEscapeCommand(value);
5361
+ const escaped = operatorText ? shellEscapeCommand(value) : null;
5208
5362
  if (escaped !== null) {
5209
5363
  pushMessage('user', value);
5210
5364
  const rowId = pushMessage('tool', `! ${escaped}`, true, '…');
@@ -5225,13 +5379,33 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5225
5379
  });
5226
5380
  return;
5227
5381
  }
5382
+ // Composer triggers (`./triggers/`). Only the composer's own submit
5383
+ // carries them, so nothing a loop, a picker or the model sends can
5384
+ // arm one. A message that is nothing but a trigger with a command of
5385
+ // its own (`bunu skill olarak kaydet`) runs that command here, in this
5386
+ // same call, so history holds the operator's words once; a trigger
5387
+ // that needs a task and got none is not applied, and says so.
5388
+ let armedTriggers = meta.source === 'composer' ? meta.triggers.map((trigger) => trigger.id) : [];
5389
+ let dispatch = value;
5390
+ if (meta.source === 'composer' && meta.onlyTriggers && armedTriggers.length > 0) {
5391
+ const command = armedTriggers
5392
+ .map((id) => triggerDefinition(id).standalone)
5393
+ .find((standalone) => standalone !== undefined);
5394
+ const needTask = armedTriggers.filter((id) => triggerDefinition(id).standalone === undefined);
5395
+ if (needTask.length > 0)
5396
+ pushMessage('system', `${needTask.map((id) => triggerDefinition(id).label).join(', ')} · not applied: the message has no task. Put the task in the same message${needTask.includes('hypermode') ? ', e.g. “hypermode fix the flaky test”' : ''}.`, false, '✧', undefined, theme.text.muted);
5397
+ armedTriggers = [];
5398
+ if (!command)
5399
+ return;
5400
+ dispatch = command;
5401
+ }
5228
5402
  // What actually gets sent. A `prompt` action replaces it with text the
5229
5403
  // command composed, and then takes the ordinary send path below —
5230
5404
  // including the queue — so a command-driven turn is not a second way
5231
5405
  // to run one.
5232
- let outgoing = value;
5406
+ let outgoing = dispatch;
5233
5407
  let skillFlow = false;
5234
- const slash = runSlash(value, slashCtx, hostCommands);
5408
+ const slash = operatorText ? runSlash(dispatch, slashCtx, hostCommands) : null;
5235
5409
  if (slash) {
5236
5410
  switch (slash.kind) {
5237
5411
  case 'message':
@@ -5370,6 +5544,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5370
5544
  description: `${session?.webSearchSummary ?? 'Not resolved'} · view tools, workspace and usage.`,
5371
5545
  },
5372
5546
  { name: 'setup', label: 'Provider setup', description: 'Check installed CLIs and access; connect or install without leaving Namzu.' },
5547
+ {
5548
+ name: `config triggers ${composerTriggersOn ? 'off' : 'on'}`,
5549
+ label: 'Composer triggers',
5550
+ description: composerTriggersOn
5551
+ ? 'On · hypermode and save-as-skill words act for their message. Select to turn off.'
5552
+ : 'Off · words in a message are only words. Select to turn on.',
5553
+ },
5373
5554
  {
5374
5555
  name: 'config sources',
5375
5556
  label: 'Setting sources',
@@ -5387,7 +5568,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5387
5568
  description: command.description,
5388
5569
  disabledReason: command.problem,
5389
5570
  })),
5390
- windowSize: 7,
5571
+ windowSize: 8,
5391
5572
  });
5392
5573
  return;
5393
5574
  }
@@ -5621,7 +5802,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
5621
5802
  const values = [undefined, ...levels];
5622
5803
  const current = reasoningEffortRef.current;
5623
5804
  setSelectedChoice(Math.max(0, values.findIndex((value) => value === current)));
5624
- setChoicePicker(reasoningEffortPicker(session, current, false, orchestrateModeRef.current) ?? null);
5805
+ setChoicePicker(reasoningEffortPicker(session, current, false, hypermodeRef.current) ?? null);
5625
5806
  return;
5626
5807
  }
5627
5808
  case 'login':
@@ -6143,9 +6324,25 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6143
6324
  : 'Raw output mode off — rich transcript rendering restored.');
6144
6325
  return;
6145
6326
  }
6146
- case 'orchestrate-mode': {
6147
- const enabled = slash.enabled === 'toggle' ? !orchestrateModeRef.current : slash.enabled;
6148
- applyOrchestrateMode(enabled);
6327
+ case 'composer-triggers': {
6328
+ const profile = ctx.configDebug?.selectedProfile?.name;
6329
+ const cascade = { cwd: ctx.cwd, ...(profile ? { profile } : {}) };
6330
+ if (slash.setting === 'list') {
6331
+ pushMessage('system', describeComposerTriggers(composerTriggersOn, cascade));
6332
+ return;
6333
+ }
6334
+ const on = slash.setting === 'on';
6335
+ // The session follows the choice whatever the files say; the
6336
+ // message says whether a later start will too.
6337
+ setComposerTriggersOverride(on);
6338
+ pushMessage('system', setComposerTriggers(on, cascade));
6339
+ return;
6340
+ }
6341
+ case 'hypermode': {
6342
+ if (slash.via === 'orchestrate')
6343
+ pushMessage('system', ORCHESTRATE_ALIAS_NOTICE);
6344
+ const enabled = slash.enabled === 'toggle' ? !hypermodeRef.current : slash.enabled;
6345
+ applyHypermode(enabled);
6149
6346
  return;
6150
6347
  }
6151
6348
  case 'export-picker': {
@@ -6215,6 +6412,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6215
6412
  text: outgoing,
6216
6413
  ...(attachments && attachments.length > 0 ? { attachments: [...attachments] } : {}),
6217
6414
  ...(skillFlow ? { skillFlow: true } : {}),
6415
+ ...(armedTriggers.length > 0 && !skillFlow ? { triggers: armedTriggers } : {}),
6218
6416
  };
6219
6417
  // `/skills save` and `/skills new` are turns of their own: steered into
6220
6418
  // a running turn, they would lose the flag that keeps the skill-making
@@ -6232,8 +6430,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6232
6430
  message: createUserMessage(expanded.sendText, prompt.attachments),
6233
6431
  attachedFiles: expanded.attached.length,
6234
6432
  queueBoundary: queuedRef.current.length,
6235
- }))
6433
+ })) {
6434
+ bindSteeredTriggers(armedTriggers);
6236
6435
  return;
6436
+ }
6237
6437
  }
6238
6438
  }
6239
6439
  enqueueQueued(prompt);
@@ -6241,6 +6441,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6241
6441
  activeSkills,
6242
6442
  advanceQueueContinuation,
6243
6443
  applyPermissionMode,
6444
+ bindSteeredTriggers,
6445
+ composerTriggersOn,
6244
6446
  conversationLearning,
6245
6447
  applyReasoningEffort,
6246
6448
  appLifetime,
@@ -6277,7 +6479,48 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6277
6479
  // picker is also used by review/export flows. Keep only this dispatch hop in
6278
6480
  // a ref so selecting a help row re-enters the one ordinary slash-command
6279
6481
  // path instead of growing a second command executor.
6280
- commandPickerSubmitRef.current = handleSubmit;
6482
+ commandPickerSubmitRef.current = (command) => handleSubmit(command, undefined, 'submit', { source: 'command-picker' });
6483
+ // A turn's after-turn triggers, when it ends. Save as skill runs exactly the
6484
+ // prompt `/skills save` sends, at the front of the queue, only when the
6485
+ // turn completed, did tool work, did not already save a skill, and saving
6486
+ // is still possible now. Otherwise one row says why, and `/skills save`
6487
+ // still works. Returns whether the turn had one, so the proposal to save
6488
+ // it is not also printed.
6489
+ triggerFollowUpsRef.current = (st, ending) => {
6490
+ const followUps = st.followUps ?? [];
6491
+ st.followUps = [];
6492
+ if (!followUps.includes('save-skill'))
6493
+ return false;
6494
+ const reason = ending === 'cancelled'
6495
+ ? 'the turn was cancelled'
6496
+ : ending === 'failed'
6497
+ ? 'the turn failed'
6498
+ : ending === 'paused'
6499
+ ? 'the turn paused'
6500
+ : ending === 'stopped'
6501
+ ? 'the turn stopped before it finished'
6502
+ : !st.learning || st.learning.steps < 1
6503
+ ? 'the turn did no tool work'
6504
+ : st.learning.savedSkill
6505
+ ? 'the turn already saved a skill'
6506
+ : triggerUnavailable('save-skill', triggerContextNow());
6507
+ if (reason) {
6508
+ pushMessage('system', `save as skill · not run: ${reason} · /skills save runs it anyway`, false, '✧', undefined, theme.text.muted);
6509
+ return true;
6510
+ }
6511
+ // The proposal was taken up, as `/skills save` records it.
6512
+ conversationLearning().proposed = true;
6513
+ const home = resolveNamzuHome();
6514
+ const ledger = readSuggestionLedger(home);
6515
+ if (ledger.unanswered > 0)
6516
+ writeSuggestionLedger(home, { ...ledger, unanswered: 0 });
6517
+ replaceQueued([
6518
+ { kind: 'human', text: learnSkillPrompt(), skillFlow: true },
6519
+ ...queuedRef.current,
6520
+ ]);
6521
+ pushMessage('system', 'save as skill · running /skills save for this turn', false, '✦', undefined, theme.accent.trigger);
6522
+ return true;
6523
+ };
6281
6524
  saveSkillNotifyRef.current = () => sendTerminalNotification({ kind: 'approval-required' });
6282
6525
  scheduleLiveRef.current = {
6283
6526
  ...(scheduleLiveRef.current?.model ? { model: scheduleLiveRef.current.model } : {}),
@@ -6287,7 +6530,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6287
6530
  },
6288
6531
  ask: askQuestion,
6289
6532
  askPermission: onPermission,
6290
- submit: (text) => handleSubmit(text),
6533
+ submit: (text, createdBy) => handleSubmit(text, undefined, 'submit', {
6534
+ source: createdBy === 'operator' ? 'operator-loop' : 'model-loop',
6535
+ }),
6291
6536
  };
6292
6537
  // Scheduled work since the TUI last looked: one line, after the first
6293
6538
  // paint, never blocking it. Loops fire when a turn ends, and on their timer.
@@ -6738,7 +6983,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6738
6983
  }
6739
6984
  if (permission && (key.upArrow || key.downArrow)) {
6740
6985
  const current = permissionChoiceRef.current;
6741
- const last = permissionAnswers(permission.toolCalls, { batchOnly: permission.batchOnly === true }).length - 1;
6986
+ const last = permissionAnswers(permission.toolCalls, {
6987
+ batchOnly: permission.batchOnly === true,
6988
+ screenConsent: permission.screenConsent === true,
6989
+ }).length - 1;
6742
6990
  const next = key.upArrow ? Math.max(0, current - 1) : Math.min(last, current + 1);
6743
6991
  setPermissionChoice(next);
6744
6992
  return;
@@ -6783,6 +7031,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
6783
7031
  // neither its `a` nor a `3` answers it.
6784
7032
  const answers = permissionAnswers(permission?.toolCalls ?? [], {
6785
7033
  batchOnly: permission?.batchOnly === true,
7034
+ screenConsent: permission?.screenConsent === true,
6786
7035
  });
6787
7036
  const numbered = /^[1-9]$/.test(ch) ? Number(ch) - 1 : null;
6788
7037
  const chosen = key.return ? permissionChoiceRef.current : numbered;
@@ -7278,6 +7527,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
7278
7527
  : `Session: ${permissionSource}`
7279
7528
  : undefined;
7280
7529
  const displayedPermissionMode = effectivePermissionMode(permissionMode, session?.approvalLatched() ?? false);
7530
+ const highestEffort = highestReasoningEffort(session?.reasoningEffortLevels);
7531
+ const hypermodeLevel = hypermodeEffort(session?.reasoningEffortLevels);
7532
+ const composerTriggers = composerTriggersOn
7533
+ ? {
7534
+ registry: composerTriggerRegistry,
7535
+ suggest: composerTriggerSettings.suggest,
7536
+ context: {
7537
+ permissionMode: displayedPermissionMode,
7538
+ sessionHypermode: hypermode,
7539
+ effortMenu: highestEffort !== undefined,
7540
+ agentTool: true,
7541
+ skillCreator: skillCreatorAvailable,
7542
+ scheduleTool: true,
7543
+ },
7544
+ ...(highestEffort ? { highestEffort } : {}),
7545
+ ...(hypermodeLevel ? { hypermodeEffort: hypermodeLevel } : {}),
7546
+ }
7547
+ : undefined;
7281
7548
  // True exactly when the phase ternary below renders its final branch — the
7282
7549
  // normal composer view, as opposed to provider setup or a lifecycle
7283
7550
  // picker/prompt. The agent rail is a feature of that view: computing this
@@ -7312,11 +7579,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
7312
7579
  saveSkillPrompt === null, thinking: thinking, tokens: turnTokens, waitingForYou: choicePicker !== null ||
7313
7580
  permission !== null ||
7314
7581
  textPrompt !== null ||
7315
- saveSkillPrompt !== null })) : null, agentSurface === null && outputViewer === null && permission === null ? (_jsx(TaskList, { tasks: liveTasks })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, choice: permissionChoice, queuedCount: queuedPermissionCount, sourceLabel: permissionSourceLabel, columns: terminal.columns, rows: terminal.rows, batchOnly: permission.batchOnly === true, siteNotes: permission.siteNotes })) : null, saveSkillPrompt && permission === null ? (_jsx(SaveSkillOverlay, { request: saveSkillPrompt.request, cwd: ctx.cwd, columns: Math.max(1, (terminal.columns ?? 80) - 2), rows: terminal.rows, onAnswer: saveSkillPrompt.resolve }, saveSkillPrompt.request.markdown)) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null &&
7582
+ saveSkillPrompt !== null })) : null, agentSurface === null && outputViewer === null && permission === null ? (_jsx(TaskList, { tasks: liveTasks })) : null, permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls, review: permission.review, summary: permission.summary, detailsOpen: permissionDetailsOpen, reviewOffset: permissionReviewOffset, choice: permissionChoice, queuedCount: queuedPermissionCount, sourceLabel: permissionSourceLabel, columns: terminal.columns, rows: terminal.rows, batchOnly: permission.batchOnly === true, siteNotes: permission.siteNotes, ...(permission.screenConsent === true
7583
+ ? { screenConsent: { provider: session?.providerSummary ?? null } }
7584
+ : {}) })) : null, saveSkillPrompt && permission === null ? (_jsx(SaveSkillOverlay, { request: saveSkillPrompt.request, cwd: ctx.cwd, columns: Math.max(1, (terminal.columns ?? 80) - 2), rows: terminal.rows, onAnswer: saveSkillPrompt.resolve }, saveSkillPrompt.request.markdown)) : null, textPrompt ? (_jsx(TextPrompt, { columns: Math.max(1, (terminal.columns ?? 80) - 2), title: textPrompt.title, placeholder: textPrompt.placeholder, initialValue: textPrompt.initialValue, emptyNotice: textPrompt.emptyNotice, hidden: permission !== null || (agentSurface !== null || outputViewer !== null), onSubmit: submitTextPrompt, onCancel: cancelTextPrompt }, textPrompt.token)) : permission === null &&
7316
7585
  agentSurface === null &&
7317
7586
  outputViewer === null &&
7318
7587
  choicePicker?.kind === 'reasoning-effort' &&
7319
- effortSlider ? (_jsx(EffortSlider, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, layout: effortSlider, columns: Math.max(1, (terminal.columns ?? 80) - 2), highest: choicePicker.options.at(-2)?.label === 'default' ? undefined : choicePicker.options.at(-2)?.label })) : permission === null && agentSurface === null && outputViewer === null && choicePicker ? (_jsx(ChoicePicker, { busy: 'busy' in choicePicker && choicePicker.busy === true, columns: Math.max(1, (terminal.columns ?? 80) - 2), title: choicePicker.title, notice: choicePicker.notice, options: filterChoiceOptions(choicePicker.options, choiceQuery), query: choicePickerSearchable(choicePicker) ? choiceQuery : undefined, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && outputViewer === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { working: state === 'thinking' || state === 'tool' || visibleActiveTools.length > 0, ...(orchestrateMode ? { mode: 'orchestrate' } : {}), focus: phase === 'ready' &&
7588
+ effortSlider ? (_jsx(EffortSlider, { title: choicePicker.title, notice: choicePicker.notice, options: choicePicker.options, selected: selectedChoice, layout: effortSlider, columns: Math.max(1, (terminal.columns ?? 80) - 2) })) : permission === null && agentSurface === null && outputViewer === null && choicePicker ? (_jsx(ChoicePicker, { busy: 'busy' in choicePicker && choicePicker.busy === true, columns: Math.max(1, (terminal.columns ?? 80) - 2), title: choicePicker.title, notice: choicePicker.notice, options: filterChoiceOptions(choicePicker.options, choiceQuery), query: choicePickerSearchable(choicePicker) ? choiceQuery : undefined, selected: selectedChoice, windowSize: choicePicker.kind === 'command' ? choicePicker.windowSize : undefined })) : permission === null && agentSurface === null && outputViewer === null && copyPicker ? (_jsx(CopyPicker, { targets: copyPicker.targets, selected: selectedCopy })) : null, _jsxs(ComposerFrame, { working: state === 'thinking' || state === 'tool' || visibleActiveTools.length > 0, ...(hypermode ? { mode: HYPERMODE } : {}), focus: phase === 'ready' &&
7320
7589
  state !== 'awaiting-permission' &&
7321
7590
  !compacting &&
7322
7591
  externalEditorRequest === null &&
@@ -7344,7 +7613,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
7344
7613
  ? queuePause.outcome === 'paused'
7345
7614
  ? 'held after a resumable turn paused; wait for recovery, change model, or send a message to release it'
7346
7615
  : `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
7347
- : 'sending when ready'] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
7616
+ : 'sending when ready', queuedTriggerLabels(queued)] }) })) : null, _jsx(Composer, { reasoningEffortLevels: session?.reasoningEffortLevels, onCycleMode: cyclePermissionMode, disabled: outputViewer !== null ||
7348
7617
  phase !== 'ready' ||
7349
7618
  state === 'awaiting-permission' ||
7350
7619
  compacting ||
@@ -7365,7 +7634,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
7365
7634
  (handoffPark !== null && state === 'idle'), onSubmit: handleSubmit, onNotice: (text) => pushMessage('system', text), onStepReasoningEffort: stepReasoningEffort, onExternalEdit: requestExternalEditor, onEditPrevious: openPromptEditor, draftToRestore: composerDraft, onDraftRestored: (token) => setComposerDraft((draft) => (draft?.token === token ? null : draft)), onDraftPresenceChange: setComposerHasDraft, onOpenAgentPanel: openAgentCockpit, userCommands: userCommands, builtins: hostCommands.map((command) => ({
7366
7635
  ...command,
7367
7636
  description: command.unavailable?.(slashCtx) ?? command.description,
7368
- })), mentionCandidates: mentionCandidates, history: history })] })] })), _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort, orchestrate: orchestrateMode, goal: statusGoal, state: state, hint: statusHint, permissionMode: displayedPermissionMode, canCycleMode: true }), showComposerSurface &&
7637
+ })), mentionCandidates: mentionCandidates, history: history, ...(composerTriggers ? { triggers: composerTriggers } : {}), turnActive: !compacting && (state === 'thinking' || state === 'tool') })] })] })), _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, effort: reasoningEffort, hypermode: hypermode, goal: statusGoal, state: state, hint: statusHint, permissionMode: displayedPermissionMode, canCycleMode: true }), showComposerSurface &&
7369
7638
  permission === null &&
7370
7639
  agentSurface === null &&
7371
7640
  outputViewer === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
@@ -7580,25 +7849,36 @@ function permissionPicker(mode, approvedAll, more = false) {
7580
7849
  };
7581
7850
  }
7582
7851
  /**
7583
- * The picker's sentinel for the orchestrate-mode row, below the rule.
7852
+ * The picker's sentinel for the hypermode row, below the rule.
7584
7853
  *
7585
7854
  * A plain string outside the `ReasoningEffort` union by construction — never
7586
7855
  * added to any model's published menu — so it can share the same picker
7587
7856
  * without ever being mistaken for a level `/effort <token>` could select.
7588
7857
  */
7589
- const ORCHESTRATE_MODE_VALUE = 'orchestrate';
7858
+ const HYPERMODE_VALUE = 'hypermode';
7859
+ /** ` · ✦ save as skill` when queued messages carry composer triggers, so the queue line says so. */
7860
+ function queuedTriggerLabels(queued) {
7861
+ const labels = new Set();
7862
+ for (const prompt of queued) {
7863
+ if (prompt.kind !== 'human')
7864
+ continue;
7865
+ for (const id of prompt.triggers ?? [])
7866
+ labels.add(triggerDefinition(id).label);
7867
+ }
7868
+ return labels.size > 0 ? ` · ✦ ${[...labels].join(', ')}` : '';
7869
+ }
7590
7870
  /** Last entry of a low-to-high menu every provider publishes in that order; `undefined` when none is known. */
7591
7871
  function highestReasoningEffort(levels) {
7592
7872
  return levels && levels.length > 0 ? levels[levels.length - 1] : undefined;
7593
7873
  }
7594
- function reasoningEffortPicker(session, current, afterModelSelection = false, orchestrateOn = false) {
7874
+ function reasoningEffortPicker(session, current, afterModelSelection = false, hypermodeOn = false) {
7595
7875
  const levels = session.reasoningEffortLevels;
7596
7876
  if (!session.hasProvider || levels === undefined)
7597
7877
  return undefined;
7598
7878
  const effortValues = [undefined, ...levels];
7599
7879
  const values = [
7600
7880
  ...effortValues,
7601
- ORCHESTRATE_MODE_VALUE,
7881
+ HYPERMODE_VALUE,
7602
7882
  ];
7603
7883
  return {
7604
7884
  kind: 'reasoning-effort',
@@ -7619,12 +7899,11 @@ function reasoningEffortPicker(session, current, afterModelSelection = false, or
7619
7899
  })),
7620
7900
  {
7621
7901
  // Below a rule, visually apart from the levels above: a session
7622
- // setting, not a sixth level the provider published.
7623
- label: 'orchestrate',
7624
- description: orchestrateOn
7625
- ? 'On · highest level and delegate by default'
7626
- : 'Off · highest level and delegate by default',
7627
- current: orchestrateOn,
7902
+ // setting, not a sixth level the provider published. The label
7903
+ // names the level it pins, then the mode.
7904
+ label: hypermodeStopLabel(hypermodeEffort(levels)),
7905
+ description: `${hypermodeOn ? 'On' : 'Off'} · ${HYPERMODE_SUMMARY}`,
7906
+ current: hypermodeOn,
7628
7907
  ruleBefore: true,
7629
7908
  },
7630
7909
  ],