@namzu/cli 30.0.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 +217 -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 +2 -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/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/plugins/runtime.d.ts +11 -9
- package/dist/integrations/plugins/runtime.d.ts.map +1 -1
- package/dist/integrations/plugins/runtime.js +17 -36
- package/dist/integrations/plugins/runtime.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/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 +19 -2
- package/dist/schedule/build.d.ts.map +1 -1
- package/dist/schedule/build.js +119 -21
- package/dist/schedule/build.js.map +1 -1
- package/dist/schedule/changes.d.ts.map +1 -1
- package/dist/schedule/changes.js +13 -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 +186 -40
- package/dist/schedule/commands/add.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 +64 -19
- 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 +17 -5
- package/dist/schedule/daemon/daemon.js.map +1 -1
- package/dist/schedule/daemon/notify.d.ts +21 -1
- package/dist/schedule/daemon/notify.d.ts.map +1 -1
- package/dist/schedule/daemon/notify.js +43 -0
- package/dist/schedule/daemon/notify.js.map +1 -1
- 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 +200 -14
- 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 +1 -1
- package/dist/schedule/floor.d.ts.map +1 -1
- package/dist/schedule/floor.js +288 -16
- 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/manifest.js +1 -1
- package/dist/schedule/service/manifest.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 +71 -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/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 +323 -60
- 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/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 +46 -7
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +386 -178
- 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/permission-review.d.ts +1 -1
- package/dist/tui/permission-review.js +1 -1
- package/dist/tui/schedule/host-commands.d.ts.map +1 -1
- package/dist/tui/schedule/host-commands.js +94 -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 +4 -1
- package/dist/tui/schedule/tool-host.d.ts.map +1 -1
- package/dist/tui/schedule/tool-host.js +155 -40
- 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/schedule-task/SKILL.md +115 -3
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
|
|
@@ -983,7 +990,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
983
990
|
ask: (question, signal) => scheduleLiveRef.current?.ask(question, signal) ?? Promise.resolve({ kind: 'skip' }),
|
|
984
991
|
askPermission: (request) => scheduleLiveRef.current?.askPermission(request) ??
|
|
985
992
|
Promise.resolve({ kind: 'reject', feedback: 'Nobody can answer yet.' }),
|
|
986
|
-
submit: (text) => scheduleLiveRef.current?.submit(text),
|
|
993
|
+
submit: (text, createdBy) => scheduleLiveRef.current?.submit(text, createdBy),
|
|
987
994
|
});
|
|
988
995
|
}
|
|
989
996
|
/**
|
|
@@ -1297,6 +1304,46 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1297
1304
|
const learningRef = useRef({ generation: -1, proposed: false, skillUsed: false });
|
|
1298
1305
|
/** `/skills save off|on` in this session, over whatever the config files say. */
|
|
1299
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);
|
|
1300
1347
|
const conversationLearning = useCallback(() => {
|
|
1301
1348
|
if (learningRef.current.generation !== conversationGenRef.current) {
|
|
1302
1349
|
learningRef.current = {
|
|
@@ -1613,22 +1660,28 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1613
1660
|
pushMessage('system', `Reasoning: ${effort} (this session).`);
|
|
1614
1661
|
}, [hasUnsettledTurn, pushMessage, session, setReasoningEffort, state]);
|
|
1615
1662
|
/**
|
|
1616
|
-
* Turn the session's
|
|
1663
|
+
* Turn the session's hypermode on or off.
|
|
1617
1664
|
*
|
|
1618
1665
|
* On: pins effort to the model's highest published level (last entry of
|
|
1619
1666
|
* `reasoningEffortLevels`, which every provider publishes low-to-high) and
|
|
1620
|
-
* strengthens delegation guidance for future turns via `SendOptions.
|
|
1667
|
+
* strengthens delegation guidance for future turns via `SendOptions.hypermode`
|
|
1621
1668
|
* — see `applyReasoningEffort` above for the mirrored guard shape. When the
|
|
1622
1669
|
* model publishes no exact menu, effort is left alone and the notice says
|
|
1623
1670
|
* so explicitly rather than silently doing nothing.
|
|
1624
1671
|
*/
|
|
1625
|
-
|
|
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) => {
|
|
1626
1679
|
if (!session?.hasProvider) {
|
|
1627
|
-
pushMessage('system', 'No active session — pick a provider before turning
|
|
1680
|
+
pushMessage('system', 'No active session — pick a provider before turning hypermode on.');
|
|
1628
1681
|
return;
|
|
1629
1682
|
}
|
|
1630
1683
|
if (selectedSession !== undefined && selectedSession !== session) {
|
|
1631
|
-
pushMessage('system', '
|
|
1684
|
+
pushMessage('system', 'Hypermode was not changed: the selected model is no longer active.');
|
|
1632
1685
|
return;
|
|
1633
1686
|
}
|
|
1634
1687
|
if (state !== 'idle' ||
|
|
@@ -1637,22 +1690,38 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
1637
1690
|
(queuedRef.current.length > 0 && selectedSession !== session) ||
|
|
1638
1691
|
permissionResolveRef.current !== null ||
|
|
1639
1692
|
compactingRef.current) {
|
|
1640
|
-
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.');
|
|
1641
1698
|
return;
|
|
1642
1699
|
}
|
|
1643
|
-
|
|
1700
|
+
setHypermode(enabled);
|
|
1644
1701
|
if (!enabled) {
|
|
1645
|
-
|
|
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.');
|
|
1646
1710
|
return;
|
|
1647
1711
|
}
|
|
1648
|
-
const
|
|
1649
|
-
if (
|
|
1650
|
-
|
|
1651
|
-
|
|
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.`);
|
|
1652
1721
|
return;
|
|
1653
1722
|
}
|
|
1654
|
-
pushMessage('system', `
|
|
1655
|
-
}, [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]);
|
|
1656
1725
|
const stepReasoningEffort = useCallback((direction) => {
|
|
1657
1726
|
const levels = session?.reasoningEffortLevels;
|
|
1658
1727
|
if (!session?.hasProvider || levels === undefined || levels.length === 0) {
|
|
@@ -2214,8 +2283,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2214
2283
|
removeStoredCredential(value);
|
|
2215
2284
|
return;
|
|
2216
2285
|
}
|
|
2217
|
-
if (picker.kind === 'reasoning-effort' && value ===
|
|
2218
|
-
|
|
2286
|
+
if (picker.kind === 'reasoning-effort' && value === HYPERMODE_VALUE) {
|
|
2287
|
+
applyHypermode(!hypermodeRef.current, picker.selectedSession);
|
|
2219
2288
|
return;
|
|
2220
2289
|
}
|
|
2221
2290
|
applyReasoningEffort(value, picker.selectedSession);
|
|
@@ -2223,7 +2292,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2223
2292
|
activateSkill,
|
|
2224
2293
|
advanceQueueContinuation,
|
|
2225
2294
|
appLifetime.signal,
|
|
2226
|
-
|
|
2295
|
+
applyHypermode,
|
|
2227
2296
|
applyPermissionMode,
|
|
2228
2297
|
applyReasoningEffort,
|
|
2229
2298
|
archiveCurrentConversation,
|
|
@@ -2546,11 +2615,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2546
2615
|
// A picker-owned provider/model change is one state transition. Clear the
|
|
2547
2616
|
// old model's effort selection before publishing the replacement session
|
|
2548
2617
|
// or releasing any paused queue. Failed and superseded candidates returned
|
|
2549
|
-
// above, so they leave the current session selection untouched.
|
|
2618
|
+
// above, so they leave the current session selection untouched. Hypermode
|
|
2550
2619
|
// mode survives the switch: re-pin to the new model's highest published
|
|
2551
2620
|
// level instead of clearing, exactly as it pinned when first turned on.
|
|
2552
2621
|
if (signal !== undefined) {
|
|
2553
|
-
|
|
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);
|
|
2554
2627
|
}
|
|
2555
2628
|
// Re-hydration (a provider switch via /model) builds a second session;
|
|
2556
2629
|
// without this the first one's tool-server child processes stay alive
|
|
@@ -2559,9 +2632,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
2559
2632
|
void previousSessionRef.current?.close();
|
|
2560
2633
|
previousSessionRef.current = s;
|
|
2561
2634
|
setSession(s);
|
|
2562
|
-
//
|
|
2635
|
+
// Hypermode already re-pinned effort above; reopening this picker
|
|
2563
2636
|
// would ask the operator to redo a choice the mode just made for them.
|
|
2564
|
-
if (!
|
|
2637
|
+
if (!hypermodeRef.current && options.chooseReasoningEffort && s.reasoningEffortLevels?.length) {
|
|
2565
2638
|
// Own input before publishing ready or releasing a paused queue. The
|
|
2566
2639
|
// menu closes only after choosing an effort or keeping the new default.
|
|
2567
2640
|
setSelectedChoice(0);
|
|
@@ -3961,6 +4034,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
3961
4034
|
...question.options.map((option) => ({
|
|
3962
4035
|
label: option.label,
|
|
3963
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 } : {}),
|
|
3964
4040
|
})),
|
|
3965
4041
|
...(question.allowFreeText
|
|
3966
4042
|
? [{ label: 'Something else…', description: 'Answer in your own words' }]
|
|
@@ -4433,7 +4509,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4433
4509
|
if (closing)
|
|
4434
4510
|
pushMessage('system', closing, false, '✻', undefined, theme.text.muted);
|
|
4435
4511
|
}
|
|
4436
|
-
|
|
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);
|
|
4437
4516
|
break;
|
|
4438
4517
|
}
|
|
4439
4518
|
case 'paused':
|
|
@@ -4445,6 +4524,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4445
4524
|
st.outcome = 'stopped';
|
|
4446
4525
|
st.queuePauseOutcome = 'paused';
|
|
4447
4526
|
st.notification = { kind: 'turn-settled', outcome: 'stopped' };
|
|
4527
|
+
triggerFollowUpsRef.current?.(st, 'paused');
|
|
4448
4528
|
if (event.handoff) {
|
|
4449
4529
|
// Nothing failed and nothing needs approving: a tool needs the
|
|
4450
4530
|
// operator to do something first. Continuing is one key.
|
|
@@ -4465,6 +4545,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4465
4545
|
case 'error':
|
|
4466
4546
|
closeAssistant();
|
|
4467
4547
|
st.outcome = event.message === 'aborted' ? 'cancelled' : 'failed';
|
|
4548
|
+
triggerFollowUpsRef.current?.(st, st.outcome);
|
|
4468
4549
|
if (event.message !== 'aborted') {
|
|
4469
4550
|
st.notification = { kind: 'turn-settled', outcome: 'failed' };
|
|
4470
4551
|
pushMessage('system', describeTurnInterruption(event), false, '!');
|
|
@@ -4581,11 +4662,29 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4581
4662
|
// Reserved before the turn begins, so everything that refers to this
|
|
4582
4663
|
// turn — its goal-round authority above all — names it from the start.
|
|
4583
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);
|
|
4584
4672
|
if (goalRound) {
|
|
4585
4673
|
pushMessage('system', `Goal round ${goalRound.round} / ${goalRound.maxGoalRounds}`, false, '◎', [`Objective: ${goalRound.objective}`]);
|
|
4586
4674
|
}
|
|
4587
4675
|
else {
|
|
4588
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
|
+
}
|
|
4589
4688
|
}
|
|
4590
4689
|
// A plan is scoped to the request it was made for. Cleared here, at
|
|
4591
4690
|
// the start of the next one, rather than when the previous turn
|
|
@@ -4617,6 +4716,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4617
4716
|
startedInPlanMode: permissionModeRef.current === 'plan',
|
|
4618
4717
|
}
|
|
4619
4718
|
: {}),
|
|
4719
|
+
followUps: turnTriggers.filter((id) => triggerDefinition(id).scope === 'after-turn'),
|
|
4720
|
+
contextTexts: triggerContextTexts(turnTriggers),
|
|
4620
4721
|
};
|
|
4621
4722
|
liveStreamRef.current = st;
|
|
4622
4723
|
// A skill activated with /skills <name> stays in the prompt of every
|
|
@@ -4792,7 +4893,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4792
4893
|
activeTurnInboxRef.current = inbox;
|
|
4793
4894
|
const turnPermissionMode = permissionModeRef.current;
|
|
4794
4895
|
const turnReasoningEffort = reasoningEffortRef.current;
|
|
4795
|
-
const
|
|
4896
|
+
const turnHypermode = hypermodeRef.current;
|
|
4796
4897
|
const turnLimits = resolveTurnGuards(ctxRef.current.limits, turnLimitsOverrideRef.current);
|
|
4797
4898
|
// Always carry the guarded callback. `auto` and `strict` decide before
|
|
4798
4899
|
// calling it in makeResumeHandler; retaining it is what lets a session
|
|
@@ -4840,8 +4941,15 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
4840
4941
|
// while this turn runs, and the change governs what follows.
|
|
4841
4942
|
currentPermissionMode: () => permissionModeRef.current,
|
|
4842
4943
|
limits: turnLimits,
|
|
4843
|
-
|
|
4844
|
-
|
|
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 ?? [],
|
|
4845
4953
|
...(goalRound ? { goalRound } : {}),
|
|
4846
4954
|
// The mode above decides whether this callback is consulted.
|
|
4847
4955
|
onPermission: askPermission,
|
|
@@ -5055,6 +5163,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5055
5163
|
sendTerminalNotification,
|
|
5056
5164
|
session,
|
|
5057
5165
|
setQueuePause,
|
|
5166
|
+
triggerContextNow,
|
|
5058
5167
|
wakeGoalDriver,
|
|
5059
5168
|
]);
|
|
5060
5169
|
const selectModelIntent = useCallback(async (query) => {
|
|
@@ -5176,7 +5285,31 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5176
5285
|
pushMessage('system', `Could not save memory: ${err instanceof Error ? err.message : String(err)}`);
|
|
5177
5286
|
}
|
|
5178
5287
|
}, [ctx.cwd, pushMessage, session]);
|
|
5179
|
-
|
|
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) => {
|
|
5180
5313
|
if (goalCommandInFlightRef.current) {
|
|
5181
5314
|
pushMessage('system', 'A goal command is still reaching durable session state. Wait for its result before sending another command or prompt.');
|
|
5182
5315
|
return;
|
|
@@ -5200,8 +5333,19 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5200
5333
|
}
|
|
5201
5334
|
// The operator moved on; a parked turn is still there for /resume.
|
|
5202
5335
|
setHandoffPark(null);
|
|
5203
|
-
|
|
5204
|
-
|
|
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;
|
|
5205
5349
|
if (selectionIntent) {
|
|
5206
5350
|
void selectModelIntent(selectionIntent.query);
|
|
5207
5351
|
return;
|
|
@@ -5209,12 +5353,12 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5209
5353
|
// `#` remembers, `!` runs — neither is a prompt. Both are the
|
|
5210
5354
|
// operator acting directly, the way other coding agents spell it,
|
|
5211
5355
|
// and both leave a row the model reads on its next turn.
|
|
5212
|
-
if (value.startsWith('#') && value.slice(1).trim().length > 0) {
|
|
5356
|
+
if (operatorText && value.startsWith('#') && value.slice(1).trim().length > 0) {
|
|
5213
5357
|
const note = value.slice(1).trim();
|
|
5214
5358
|
void rememberProjectNote(note);
|
|
5215
5359
|
return;
|
|
5216
5360
|
}
|
|
5217
|
-
const escaped = shellEscapeCommand(value);
|
|
5361
|
+
const escaped = operatorText ? shellEscapeCommand(value) : null;
|
|
5218
5362
|
if (escaped !== null) {
|
|
5219
5363
|
pushMessage('user', value);
|
|
5220
5364
|
const rowId = pushMessage('tool', `! ${escaped}`, true, '…');
|
|
@@ -5235,13 +5379,33 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5235
5379
|
});
|
|
5236
5380
|
return;
|
|
5237
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
|
+
}
|
|
5238
5402
|
// What actually gets sent. A `prompt` action replaces it with text the
|
|
5239
5403
|
// command composed, and then takes the ordinary send path below —
|
|
5240
5404
|
// including the queue — so a command-driven turn is not a second way
|
|
5241
5405
|
// to run one.
|
|
5242
|
-
let outgoing =
|
|
5406
|
+
let outgoing = dispatch;
|
|
5243
5407
|
let skillFlow = false;
|
|
5244
|
-
const slash = runSlash(
|
|
5408
|
+
const slash = operatorText ? runSlash(dispatch, slashCtx, hostCommands) : null;
|
|
5245
5409
|
if (slash) {
|
|
5246
5410
|
switch (slash.kind) {
|
|
5247
5411
|
case 'message':
|
|
@@ -5380,6 +5544,13 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5380
5544
|
description: `${session?.webSearchSummary ?? 'Not resolved'} · view tools, workspace and usage.`,
|
|
5381
5545
|
},
|
|
5382
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
|
+
},
|
|
5383
5554
|
{
|
|
5384
5555
|
name: 'config sources',
|
|
5385
5556
|
label: 'Setting sources',
|
|
@@ -5397,7 +5568,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5397
5568
|
description: command.description,
|
|
5398
5569
|
disabledReason: command.problem,
|
|
5399
5570
|
})),
|
|
5400
|
-
windowSize:
|
|
5571
|
+
windowSize: 8,
|
|
5401
5572
|
});
|
|
5402
5573
|
return;
|
|
5403
5574
|
}
|
|
@@ -5631,7 +5802,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
5631
5802
|
const values = [undefined, ...levels];
|
|
5632
5803
|
const current = reasoningEffortRef.current;
|
|
5633
5804
|
setSelectedChoice(Math.max(0, values.findIndex((value) => value === current)));
|
|
5634
|
-
setChoicePicker(reasoningEffortPicker(session, current, false,
|
|
5805
|
+
setChoicePicker(reasoningEffortPicker(session, current, false, hypermodeRef.current) ?? null);
|
|
5635
5806
|
return;
|
|
5636
5807
|
}
|
|
5637
5808
|
case 'login':
|
|
@@ -6153,9 +6324,25 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6153
6324
|
: 'Raw output mode off — rich transcript rendering restored.');
|
|
6154
6325
|
return;
|
|
6155
6326
|
}
|
|
6156
|
-
case '
|
|
6157
|
-
const
|
|
6158
|
-
|
|
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);
|
|
6159
6346
|
return;
|
|
6160
6347
|
}
|
|
6161
6348
|
case 'export-picker': {
|
|
@@ -6225,6 +6412,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6225
6412
|
text: outgoing,
|
|
6226
6413
|
...(attachments && attachments.length > 0 ? { attachments: [...attachments] } : {}),
|
|
6227
6414
|
...(skillFlow ? { skillFlow: true } : {}),
|
|
6415
|
+
...(armedTriggers.length > 0 && !skillFlow ? { triggers: armedTriggers } : {}),
|
|
6228
6416
|
};
|
|
6229
6417
|
// `/skills save` and `/skills new` are turns of their own: steered into
|
|
6230
6418
|
// a running turn, they would lose the flag that keeps the skill-making
|
|
@@ -6242,8 +6430,10 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6242
6430
|
message: createUserMessage(expanded.sendText, prompt.attachments),
|
|
6243
6431
|
attachedFiles: expanded.attached.length,
|
|
6244
6432
|
queueBoundary: queuedRef.current.length,
|
|
6245
|
-
}))
|
|
6433
|
+
})) {
|
|
6434
|
+
bindSteeredTriggers(armedTriggers);
|
|
6246
6435
|
return;
|
|
6436
|
+
}
|
|
6247
6437
|
}
|
|
6248
6438
|
}
|
|
6249
6439
|
enqueueQueued(prompt);
|
|
@@ -6251,6 +6441,8 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6251
6441
|
activeSkills,
|
|
6252
6442
|
advanceQueueContinuation,
|
|
6253
6443
|
applyPermissionMode,
|
|
6444
|
+
bindSteeredTriggers,
|
|
6445
|
+
composerTriggersOn,
|
|
6254
6446
|
conversationLearning,
|
|
6255
6447
|
applyReasoningEffort,
|
|
6256
6448
|
appLifetime,
|
|
@@ -6287,7 +6479,48 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6287
6479
|
// picker is also used by review/export flows. Keep only this dispatch hop in
|
|
6288
6480
|
// a ref so selecting a help row re-enters the one ordinary slash-command
|
|
6289
6481
|
// path instead of growing a second command executor.
|
|
6290
|
-
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
|
+
};
|
|
6291
6524
|
saveSkillNotifyRef.current = () => sendTerminalNotification({ kind: 'approval-required' });
|
|
6292
6525
|
scheduleLiveRef.current = {
|
|
6293
6526
|
...(scheduleLiveRef.current?.model ? { model: scheduleLiveRef.current.model } : {}),
|
|
@@ -6297,7 +6530,9 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
6297
6530
|
},
|
|
6298
6531
|
ask: askQuestion,
|
|
6299
6532
|
askPermission: onPermission,
|
|
6300
|
-
submit: (text) => handleSubmit(text
|
|
6533
|
+
submit: (text, createdBy) => handleSubmit(text, undefined, 'submit', {
|
|
6534
|
+
source: createdBy === 'operator' ? 'operator-loop' : 'model-loop',
|
|
6535
|
+
}),
|
|
6301
6536
|
};
|
|
6302
6537
|
// Scheduled work since the TUI last looked: one line, after the first
|
|
6303
6538
|
// paint, never blocking it. Loops fire when a turn ends, and on their timer.
|
|
@@ -7292,6 +7527,24 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7292
7527
|
: `Session: ${permissionSource}`
|
|
7293
7528
|
: undefined;
|
|
7294
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;
|
|
7295
7548
|
// True exactly when the phase ternary below renders its final branch — the
|
|
7296
7549
|
// normal composer view, as opposed to provider setup or a lifecycle
|
|
7297
7550
|
// picker/prompt. The agent rail is a feature of that view: computing this
|
|
@@ -7332,7 +7585,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7332
7585
|
agentSurface === null &&
|
|
7333
7586
|
outputViewer === null &&
|
|
7334
7587
|
choicePicker?.kind === 'reasoning-effort' &&
|
|
7335
|
-
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' &&
|
|
7336
7589
|
state !== 'awaiting-permission' &&
|
|
7337
7590
|
!compacting &&
|
|
7338
7591
|
externalEditorRequest === null &&
|
|
@@ -7360,7 +7613,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7360
7613
|
? queuePause.outcome === 'paused'
|
|
7361
7614
|
? 'held after a resumable turn paused; wait for recovery, change model, or send a message to release it'
|
|
7362
7615
|
: `paused after a ${queuePause.outcome} turn; send a message or change model to continue`
|
|
7363
|
-
: '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 ||
|
|
7364
7617
|
phase !== 'ready' ||
|
|
7365
7618
|
state === 'awaiting-permission' ||
|
|
7366
7619
|
compacting ||
|
|
@@ -7381,7 +7634,7 @@ export function App({ ctx: initialCtx, onExitSummary, externalEditor = defaultEx
|
|
|
7381
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) => ({
|
|
7382
7635
|
...command,
|
|
7383
7636
|
description: command.unavailable?.(slashCtx) ?? command.description,
|
|
7384
|
-
})), 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 &&
|
|
7385
7638
|
permission === null &&
|
|
7386
7639
|
agentSurface === null &&
|
|
7387
7640
|
outputViewer === null ? (_jsx(AgentNarrationBand, { lines: narration })) : null, showComposerSurface &&
|
|
@@ -7596,25 +7849,36 @@ function permissionPicker(mode, approvedAll, more = false) {
|
|
|
7596
7849
|
};
|
|
7597
7850
|
}
|
|
7598
7851
|
/**
|
|
7599
|
-
* The picker's sentinel for the
|
|
7852
|
+
* The picker's sentinel for the hypermode row, below the rule.
|
|
7600
7853
|
*
|
|
7601
7854
|
* A plain string outside the `ReasoningEffort` union by construction — never
|
|
7602
7855
|
* added to any model's published menu — so it can share the same picker
|
|
7603
7856
|
* without ever being mistaken for a level `/effort <token>` could select.
|
|
7604
7857
|
*/
|
|
7605
|
-
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
|
+
}
|
|
7606
7870
|
/** Last entry of a low-to-high menu every provider publishes in that order; `undefined` when none is known. */
|
|
7607
7871
|
function highestReasoningEffort(levels) {
|
|
7608
7872
|
return levels && levels.length > 0 ? levels[levels.length - 1] : undefined;
|
|
7609
7873
|
}
|
|
7610
|
-
function reasoningEffortPicker(session, current, afterModelSelection = false,
|
|
7874
|
+
function reasoningEffortPicker(session, current, afterModelSelection = false, hypermodeOn = false) {
|
|
7611
7875
|
const levels = session.reasoningEffortLevels;
|
|
7612
7876
|
if (!session.hasProvider || levels === undefined)
|
|
7613
7877
|
return undefined;
|
|
7614
7878
|
const effortValues = [undefined, ...levels];
|
|
7615
7879
|
const values = [
|
|
7616
7880
|
...effortValues,
|
|
7617
|
-
|
|
7881
|
+
HYPERMODE_VALUE,
|
|
7618
7882
|
];
|
|
7619
7883
|
return {
|
|
7620
7884
|
kind: 'reasoning-effort',
|
|
@@ -7635,12 +7899,11 @@ function reasoningEffortPicker(session, current, afterModelSelection = false, or
|
|
|
7635
7899
|
})),
|
|
7636
7900
|
{
|
|
7637
7901
|
// Below a rule, visually apart from the levels above: a session
|
|
7638
|
-
// setting, not a sixth level the provider published.
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
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,
|
|
7644
7907
|
ruleBefore: true,
|
|
7645
7908
|
},
|
|
7646
7909
|
],
|