@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.
- package/CHANGELOG.md +296 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +20 -2
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +42 -5
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +2 -1
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/exec-json.d.ts.map +1 -1
- package/dist/commands/exec-json.js +24 -1
- package/dist/commands/exec-json.js.map +1 -1
- package/dist/commands/exec.d.ts.map +1 -1
- package/dist/commands/exec.js +5 -1
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/prior-messages.d.ts.map +1 -1
- package/dist/commands/prior-messages.js +42 -2
- package/dist/commands/prior-messages.js.map +1 -1
- package/dist/commands/schedule.d.ts.map +1 -1
- package/dist/commands/schedule.js +7 -5
- package/dist/commands/schedule.js.map +1 -1
- package/dist/config/composer-triggers.d.ts +41 -0
- package/dist/config/composer-triggers.d.ts.map +1 -0
- package/dist/config/composer-triggers.js +80 -0
- package/dist/config/composer-triggers.js.map +1 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +58 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/doctrine.d.ts +3 -3
- package/dist/context/doctrine.d.ts.map +1 -1
- package/dist/context/doctrine.js +3 -3
- package/dist/context/doctrine.js.map +1 -1
- package/dist/context/environment.d.ts +26 -3
- package/dist/context/environment.d.ts.map +1 -1
- package/dist/context/environment.js +68 -9
- package/dist/context/environment.js.map +1 -1
- package/dist/context/runtime-message.d.ts.map +1 -1
- package/dist/context/runtime-message.js +4 -0
- package/dist/context/runtime-message.js.map +1 -1
- package/dist/integrations/mcp/servers.d.ts +72 -5
- package/dist/integrations/mcp/servers.d.ts.map +1 -1
- package/dist/integrations/mcp/servers.js +144 -23
- package/dist/integrations/mcp/servers.js.map +1 -1
- package/dist/integrations/notifications/desktop.d.ts +2 -0
- package/dist/integrations/notifications/desktop.d.ts.map +1 -1
- package/dist/integrations/notifications/desktop.js +6 -5
- package/dist/integrations/notifications/desktop.js.map +1 -1
- package/dist/integrations/plugins/runtime.d.ts +10 -2
- package/dist/integrations/plugins/runtime.d.ts.map +1 -1
- package/dist/integrations/plugins/runtime.js +16 -29
- package/dist/integrations/plugins/runtime.js.map +1 -1
- package/dist/integrations/providers/harness-credentials.d.ts +9 -4
- package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
- package/dist/integrations/providers/harness-credentials.js +16 -8
- package/dist/integrations/providers/harness-credentials.js.map +1 -1
- package/dist/integrations/resident/session-step.d.ts.map +1 -1
- package/dist/integrations/resident/session-step.js +2 -1
- package/dist/integrations/resident/session-step.js.map +1 -1
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +20 -2
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/subagents/NamzuCliAgent.d.ts +8 -0
- package/dist/integrations/subagents/NamzuCliAgent.d.ts.map +1 -0
- package/dist/integrations/subagents/NamzuCliAgent.js +9 -0
- package/dist/integrations/subagents/NamzuCliAgent.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +5 -4
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +46 -11
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/web/open-url.d.ts +24 -0
- package/dist/integrations/web/open-url.d.ts.map +1 -0
- package/dist/integrations/web/open-url.js +73 -0
- package/dist/integrations/web/open-url.js.map +1 -0
- package/dist/integrations/web/search.d.ts.map +1 -1
- package/dist/integrations/web/search.js +8 -6
- package/dist/integrations/web/search.js.map +1 -1
- package/dist/permissions/rules.d.ts +5 -1
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +85 -3
- package/dist/permissions/rules.js.map +1 -1
- package/dist/schedule/build.d.ts +26 -2
- package/dist/schedule/build.d.ts.map +1 -1
- package/dist/schedule/build.js +139 -20
- package/dist/schedule/build.js.map +1 -1
- package/dist/schedule/changes.d.ts +6 -0
- package/dist/schedule/changes.d.ts.map +1 -1
- package/dist/schedule/changes.js +23 -0
- package/dist/schedule/changes.js.map +1 -1
- package/dist/schedule/commands/add.d.ts +1 -1
- package/dist/schedule/commands/add.d.ts.map +1 -1
- package/dist/schedule/commands/add.js +188 -54
- package/dist/schedule/commands/add.js.map +1 -1
- package/dist/schedule/commands/lifecycle.d.ts.map +1 -1
- package/dist/schedule/commands/lifecycle.js +2 -1
- package/dist/schedule/commands/lifecycle.js.map +1 -1
- package/dist/schedule/commands/list.d.ts +2 -0
- package/dist/schedule/commands/list.d.ts.map +1 -1
- package/dist/schedule/commands/list.js +69 -20
- package/dist/schedule/commands/list.js.map +1 -1
- package/dist/schedule/daemon/daemon.d.ts +1 -1
- package/dist/schedule/daemon/daemon.d.ts.map +1 -1
- package/dist/schedule/daemon/daemon.js +22 -5
- package/dist/schedule/daemon/daemon.js.map +1 -1
- package/dist/schedule/daemon/notify.d.ts +29 -2
- package/dist/schedule/daemon/notify.d.ts.map +1 -1
- package/dist/schedule/daemon/notify.js +71 -2
- package/dist/schedule/daemon/notify.js.map +1 -1
- package/dist/schedule/env.d.ts +8 -4
- package/dist/schedule/env.d.ts.map +1 -1
- package/dist/schedule/env.js +13 -6
- package/dist/schedule/env.js.map +1 -1
- package/dist/schedule/fire/calls.d.ts +49 -0
- package/dist/schedule/fire/calls.d.ts.map +1 -0
- package/dist/schedule/fire/calls.js +114 -0
- package/dist/schedule/fire/calls.js.map +1 -0
- package/dist/schedule/fire/fire.d.ts +1 -1
- package/dist/schedule/fire/fire.d.ts.map +1 -1
- package/dist/schedule/fire/fire.js +209 -15
- package/dist/schedule/fire/fire.js.map +1 -1
- package/dist/schedule/fire/result.d.ts +1 -1
- package/dist/schedule/fire/result.d.ts.map +1 -1
- package/dist/schedule/fire/result.js +2 -1
- package/dist/schedule/fire/result.js.map +1 -1
- package/dist/schedule/fire/run-script.d.ts +46 -0
- package/dist/schedule/fire/run-script.d.ts.map +1 -0
- package/dist/schedule/fire/run-script.js +81 -0
- package/dist/schedule/fire/run-script.js.map +1 -0
- package/dist/schedule/fire/unattended-note.d.ts +8 -0
- package/dist/schedule/fire/unattended-note.d.ts.map +1 -1
- package/dist/schedule/fire/unattended-note.js +5 -0
- package/dist/schedule/fire/unattended-note.js.map +1 -1
- package/dist/schedule/fire/wake-gate.d.ts +39 -0
- package/dist/schedule/fire/wake-gate.d.ts.map +1 -0
- package/dist/schedule/fire/wake-gate.js +90 -0
- package/dist/schedule/fire/wake-gate.js.map +1 -0
- package/dist/schedule/floor.d.ts +28 -8
- package/dist/schedule/floor.d.ts.map +1 -1
- package/dist/schedule/floor.js +624 -114
- package/dist/schedule/floor.js.map +1 -1
- package/dist/schedule/policy.d.ts +40 -0
- package/dist/schedule/policy.d.ts.map +1 -1
- package/dist/schedule/policy.js +36 -0
- package/dist/schedule/policy.js.map +1 -1
- package/dist/schedule/script-check.d.ts +53 -0
- package/dist/schedule/script-check.d.ts.map +1 -0
- package/dist/schedule/script-check.js +145 -0
- package/dist/schedule/script-check.js.map +1 -0
- package/dist/schedule/script-shell.d.ts +3 -0
- package/dist/schedule/script-shell.d.ts.map +1 -0
- package/dist/schedule/script-shell.js +5 -0
- package/dist/schedule/script-shell.js.map +1 -0
- package/dist/schedule/service/index.d.ts +1 -1
- package/dist/schedule/service/index.d.ts.map +1 -1
- package/dist/schedule/service/index.js +20 -11
- package/dist/schedule/service/index.js.map +1 -1
- package/dist/schedule/service/manifest.js +1 -1
- package/dist/schedule/service/manifest.js.map +1 -1
- package/dist/schedule/service/windows-task.d.ts +7 -0
- package/dist/schedule/service/windows-task.d.ts.map +1 -1
- package/dist/schedule/service/windows-task.js +65 -0
- package/dist/schedule/service/windows-task.js.map +1 -1
- package/dist/schedule/service/wsl.d.ts +13 -1
- package/dist/schedule/service/wsl.d.ts.map +1 -1
- package/dist/schedule/service/wsl.js +16 -6
- package/dist/schedule/service/wsl.js.map +1 -1
- package/dist/schedule/store/atomic.d.ts +5 -2
- package/dist/schedule/store/atomic.d.ts.map +1 -1
- package/dist/schedule/store/atomic.js +7 -4
- package/dist/schedule/store/atomic.js.map +1 -1
- package/dist/schedule/store/claims.js +1 -1
- package/dist/schedule/store/claims.js.map +1 -1
- package/dist/schedule/store/history.d.ts +1 -1
- package/dist/schedule/store/history.d.ts.map +1 -1
- package/dist/schedule/store/history.js +4 -1
- package/dist/schedule/store/history.js.map +1 -1
- package/dist/schedule/store/jobs.d.ts +19 -3
- package/dist/schedule/store/jobs.d.ts.map +1 -1
- package/dist/schedule/store/jobs.js +83 -16
- package/dist/schedule/store/jobs.js.map +1 -1
- package/dist/schedule/store/state.js +1 -1
- package/dist/schedule/store/state.js.map +1 -1
- package/dist/schedule/types.d.ts +98 -6
- package/dist/schedule/types.d.ts.map +1 -1
- package/dist/schedule/types.js +25 -1
- package/dist/schedule/types.js.map +1 -1
- package/dist/schedule/visible-source.d.ts +18 -0
- package/dist/schedule/visible-source.d.ts.map +1 -0
- package/dist/schedule/visible-source.js +59 -0
- package/dist/schedule/visible-source.js.map +1 -0
- package/dist/skills/directory.d.ts +38 -0
- package/dist/skills/directory.d.ts.map +1 -0
- package/dist/skills/directory.js +51 -0
- package/dist/skills/directory.js.map +1 -0
- package/dist/test-support/toolset.d.ts +23 -0
- package/dist/test-support/toolset.d.ts.map +1 -0
- package/dist/test-support/toolset.js +27 -0
- package/dist/test-support/toolset.js.map +1 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +343 -64
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/ChoicePicker.d.ts +2 -0
- package/dist/tui/ChoicePicker.d.ts.map +1 -1
- package/dist/tui/ChoicePicker.js +10 -2
- package/dist/tui/ChoicePicker.js.map +1 -1
- package/dist/tui/Composer.d.ts +62 -3
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +162 -25
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/ComposerFrame.d.ts +1 -1
- package/dist/tui/ComposerFrame.js +3 -3
- package/dist/tui/ComposerFrame.js.map +1 -1
- package/dist/tui/EffortSlider.d.ts +8 -9
- package/dist/tui/EffortSlider.d.ts.map +1 -1
- package/dist/tui/EffortSlider.js +15 -12
- package/dist/tui/EffortSlider.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +13 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +26 -4
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/StatusBar.d.ts +12 -12
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/StatusBar.js +15 -15
- package/dist/tui/StatusBar.js.map +1 -1
- package/dist/tui/agent.d.ts +73 -12
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +430 -180
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/hypermode.d.ts +37 -0
- package/dist/tui/hypermode.d.ts.map +1 -0
- package/dist/tui/hypermode.js +58 -0
- package/dist/tui/hypermode.js.map +1 -0
- package/dist/tui/open-browser.d.ts +29 -1
- package/dist/tui/open-browser.d.ts.map +1 -1
- package/dist/tui/open-browser.js +83 -11
- package/dist/tui/open-browser.js.map +1 -1
- package/dist/tui/permission-review.d.ts +11 -2
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +176 -19
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/schedule/host-commands.d.ts.map +1 -1
- package/dist/tui/schedule/host-commands.js +95 -33
- package/dist/tui/schedule/host-commands.js.map +1 -1
- package/dist/tui/schedule/integration.d.ts +6 -2
- package/dist/tui/schedule/integration.d.ts.map +1 -1
- package/dist/tui/schedule/integration.js +3 -1
- package/dist/tui/schedule/integration.js.map +1 -1
- package/dist/tui/schedule/resume.d.ts.map +1 -1
- package/dist/tui/schedule/resume.js +10 -1
- package/dist/tui/schedule/resume.js.map +1 -1
- package/dist/tui/schedule/tool-host.d.ts +18 -1
- package/dist/tui/schedule/tool-host.d.ts.map +1 -1
- package/dist/tui/schedule/tool-host.js +384 -84
- package/dist/tui/schedule/tool-host.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +23 -5
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +80 -31
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/theme.d.ts +10 -4
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +4 -3
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/triggers/analyze.d.ts +57 -0
- package/dist/tui/triggers/analyze.d.ts.map +1 -0
- package/dist/tui/triggers/analyze.js +221 -0
- package/dist/tui/triggers/analyze.js.map +1 -0
- package/dist/tui/triggers/context-text.d.ts +21 -0
- package/dist/tui/triggers/context-text.d.ts.map +1 -0
- package/dist/tui/triggers/context-text.js +44 -0
- package/dist/tui/triggers/context-text.js.map +1 -0
- package/dist/tui/triggers/copy.d.ts +38 -0
- package/dist/tui/triggers/copy.d.ts.map +1 -0
- package/dist/tui/triggers/copy.js +163 -0
- package/dist/tui/triggers/copy.js.map +1 -0
- package/dist/tui/triggers/detect.d.ts +83 -0
- package/dist/tui/triggers/detect.d.ts.map +1 -0
- package/dist/tui/triggers/detect.js +295 -0
- package/dist/tui/triggers/detect.js.map +1 -0
- package/dist/tui/triggers/fold.d.ts +34 -0
- package/dist/tui/triggers/fold.d.ts.map +1 -0
- package/dist/tui/triggers/fold.js +44 -0
- package/dist/tui/triggers/fold.js.map +1 -0
- package/dist/tui/triggers/pattern.d.ts +66 -0
- package/dist/tui/triggers/pattern.d.ts.map +1 -0
- package/dist/tui/triggers/pattern.js +243 -0
- package/dist/tui/triggers/pattern.js.map +1 -0
- package/dist/tui/triggers/provenance.d.ts +58 -0
- package/dist/tui/triggers/provenance.d.ts.map +1 -0
- package/dist/tui/triggers/provenance.js +98 -0
- package/dist/tui/triggers/provenance.js.map +1 -0
- package/dist/tui/triggers/registry.d.ts +113 -0
- package/dist/tui/triggers/registry.d.ts.map +1 -0
- package/dist/tui/triggers/registry.js +164 -0
- package/dist/tui/triggers/registry.js.map +1 -0
- package/dist/tui/triggers/setting.d.ts +18 -0
- package/dist/tui/triggers/setting.d.ts.map +1 -0
- package/dist/tui/triggers/setting.js +61 -0
- package/dist/tui/triggers/setting.js.map +1 -0
- package/dist/tui/triggers/verbs.d.ts +29 -0
- package/dist/tui/triggers/verbs.d.ts.map +1 -0
- package/dist/tui/triggers/verbs.js +140 -0
- package/dist/tui/triggers/verbs.js.map +1 -0
- package/dist/tui/types.d.ts +3 -1
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +12 -12
- package/skills/README.md +5 -0
- 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 [
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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', '
|
|
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', '
|
|
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
|
-
|
|
1700
|
+
setHypermode(enabled);
|
|
1641
1701
|
if (!enabled) {
|
|
1642
|
-
|
|
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
|
|
1646
|
-
if (
|
|
1647
|
-
|
|
1648
|
-
|
|
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', `
|
|
1652
|
-
}, [hasUnsettledTurn, pushMessage, session,
|
|
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 ===
|
|
2215
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
4834
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5194
|
-
|
|
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 =
|
|
5406
|
+
let outgoing = dispatch;
|
|
5233
5407
|
let skillFlow = false;
|
|
5234
|
-
const slash = runSlash(
|
|
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:
|
|
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,
|
|
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 '
|
|
6147
|
-
const
|
|
6148
|
-
|
|
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, {
|
|
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
|
|
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)
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
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
|
],
|