@oh-my-pi/pi-coding-agent 15.7.1 → 15.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -6
- package/dist/types/auto-thinking/classifier.d.ts +35 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/extension-flags.d.ts +36 -0
- package/dist/types/config/config-file.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +23 -0
- package/dist/types/config/keybindings.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +112 -69
- package/dist/types/edit/hashline/diff.d.ts +9 -3
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
- package/dist/types/eval/agent-bridge.d.ts +25 -0
- package/dist/types/eval/backend.d.ts +17 -2
- package/dist/types/eval/budget-bridge.d.ts +29 -0
- package/dist/types/eval/idle-timeout.d.ts +28 -0
- package/dist/types/eval/js/executor.d.ts +8 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +13 -0
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
- package/dist/types/extensibility/plugins/manager.d.ts +12 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/memory-backend/index.d.ts +1 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +3 -3
- package/dist/types/mnemopi/backend.d.ts +4 -0
- package/dist/types/mnemopi/config.d.ts +29 -0
- package/dist/types/mnemopi/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -2
- package/dist/types/modes/components/model-selector.d.ts +3 -2
- package/dist/types/modes/components/omfg-panel.d.ts +19 -0
- package/dist/types/modes/controllers/command-controller.d.ts +7 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
- package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
- package/dist/types/modes/gradient-highlight.d.ts +5 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -3
- package/dist/types/modes/magic-keywords.d.ts +14 -0
- package/dist/types/modes/markdown-prose.d.ts +27 -0
- package/dist/types/modes/orchestrate.d.ts +7 -2
- package/dist/types/modes/shared.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/turn-budget.d.ts +18 -0
- package/dist/types/modes/types.d.ts +7 -3
- package/dist/types/modes/ultrathink.d.ts +7 -2
- package/dist/types/modes/workflow.d.ts +15 -0
- package/dist/types/sdk.d.ts +15 -4
- package/dist/types/session/agent-session.d.ts +59 -23
- package/dist/types/session/session-manager.d.ts +18 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/session/shake-types.d.ts +24 -0
- package/dist/types/task/executor.d.ts +2 -2
- package/dist/types/thinking.d.ts +39 -1
- package/dist/types/tiny/device.d.ts +3 -3
- package/dist/types/tiny/models.d.ts +34 -1
- package/dist/types/tiny/title-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +19 -3
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/package.json +10 -10
- package/src/auto-thinking/classifier.ts +180 -0
- package/src/autoresearch/tools/run-experiment.ts +45 -113
- package/src/cli/args.ts +39 -16
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/plugin-cli.ts +11 -2
- package/src/config/config-file.ts +98 -13
- package/src/config/file-lock.ts +60 -17
- package/src/config/keybindings.ts +78 -27
- package/src/config/model-registry.ts +7 -1
- package/src/config/settings-schema.ts +118 -71
- package/src/config/settings.ts +12 -0
- package/src/edit/hashline/diff.ts +87 -22
- package/src/edit/renderer.ts +16 -12
- package/src/edit/streaming.ts +17 -6
- package/src/eval/__tests__/agent-bridge.test.ts +433 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/idle-timeout.test.ts +66 -0
- package/src/eval/__tests__/shared-executors.test.ts +53 -0
- package/src/eval/agent-bridge.ts +295 -0
- package/src/eval/backend.ts +17 -2
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/idle-timeout.ts +80 -0
- package/src/eval/js/executor.ts +35 -7
- package/src/eval/js/index.ts +2 -1
- package/src/eval/js/shared/local-module-loader.ts +75 -10
- package/src/eval/js/shared/prelude.txt +85 -1
- package/src/eval/js/tool-bridge.ts +9 -0
- package/src/eval/py/executor.ts +41 -14
- package/src/eval/py/index.ts +2 -1
- package/src/eval/py/prelude.py +132 -1
- package/src/exec/bash-executor.ts +2 -3
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/runner.ts +12 -2
- package/src/extensibility/plugins/git-url.ts +90 -4
- package/src/extensibility/plugins/manager.ts +103 -7
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +88 -88
- package/src/main.ts +50 -56
- package/src/memory-backend/index.ts +1 -1
- package/src/memory-backend/resolve.ts +3 -3
- package/src/memory-backend/types.ts +3 -3
- package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
- package/src/{mnemosyne → mnemopi}/config.ts +36 -36
- package/src/{mnemosyne → mnemopi}/state.ts +67 -67
- package/src/modes/acp/acp-agent.ts +13 -3
- package/src/modes/components/agent-dashboard.ts +6 -6
- package/src/modes/components/custom-editor.ts +4 -11
- package/src/modes/components/extensions/state-manager.ts +3 -4
- package/src/modes/components/footer.ts +18 -12
- package/src/modes/components/hook-selector.ts +86 -20
- package/src/modes/components/model-selector.ts +20 -11
- package/src/modes/components/oauth-selector.ts +93 -21
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/settings-defs.ts +9 -2
- package/src/modes/components/settings-selector.ts +4 -1
- package/src/modes/components/status-line/segments.ts +13 -5
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +38 -19
- package/src/modes/components/tree-selector.ts +4 -3
- package/src/modes/components/user-message-selector.ts +94 -19
- package/src/modes/components/user-message.ts +8 -1
- package/src/modes/controllers/command-controller.ts +57 -0
- package/src/modes/controllers/event-controller.ts +65 -3
- package/src/modes/controllers/input-controller.ts +14 -11
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +21 -6
- package/src/modes/gradient-highlight.ts +23 -6
- package/src/modes/interactive-mode.ts +41 -7
- package/src/modes/magic-keywords.ts +20 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/orchestrate.ts +17 -11
- package/src/modes/shared.ts +3 -11
- package/src/modes/theme/theme.ts +6 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/ultrathink.ts +16 -10
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/workflow.ts +42 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +12 -0
- package/src/prompts/system/omfg-user.md +51 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +70 -0
- package/src/prompts/tools/eval.md +13 -1
- package/src/prompts/tools/memory-edit.md +1 -1
- package/src/sdk.ts +86 -38
- package/src/session/agent-session.ts +558 -80
- package/src/session/session-manager.ts +32 -0
- package/src/session/session-storage.ts +68 -8
- package/src/session/shake-types.ts +44 -0
- package/src/slash-commands/builtin-registry.ts +41 -16
- package/src/task/executor.ts +3 -3
- package/src/task/index.ts +6 -6
- package/src/thinking.ts +73 -1
- package/src/tiny/device.ts +4 -10
- package/src/tiny/models.ts +54 -2
- package/src/tiny/title-protocol.ts +11 -1
- package/src/tiny/worker.ts +19 -7
- package/src/tools/eval.ts +202 -26
- package/src/tools/grouped-file-output.ts +9 -2
- package/src/tools/index.ts +17 -5
- package/src/tools/memory-edit.ts +4 -4
- package/src/tools/memory-recall.ts +5 -5
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +4 -4
- package/src/tools/render-utils.ts +2 -1
- package/src/tools/search.ts +480 -76
- package/dist/types/mnemosyne/backend.d.ts +0 -4
- package/dist/types/mnemosyne/config.d.ts +0 -29
- package/dist/types/mnemosyne/state.d.ts +0 -72
- /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
- /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { maskNonProse } from "./markdown-prose";
|
|
1
2
|
import { theme } from "./theme/theme";
|
|
2
3
|
|
|
4
|
+
/** A gradient keyword highlighter. `resetTo` is the SGR foreground sequence
|
|
5
|
+
* re-emitted after each painted keyword so surrounding text keeps its color;
|
|
6
|
+
* it defaults to a plain foreground reset (editor / default-colored text). */
|
|
7
|
+
export type KeywordHighlighter = (text: string, resetTo?: string) => string;
|
|
8
|
+
|
|
3
9
|
const FG_RESET = "\x1b[39m";
|
|
4
10
|
|
|
5
11
|
/** Declarative spec for {@link createGradientHighlighter}. */
|
|
@@ -25,7 +31,7 @@ export interface GradientHighlightSpec {
|
|
|
25
31
|
* untouched when `probe` does not match. The palette is compiled lazily and
|
|
26
32
|
* memoized per active color mode.
|
|
27
33
|
*/
|
|
28
|
-
export function createGradientHighlighter(spec: GradientHighlightSpec):
|
|
34
|
+
export function createGradientHighlighter(spec: GradientHighlightSpec): KeywordHighlighter {
|
|
29
35
|
const { probe, highlight, stops, hue, saturation = 90, lightness = 62 } = spec;
|
|
30
36
|
|
|
31
37
|
let cachedMode: string | undefined;
|
|
@@ -45,8 +51,8 @@ export function createGradientHighlighter(spec: GradientHighlightSpec): (text: s
|
|
|
45
51
|
return next;
|
|
46
52
|
};
|
|
47
53
|
|
|
48
|
-
/** Paint each character of `word` with the next gradient stop,
|
|
49
|
-
const paint = (word: string): string => {
|
|
54
|
+
/** Paint each character of `word` with the next gradient stop, restoring `resetTo` after. */
|
|
55
|
+
const paint = (word: string, resetTo: string): string => {
|
|
50
56
|
const stopsArr = palette();
|
|
51
57
|
const n = word.length;
|
|
52
58
|
let out = "";
|
|
@@ -60,11 +66,22 @@ export function createGradientHighlighter(spec: GradientHighlightSpec): (text: s
|
|
|
60
66
|
}
|
|
61
67
|
out += word[i];
|
|
62
68
|
}
|
|
63
|
-
return `${out}${
|
|
69
|
+
return `${out}${resetTo}`;
|
|
64
70
|
};
|
|
65
71
|
|
|
66
|
-
return (text: string): string => {
|
|
72
|
+
return (text: string, resetTo: string = FG_RESET): string => {
|
|
67
73
|
if (!probe.test(text)) return text;
|
|
68
|
-
|
|
74
|
+
// Match against a code/markup-masked copy so keywords inside code spans,
|
|
75
|
+
// fenced blocks, or XML sections never paint; indices still address `text`.
|
|
76
|
+
const masked = maskNonProse(text);
|
|
77
|
+
let out = "";
|
|
78
|
+
let last = 0;
|
|
79
|
+
for (const m of masked.matchAll(highlight)) {
|
|
80
|
+
const start = m.index ?? 0;
|
|
81
|
+
const end = start + m[0].length;
|
|
82
|
+
out += text.slice(last, start) + paint(text.slice(start, end), resetTo);
|
|
83
|
+
last = end;
|
|
84
|
+
}
|
|
85
|
+
return out + text.slice(last);
|
|
69
86
|
};
|
|
70
87
|
}
|
|
@@ -62,6 +62,7 @@ import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../sess
|
|
|
62
62
|
import { HistoryStorage } from "../session/history-storage";
|
|
63
63
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
64
64
|
import { getRecentSessions } from "../session/session-manager";
|
|
65
|
+
import type { ShakeMode } from "../session/shake-types";
|
|
65
66
|
import { formatDuration } from "../slash-commands/helpers/format";
|
|
66
67
|
import { STTController, type SttState } from "../stt";
|
|
67
68
|
import type { LspStartupServerInfo } from "../tools";
|
|
@@ -91,6 +92,7 @@ import { EventController } from "./controllers/event-controller";
|
|
|
91
92
|
import { ExtensionUiController } from "./controllers/extension-ui-controller";
|
|
92
93
|
import { InputController } from "./controllers/input-controller";
|
|
93
94
|
import { MCPCommandController } from "./controllers/mcp-command-controller";
|
|
95
|
+
import { OmfgController } from "./controllers/omfg-controller";
|
|
94
96
|
import { SelectorController } from "./controllers/selector-controller";
|
|
95
97
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
96
98
|
import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
@@ -236,6 +238,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
236
238
|
statusContainer: Container;
|
|
237
239
|
todoContainer: Container;
|
|
238
240
|
btwContainer: Container;
|
|
241
|
+
omfgContainer: Container;
|
|
239
242
|
editor: CustomEditor;
|
|
240
243
|
editorContainer: Container;
|
|
241
244
|
hookWidgetContainerAbove: Container;
|
|
@@ -315,6 +318,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
315
318
|
readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
316
319
|
|
|
317
320
|
readonly #btwController: BtwController;
|
|
321
|
+
readonly #omfgController: OmfgController;
|
|
318
322
|
readonly #commandController: CommandController;
|
|
319
323
|
readonly #todoCommandController: TodoCommandController;
|
|
320
324
|
readonly #eventController: EventController;
|
|
@@ -368,6 +372,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
368
372
|
this.statusContainer = new Container();
|
|
369
373
|
this.todoContainer = new Container();
|
|
370
374
|
this.btwContainer = new Container();
|
|
375
|
+
this.omfgContainer = new Container();
|
|
371
376
|
this.editor = new CustomEditor(getEditorTheme());
|
|
372
377
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
373
378
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
@@ -429,6 +434,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
429
434
|
|
|
430
435
|
this.#uiHelpers = new UiHelpers(this);
|
|
431
436
|
this.#btwController = new BtwController(this);
|
|
437
|
+
this.#omfgController = new OmfgController(this);
|
|
432
438
|
this.#extensionUiController = new ExtensionUiController(this);
|
|
433
439
|
this.#eventController = new EventController(this);
|
|
434
440
|
this.#commandController = new CommandController(this);
|
|
@@ -526,6 +532,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
526
532
|
this.ui.addChild(this.statusContainer);
|
|
527
533
|
this.ui.addChild(this.todoContainer);
|
|
528
534
|
this.ui.addChild(this.btwContainer);
|
|
535
|
+
this.ui.addChild(this.omfgContainer);
|
|
529
536
|
this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
|
|
530
537
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
531
538
|
this.ui.addChild(this.editorContainer);
|
|
@@ -2034,16 +2041,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2034
2041
|
: "Approve and keep context";
|
|
2035
2042
|
|
|
2036
2043
|
// Model-tier slider: let the operator pick which configured role model
|
|
2037
|
-
// (smol/default/slow/…) executes the approved plan.
|
|
2038
|
-
//
|
|
2039
|
-
//
|
|
2044
|
+
// (smol/default/slow/…) executes the approved plan. The slider always starts
|
|
2045
|
+
// on the `default` tier so execution defaults to the default model no matter
|
|
2046
|
+
// which model drove the planning conversation. Left/right move it from there;
|
|
2047
|
+
// hidden when fewer than two role models resolve — a lone tier is no choice.
|
|
2048
|
+
// `selectedTierIndex` tracks the live slider position.
|
|
2040
2049
|
const cycle = this.session.getRoleModelCycle(this.session.settings.get("cycleOrder"));
|
|
2041
|
-
|
|
2050
|
+
const defaultTierIndex = cycle ? cycle.models.findIndex(entry => entry.role === "default") : -1;
|
|
2051
|
+
const startTierIndex = defaultTierIndex >= 0 ? defaultTierIndex : (cycle?.currentIndex ?? 0);
|
|
2052
|
+
let selectedTierIndex = startTierIndex;
|
|
2042
2053
|
const slider: HookSelectorSlider | undefined =
|
|
2043
2054
|
cycle && cycle.models.length > 1
|
|
2044
2055
|
? {
|
|
2045
2056
|
caption: "continue with",
|
|
2046
|
-
index:
|
|
2057
|
+
index: startTierIndex,
|
|
2047
2058
|
segments: cycle.models.map(entry => ({
|
|
2048
2059
|
label: entry.role,
|
|
2049
2060
|
color: MODEL_ROLES[entry.role as ModelRole]?.color,
|
|
@@ -2080,6 +2091,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2080
2091
|
// applying the slider choice any earlier would be silently reverted —
|
|
2081
2092
|
// the bug that made "continue with slow" keep executing on the default
|
|
2082
2093
|
// model. Deferred application also survives newSession()/compaction.
|
|
2094
|
+
// `cycle.currentIndex` is exactly that restored model, so any chosen tier
|
|
2095
|
+
// differing from it needs an explicit executionModel — this also covers
|
|
2096
|
+
// leaving the slider on its `default` anchor while planning ran elsewhere.
|
|
2083
2097
|
const executionModel =
|
|
2084
2098
|
cycle && selectedTierIndex !== cycle.currentIndex ? cycle.models[selectedTierIndex] : undefined;
|
|
2085
2099
|
await this.#approvePlan(latestPlanContent, {
|
|
@@ -2211,6 +2225,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2211
2225
|
logger.warn("Failed to save session draft", { error: String(err) });
|
|
2212
2226
|
}
|
|
2213
2227
|
this.#btwController.dispose();
|
|
2228
|
+
this.#omfgController.dispose();
|
|
2214
2229
|
|
|
2215
2230
|
// Emit shutdown event to hooks
|
|
2216
2231
|
await this.session.dispose();
|
|
@@ -2532,6 +2547,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2532
2547
|
|
|
2533
2548
|
#prepareSessionSwitch(): void {
|
|
2534
2549
|
this.#btwController.dispose();
|
|
2550
|
+
this.#omfgController.dispose();
|
|
2535
2551
|
this.#extensionUiController.clearExtensionTerminalInputListeners();
|
|
2536
2552
|
this.#planReviewContainer = undefined;
|
|
2537
2553
|
}
|
|
@@ -2548,6 +2564,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2548
2564
|
|
|
2549
2565
|
handleForkCommand(): Promise<void> {
|
|
2550
2566
|
this.#btwController.dispose();
|
|
2567
|
+
this.#omfgController.dispose();
|
|
2551
2568
|
return this.#commandController.handleForkCommand();
|
|
2552
2569
|
}
|
|
2553
2570
|
|
|
@@ -2683,6 +2700,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2683
2700
|
return this.#commandController.handleHandoffCommand(customInstructions);
|
|
2684
2701
|
}
|
|
2685
2702
|
|
|
2703
|
+
handleShakeCommand(mode: ShakeMode): Promise<void> {
|
|
2704
|
+
return this.#commandController.handleShakeCommand(mode);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2686
2707
|
executeCompaction(
|
|
2687
2708
|
customInstructionsOrOptions?: string | CompactOptions,
|
|
2688
2709
|
isAuto?: boolean,
|
|
@@ -2733,6 +2754,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2733
2754
|
|
|
2734
2755
|
handleResumeSession(sessionPath: string): Promise<void> {
|
|
2735
2756
|
this.#btwController.dispose();
|
|
2757
|
+
this.#omfgController.dispose();
|
|
2736
2758
|
this.resetObserverRegistry();
|
|
2737
2759
|
return this.#selectorController.handleResumeSession(sessionPath);
|
|
2738
2760
|
}
|
|
@@ -2786,12 +2808,24 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2786
2808
|
return this.#btwController.handleEscape();
|
|
2787
2809
|
}
|
|
2788
2810
|
|
|
2811
|
+
handleOmfgCommand(complaint: string): Promise<void> {
|
|
2812
|
+
return this.#omfgController.start(complaint);
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
hasActiveOmfg(): boolean {
|
|
2816
|
+
return this.#omfgController.hasActiveRequest();
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
handleOmfgEscape(): boolean {
|
|
2820
|
+
return this.#omfgController.handleEscape();
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2789
2823
|
cycleThinkingLevel(): void {
|
|
2790
2824
|
this.#inputController.cycleThinkingLevel();
|
|
2791
2825
|
}
|
|
2792
2826
|
|
|
2793
|
-
cycleRoleModel(
|
|
2794
|
-
return this.#inputController.cycleRoleModel(
|
|
2827
|
+
cycleRoleModel(direction?: "forward" | "backward"): Promise<void> {
|
|
2828
|
+
return this.#inputController.cycleRoleModel(direction);
|
|
2795
2829
|
}
|
|
2796
2830
|
|
|
2797
2831
|
toggleToolOutputExpansion(): void {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { highlightOrchestrate } from "./orchestrate";
|
|
2
|
+
import { highlightUltrathink } from "./ultrathink";
|
|
3
|
+
import { highlightWorkflow } from "./workflow";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gradient-highlight every magic keyword ("ultrathink", "orchestrate",
|
|
7
|
+
* "workflow") that appears as standalone prose, skipping any occurrence inside a
|
|
8
|
+
* code block, inline code span, or XML/HTML section. Each highlighter paints its
|
|
9
|
+
* own keyword with its own gradient, so chaining is order-independent — the
|
|
10
|
+
* earlier passes only inject zero-width SGR escapes (no backticks or angle
|
|
11
|
+
* brackets), which never confuse the later passes' markdown masking.
|
|
12
|
+
*
|
|
13
|
+
* `resetTo` is the SGR foreground sequence restored after each painted keyword;
|
|
14
|
+
* pass the surrounding text color when decorating already-colored content (e.g.
|
|
15
|
+
* a themed message bubble) so the gradient does not bleed into the rest of the
|
|
16
|
+
* line. Defaults to a plain foreground reset for default-colored editor text.
|
|
17
|
+
*/
|
|
18
|
+
export function highlightMagicKeywords(text: string, resetTo?: string): string {
|
|
19
|
+
return highlightWorkflow(highlightOrchestrate(highlightUltrathink(text, resetTo), resetTo), resetTo);
|
|
20
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown structure awareness for the magic-keyword affordances
|
|
3
|
+
* ("ultrathink"/"orchestrate"/"workflow").
|
|
4
|
+
*
|
|
5
|
+
* Keyword detection and editor/transcript highlighting must fire only on prose
|
|
6
|
+
* the user is actually addressing to the model — never on a word that happens to
|
|
7
|
+
* live inside a fenced code block, an inline code span, or an HTML/XML section.
|
|
8
|
+
* {@link maskNonProse} returns a length-preserving copy of the text where every
|
|
9
|
+
* such region is blanked to spaces, so a word-bounded match run against the mask
|
|
10
|
+
* never lands inside code/markup while its indices still address the original
|
|
11
|
+
* text for painting.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Tag/element name: HTML5/XML start char + name chars. Sticky so we can probe at
|
|
15
|
+
// a precise offset without slicing.
|
|
16
|
+
const TAG_NAME = /[A-Za-z][A-Za-z0-9-]*/y;
|
|
17
|
+
|
|
18
|
+
// A line that opens or closes a fenced code block: up to 3 leading spaces then a
|
|
19
|
+
// run of >=3 backticks or tildes.
|
|
20
|
+
const FENCE = /^( {0,3})([`~]{3,})/;
|
|
21
|
+
|
|
22
|
+
/** Index just past the run of backticks beginning at `i`. */
|
|
23
|
+
function backtickRunEnd(text: string, i: number, n: number): number {
|
|
24
|
+
let j = i;
|
|
25
|
+
while (j < n && text[j] === "`") j++;
|
|
26
|
+
return j;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Find the closing backtick run that matches an opening run of `runLen`
|
|
31
|
+
* backticks, scanning from `from`. Returns the index just past the closing run,
|
|
32
|
+
* or -1 when no run of the exact length exists (an unmatched run is literal text,
|
|
33
|
+
* not a code span). Already-masked positions (fenced code) are skipped.
|
|
34
|
+
*/
|
|
35
|
+
function findBacktickClose(text: string, from: number, n: number, runLen: number, masked: Uint8Array): number {
|
|
36
|
+
let k = from;
|
|
37
|
+
while (k < n) {
|
|
38
|
+
if (masked[k]) {
|
|
39
|
+
k++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (text[k] === "`") {
|
|
43
|
+
const e = backtickRunEnd(text, k, n);
|
|
44
|
+
if (e - k === runLen) return e;
|
|
45
|
+
k = e;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
k++;
|
|
49
|
+
}
|
|
50
|
+
return -1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Index of the `>` that closes a tag whose attributes begin at `j`, honoring
|
|
55
|
+
* quoted attribute values. Returns -1 when the tag is malformed (a new `<`
|
|
56
|
+
* appears first, or there is no `>`), so callers can treat the `<` as literal.
|
|
57
|
+
*/
|
|
58
|
+
function findTagEnd(text: string, j: number, n: number): number {
|
|
59
|
+
let quote = "";
|
|
60
|
+
for (let k = j; k < n; k++) {
|
|
61
|
+
const ch = text[k];
|
|
62
|
+
if (quote) {
|
|
63
|
+
if (ch === quote) quote = "";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ch === '"' || ch === "'") {
|
|
67
|
+
quote = ch;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === ">") return k;
|
|
71
|
+
if (ch === "<") return -1;
|
|
72
|
+
}
|
|
73
|
+
return -1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Locate the `</name>` that balances an opening `<name>` at `start`, counting
|
|
78
|
+
* nested same-name tags. Returns the index just past the matching close tag's
|
|
79
|
+
* `>`, or -1 when the section is never closed (so callers mask only the opening
|
|
80
|
+
* tag rather than swallowing the rest of the document).
|
|
81
|
+
*/
|
|
82
|
+
function findMatchingClose(text: string, start: number, n: number, name: string, masked: Uint8Array): number {
|
|
83
|
+
const lname = name.toLowerCase();
|
|
84
|
+
let depth = 1;
|
|
85
|
+
let k = start;
|
|
86
|
+
while (k < n) {
|
|
87
|
+
if (masked[k] || text[k] !== "<") {
|
|
88
|
+
k++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let m = k + 1;
|
|
92
|
+
let isClose = false;
|
|
93
|
+
if (text[m] === "/") {
|
|
94
|
+
isClose = true;
|
|
95
|
+
m++;
|
|
96
|
+
}
|
|
97
|
+
TAG_NAME.lastIndex = m;
|
|
98
|
+
const nm = TAG_NAME.exec(text);
|
|
99
|
+
if (!nm) {
|
|
100
|
+
k++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const gt = findTagEnd(text, TAG_NAME.lastIndex, n);
|
|
104
|
+
if (gt < 0) {
|
|
105
|
+
k++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (nm[0].toLowerCase() === lname) {
|
|
109
|
+
if (isClose) {
|
|
110
|
+
depth--;
|
|
111
|
+
if (depth === 0) return gt + 1;
|
|
112
|
+
} else if (text[gt - 1] !== "/") {
|
|
113
|
+
depth++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
k = gt + 1;
|
|
117
|
+
}
|
|
118
|
+
return -1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Mask the HTML/XML construct beginning at `<` (index `i`): an HTML comment, a
|
|
123
|
+
* self-closing/closing tag (the tag alone), or an opening tag together with the
|
|
124
|
+
* content through its matching close tag. Returns the index just past the masked
|
|
125
|
+
* region, or `i` when the `<` does not begin a tag (e.g. a stray less-than).
|
|
126
|
+
*/
|
|
127
|
+
function maskTagAt(text: string, i: number, n: number, masked: Uint8Array): number {
|
|
128
|
+
if (text.startsWith("<!--", i)) {
|
|
129
|
+
const end = text.indexOf("-->", i + 4);
|
|
130
|
+
const stop = end < 0 ? n : end + 3;
|
|
131
|
+
for (let p = i; p < stop; p++) masked[p] = 1;
|
|
132
|
+
return stop;
|
|
133
|
+
}
|
|
134
|
+
let j = i + 1;
|
|
135
|
+
let closing = false;
|
|
136
|
+
if (text[j] === "/") {
|
|
137
|
+
closing = true;
|
|
138
|
+
j++;
|
|
139
|
+
}
|
|
140
|
+
TAG_NAME.lastIndex = j;
|
|
141
|
+
const nm = TAG_NAME.exec(text);
|
|
142
|
+
if (!nm) return i;
|
|
143
|
+
const gt = findTagEnd(text, TAG_NAME.lastIndex, n);
|
|
144
|
+
if (gt < 0) return i;
|
|
145
|
+
const tagEnd = gt + 1;
|
|
146
|
+
const selfClosing = text[gt - 1] === "/";
|
|
147
|
+
for (let p = i; p < tagEnd; p++) masked[p] = 1;
|
|
148
|
+
if (closing || selfClosing) return tagEnd;
|
|
149
|
+
const close = findMatchingClose(text, tagEnd, n, nm[0], masked);
|
|
150
|
+
if (close < 0) return tagEnd;
|
|
151
|
+
for (let p = tagEnd; p < close; p++) masked[p] = 1;
|
|
152
|
+
return close;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Return a copy of `text` with identical length (indices map 1:1) where every
|
|
157
|
+
* character inside a non-prose region is replaced by a space. Non-prose regions
|
|
158
|
+
* are markdown fenced code blocks, inline code spans, and HTML/XML tags together
|
|
159
|
+
* with the content they enclose. Newlines are preserved. Text with no construct
|
|
160
|
+
* that could open such a region is returned unchanged.
|
|
161
|
+
*/
|
|
162
|
+
export function maskNonProse(text: string): string {
|
|
163
|
+
if (!text.includes("`") && !text.includes("<") && !text.includes("~~~")) {
|
|
164
|
+
return text;
|
|
165
|
+
}
|
|
166
|
+
const n = text.length;
|
|
167
|
+
const masked = new Uint8Array(n);
|
|
168
|
+
|
|
169
|
+
// Phase 1: fenced code blocks, line by line.
|
|
170
|
+
let fenceChar = "";
|
|
171
|
+
let fenceLen = 0;
|
|
172
|
+
let lineStart = 0;
|
|
173
|
+
while (lineStart <= n) {
|
|
174
|
+
let nl = text.indexOf("\n", lineStart);
|
|
175
|
+
if (nl < 0) nl = n;
|
|
176
|
+
const line = text.slice(lineStart, nl);
|
|
177
|
+
const open = FENCE.exec(line);
|
|
178
|
+
if (fenceChar) {
|
|
179
|
+
for (let p = lineStart; p < nl; p++) masked[p] = 1;
|
|
180
|
+
// A closing fence is the same char, at least as long, with nothing else on the line.
|
|
181
|
+
if (
|
|
182
|
+
open &&
|
|
183
|
+
open[2]![0] === fenceChar &&
|
|
184
|
+
open[2]!.length >= fenceLen &&
|
|
185
|
+
line.slice(open[1]!.length + open[2]!.length).trim() === ""
|
|
186
|
+
) {
|
|
187
|
+
fenceChar = "";
|
|
188
|
+
fenceLen = 0;
|
|
189
|
+
}
|
|
190
|
+
} else if (open) {
|
|
191
|
+
const marker = open[2]!;
|
|
192
|
+
const ch = marker[0]!;
|
|
193
|
+
// A backtick fence's info string may not contain a backtick.
|
|
194
|
+
if (!(ch === "`" && line.slice(open[1]!.length + marker.length).includes("`"))) {
|
|
195
|
+
fenceChar = ch;
|
|
196
|
+
fenceLen = marker.length;
|
|
197
|
+
for (let p = lineStart; p < nl; p++) masked[p] = 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (nl === n) break;
|
|
201
|
+
lineStart = nl + 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Phase 2: inline code spans and HTML/XML, over not-yet-masked regions.
|
|
205
|
+
let i = 0;
|
|
206
|
+
while (i < n) {
|
|
207
|
+
if (masked[i]) {
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const c = text[i];
|
|
212
|
+
if (c === "`") {
|
|
213
|
+
const runEnd = backtickRunEnd(text, i, n);
|
|
214
|
+
const close = findBacktickClose(text, runEnd, n, runEnd - i, masked);
|
|
215
|
+
if (close >= 0) {
|
|
216
|
+
for (let p = i; p < close; p++) masked[p] = 1;
|
|
217
|
+
i = close;
|
|
218
|
+
} else {
|
|
219
|
+
i = runEnd;
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (c === "<") {
|
|
224
|
+
const end = maskTagAt(text, i, n, masked);
|
|
225
|
+
i = end > i ? end : i + 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
i++;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const arr = text.split("");
|
|
232
|
+
for (let p = 0; p < n; p++) {
|
|
233
|
+
if (masked[p] && arr[p] !== "\n") arr[p] = " ";
|
|
234
|
+
}
|
|
235
|
+
return arr.join("");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Whether `text` contains a standalone keyword match (per the non-global,
|
|
240
|
+
* word-bounded `word` regex) that lives in prose rather than inside a code
|
|
241
|
+
* block, inline code span, or HTML/XML section. `word` MUST be non-global so
|
|
242
|
+
* `.test` stays stateless.
|
|
243
|
+
*/
|
|
244
|
+
export function keywordInProse(text: string, word: RegExp): boolean {
|
|
245
|
+
if (!word.test(text)) return false;
|
|
246
|
+
return word.test(maskNonProse(text));
|
|
247
|
+
}
|
package/src/modes/orchestrate.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import orchestrateNotice from "../prompts/system/orchestrate-notice.md" with { type: "text" };
|
|
2
|
-
import { createGradientHighlighter } from "./gradient-highlight";
|
|
2
|
+
import { createGradientHighlighter, type KeywordHighlighter } from "./gradient-highlight";
|
|
3
|
+
import { keywordInProse } from "./markdown-prose";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* "orchestrate" keyword support.
|
|
@@ -7,20 +8,25 @@ import { createGradientHighlighter } from "./gradient-highlight";
|
|
|
7
8
|
* Typing the standalone word in the input editor paints it with a cool
|
|
8
9
|
* teal→violet gradient ({@link highlightOrchestrate}); submitting a message that
|
|
9
10
|
* mentions it appends a hidden {@link ORCHESTRATE_NOTICE} that switches the model
|
|
10
|
-
* into multi-agent orchestration mode. Matching is
|
|
11
|
-
* case-
|
|
12
|
-
* behavior. Replaces the former
|
|
11
|
+
* into multi-agent orchestration mode. Matching is whitespace-delimited and
|
|
12
|
+
* case-sensitive (lowercase only), so "orchestrated", "Orchestrate", or a path
|
|
13
|
+
* like "orchestrate.ts" never trigger either behavior. Replaces the former
|
|
14
|
+
* `/orchestrate` slash command.
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
|
-
// Detection:
|
|
16
|
-
const ORCHESTRATE_WORD =
|
|
17
|
+
// Detection: lowercase keyword flanked by whitespace or a string edge. Non-global so `.test` stays stateless.
|
|
18
|
+
const ORCHESTRATE_WORD = /(?<!\S)orchestrate(?!\S)/;
|
|
17
19
|
|
|
18
20
|
/** Hidden system notice appended after a user message that mentions "orchestrate". */
|
|
19
21
|
export const ORCHESTRATE_NOTICE: string = orchestrateNotice.trim();
|
|
20
22
|
|
|
21
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Whether `text` contains the standalone keyword "orchestrate" (lowercase,
|
|
25
|
+
* whitespace-delimited) in prose — never inside a code block, inline code span,
|
|
26
|
+
* or XML/HTML section.
|
|
27
|
+
*/
|
|
22
28
|
export function containsOrchestrate(text: string): boolean {
|
|
23
|
-
return
|
|
29
|
+
return keywordInProse(text, ORCHESTRATE_WORD);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
/**
|
|
@@ -28,9 +34,9 @@ export function containsOrchestrate(text: string): boolean {
|
|
|
28
34
|
* cool teal→violet gradient (hue 150..280), visually distinct from ultrathink's
|
|
29
35
|
* full-spectrum rainbow.
|
|
30
36
|
*/
|
|
31
|
-
export const highlightOrchestrate:
|
|
32
|
-
probe: /orchestrate
|
|
33
|
-
highlight:
|
|
37
|
+
export const highlightOrchestrate: KeywordHighlighter = createGradientHighlighter({
|
|
38
|
+
probe: /orchestrate/,
|
|
39
|
+
highlight: /(?<!\S)orchestrate(?!\S)/g,
|
|
34
40
|
stops: 14,
|
|
35
41
|
hue: t => 150 + t * 130,
|
|
36
42
|
});
|
package/src/modes/shared.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from "node:util";
|
|
1
2
|
import type { TabBarTheme } from "@oh-my-pi/pi-tui";
|
|
2
3
|
import { theme } from "./theme/theme";
|
|
3
4
|
|
|
@@ -5,18 +6,9 @@ import { theme } from "./theme/theme";
|
|
|
5
6
|
// Text Sanitization
|
|
6
7
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
const ANSI_STRING_RE = /\x1b(?:P|_|\^)[\s\S]*?\x1b\\|[\x90\x9e\x9f][\s\S]*?\x9c/g;
|
|
10
|
-
const ANSI_CSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]|\x9b[0-?]*[ -/]*[@-~]/g;
|
|
11
|
-
const ANSI_SINGLE_RE = /\x1b[@-Z\\-_]/g;
|
|
12
|
-
|
|
13
|
-
/** Sanitize text for display in a single-line status. Strips ANSI escape sequences, C0/C1 control characters, collapses whitespace, trims. */
|
|
9
|
+
/** Sanitize text for display in a single-line status. Strips ANSI/VT escape sequences, maps remaining C0/C1 control characters to spaces, collapses whitespace, trims. */
|
|
14
10
|
export function sanitizeStatusText(text: string): string {
|
|
15
|
-
return text
|
|
16
|
-
.replace(ANSI_OSC_RE, "")
|
|
17
|
-
.replace(ANSI_STRING_RE, "")
|
|
18
|
-
.replace(ANSI_CSI_RE, "")
|
|
19
|
-
.replace(ANSI_SINGLE_RE, "")
|
|
11
|
+
return stripVTControlCharacters(text)
|
|
20
12
|
.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
21
13
|
.replace(/ +/g, " ")
|
|
22
14
|
.trim();
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -132,6 +132,7 @@ export type SymbolKey =
|
|
|
132
132
|
| "thinking.medium"
|
|
133
133
|
| "thinking.high"
|
|
134
134
|
| "thinking.xhigh"
|
|
135
|
+
| "thinking.autoPending"
|
|
135
136
|
// Checkboxes
|
|
136
137
|
| "checkbox.checked"
|
|
137
138
|
| "checkbox.unchecked"
|
|
@@ -297,6 +298,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
297
298
|
"thinking.medium": "◒ med",
|
|
298
299
|
"thinking.high": "◕ high",
|
|
299
300
|
"thinking.xhigh": "◉ xhigh",
|
|
301
|
+
"thinking.autoPending": "▣?",
|
|
300
302
|
// Checkboxes
|
|
301
303
|
"checkbox.checked": "☑",
|
|
302
304
|
"checkbox.unchecked": "☐",
|
|
@@ -550,6 +552,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
550
552
|
"thinking.high": "\u{F111} high",
|
|
551
553
|
// pick: 🧠 xhi | alt: xhi xhi
|
|
552
554
|
"thinking.xhigh": "\u{F06D} xhi",
|
|
555
|
+
// pick: (nf-md-help_box) | alt: [?]
|
|
556
|
+
"thinking.autoPending": "\u{f078b}",
|
|
553
557
|
// Checkboxes
|
|
554
558
|
// pick: | alt:
|
|
555
559
|
"checkbox.checked": "\uf14a",
|
|
@@ -723,6 +727,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
723
727
|
"thinking.medium": "[med]",
|
|
724
728
|
"thinking.high": "[high]",
|
|
725
729
|
"thinking.xhigh": "[xhi]",
|
|
730
|
+
"thinking.autoPending": "[?]",
|
|
726
731
|
// Checkboxes
|
|
727
732
|
"checkbox.checked": "[x]",
|
|
728
733
|
"checkbox.unchecked": "[ ]",
|
|
@@ -1518,6 +1523,7 @@ export class Theme {
|
|
|
1518
1523
|
medium: this.#symbols["thinking.medium"],
|
|
1519
1524
|
high: this.#symbols["thinking.high"],
|
|
1520
1525
|
xhigh: this.#symbols["thinking.xhigh"],
|
|
1526
|
+
autoPending: this.#symbols["thinking.autoPending"],
|
|
1521
1527
|
};
|
|
1522
1528
|
}
|
|
1523
1529
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "+Nk" turn token-budget directive.
|
|
3
|
+
*
|
|
4
|
+
* A standalone `+<number>[k|m]` token in the user's message sets a per-turn
|
|
5
|
+
* output-token budget surfaced by the `eval` `budget` helper. By default it is
|
|
6
|
+
* ADVISORY — the model self-limits via `budget.remaining()`. Append `!`
|
|
7
|
+
* (`+500k!`) to make it a HARD ceiling: eval `agent()` refuses to spawn once the
|
|
8
|
+
* turn's spend reaches it. Matching is anchored to token boundaries so it does
|
|
9
|
+
* not fire on prices or version strings embedded in prose.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Number, optional k/m multiplier, optional `!` hard marker, bounded by whitespace/string edges.
|
|
13
|
+
const TURN_BUDGET = /(?:^|\s)\+(\d+(?:\.\d+)?)([km])?(!)?(?=\s|$)/i;
|
|
14
|
+
|
|
15
|
+
export interface TurnBudget {
|
|
16
|
+
/** Output-token ceiling for the turn. */
|
|
17
|
+
total: number;
|
|
18
|
+
/** Whether the ceiling is enforced (eval `agent()` throws past it) vs advisory. */
|
|
19
|
+
hard: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Parse a `+Nk`/`+N`/`+Nm`(`!`) turn-budget directive from `text`, or null when absent. */
|
|
23
|
+
export function parseTurnBudget(text: string): TurnBudget | null {
|
|
24
|
+
const match = TURN_BUDGET.exec(text);
|
|
25
|
+
if (!match) return null;
|
|
26
|
+
const value = Number(match[1]);
|
|
27
|
+
if (!Number.isFinite(value) || value <= 0) return null;
|
|
28
|
+
const unit = match[2]?.toLowerCase();
|
|
29
|
+
const multiplier = unit === "k" ? 1_000 : unit === "m" ? 1_000_000 : 1;
|
|
30
|
+
return { total: Math.round(value * multiplier), hard: match[3] === "!" };
|
|
31
|
+
}
|
package/src/modes/types.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { PlanApprovalDetails } from "../plan-mode/approved-plan";
|
|
|
16
16
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
17
17
|
import type { HistoryStorage } from "../session/history-storage";
|
|
18
18
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
19
|
+
import type { ShakeMode } from "../session/shake-types";
|
|
19
20
|
import type { LspStartupServerInfo } from "../tools";
|
|
20
21
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
21
22
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
@@ -70,6 +71,7 @@ export interface InteractiveModeContext {
|
|
|
70
71
|
statusContainer: Container;
|
|
71
72
|
todoContainer: Container;
|
|
72
73
|
btwContainer: Container;
|
|
74
|
+
omfgContainer: Container;
|
|
73
75
|
editor: CustomEditor;
|
|
74
76
|
editorContainer: Container;
|
|
75
77
|
hookWidgetContainerAbove: Container;
|
|
@@ -228,6 +230,7 @@ export interface InteractiveModeContext {
|
|
|
228
230
|
handleSSHCommand(text: string): Promise<void>;
|
|
229
231
|
handleCompactCommand(customInstructions?: string): Promise<CompactionOutcome>;
|
|
230
232
|
handleHandoffCommand(customInstructions?: string): Promise<void>;
|
|
233
|
+
handleShakeCommand(mode: ShakeMode): Promise<void>;
|
|
231
234
|
handleMoveCommand(targetPath: string): Promise<void>;
|
|
232
235
|
handleRenameCommand(title: string): Promise<void>;
|
|
233
236
|
handleMemoryCommand(text: string): Promise<void>;
|
|
@@ -267,8 +270,11 @@ export interface InteractiveModeContext {
|
|
|
267
270
|
handleBtwCommand(question: string): Promise<void>;
|
|
268
271
|
hasActiveBtw(): boolean;
|
|
269
272
|
handleBtwEscape(): boolean;
|
|
273
|
+
handleOmfgCommand(complaint: string): Promise<void>;
|
|
274
|
+
hasActiveOmfg(): boolean;
|
|
275
|
+
handleOmfgEscape(): boolean;
|
|
270
276
|
cycleThinkingLevel(): void;
|
|
271
|
-
cycleRoleModel(
|
|
277
|
+
cycleRoleModel(direction?: "forward" | "backward"): Promise<void>;
|
|
272
278
|
toggleToolOutputExpansion(): void;
|
|
273
279
|
setToolsExpanded(expanded: boolean): void;
|
|
274
280
|
toggleThinkingBlockVisibility(): void;
|