@makefinks/daemon 0.2.0 → 0.3.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/package.json +1 -1
- package/src/ai/daemon-ai.ts +30 -85
- package/src/ai/system-prompt.ts +134 -111
- package/src/ai/tool-approval-coordinator.ts +113 -0
- package/src/ai/tools/index.ts +12 -32
- package/src/ai/tools/subagents.ts +16 -30
- package/src/ai/tools/tool-registry.ts +203 -0
- package/src/app/App.tsx +23 -641
- package/src/app/components/AppOverlays.tsx +8 -0
- package/src/components/HotkeysPane.tsx +2 -1
- package/src/components/ToolsMenu.tsx +235 -0
- package/src/hooks/use-app-context-builder.ts +2 -0
- package/src/hooks/use-app-controller.ts +546 -0
- package/src/hooks/use-app-menus.ts +6 -0
- package/src/hooks/use-app-preferences-bootstrap.ts +9 -0
- package/src/hooks/use-bootstrap-controller.ts +92 -0
- package/src/hooks/use-daemon-keyboard.ts +28 -8
- package/src/hooks/use-daemon-runtime-controller.ts +147 -0
- package/src/hooks/use-overlay-controller.ts +6 -0
- package/src/hooks/use-session-controller.ts +79 -0
- package/src/state/app-context.tsx +2 -0
- package/src/state/daemon-state.ts +19 -8
- package/src/types/index.ts +25 -0
- package/src/utils/preferences.ts +10 -0
|
@@ -24,6 +24,7 @@ export interface KeyboardHandlerActions {
|
|
|
24
24
|
setShowHotkeysPane: (show: boolean) => void;
|
|
25
25
|
setShowGroundingMenu: (show: boolean) => void;
|
|
26
26
|
setShowUrlMenu: (show: boolean) => void;
|
|
27
|
+
setShowToolsMenu: (show: boolean) => void;
|
|
27
28
|
setTypingInput: (input: string | ((prev: string) => string)) => void;
|
|
28
29
|
setCurrentTranscription: (text: string) => void;
|
|
29
30
|
setCurrentResponse: (text: string) => void;
|
|
@@ -41,7 +42,7 @@ export interface KeyboardHandlerActions {
|
|
|
41
42
|
|
|
42
43
|
export function useDaemonKeyboard(state: KeyboardHandlerState, actions: KeyboardHandlerActions) {
|
|
43
44
|
const manager = getDaemonManager();
|
|
44
|
-
const { isOverlayOpen, escPendingCancel, hasInteracted, hasGrounding } = state;
|
|
45
|
+
const { isOverlayOpen, escPendingCancel, hasInteracted, hasGrounding, showFullReasoning } = state;
|
|
45
46
|
|
|
46
47
|
const closeAllMenus = useCallback(() => {
|
|
47
48
|
actions.setShowDeviceMenu(false);
|
|
@@ -52,6 +53,7 @@ export function useDaemonKeyboard(state: KeyboardHandlerState, actions: Keyboard
|
|
|
52
53
|
actions.setShowHotkeysPane(false);
|
|
53
54
|
actions.setShowGroundingMenu(false);
|
|
54
55
|
actions.setShowUrlMenu(false);
|
|
56
|
+
actions.setShowToolsMenu(false);
|
|
55
57
|
}, [actions]);
|
|
56
58
|
|
|
57
59
|
const handleKeyPress = useCallback(
|
|
@@ -163,11 +165,13 @@ export function useDaemonKeyboard(state: KeyboardHandlerState, actions: Keyboard
|
|
|
163
165
|
return;
|
|
164
166
|
}
|
|
165
167
|
|
|
166
|
-
// 'G' key to open grounding menu (in IDLE or
|
|
168
|
+
// 'G' key to open grounding menu (in IDLE, SPEAKING, or RESPONDING state when grounding exists)
|
|
167
169
|
if (
|
|
168
170
|
(key.sequence === "g" || key.sequence === "G") &&
|
|
169
171
|
key.eventType === "press" &&
|
|
170
|
-
(currentState === DaemonState.IDLE ||
|
|
172
|
+
(currentState === DaemonState.IDLE ||
|
|
173
|
+
currentState === DaemonState.SPEAKING ||
|
|
174
|
+
currentState === DaemonState.RESPONDING) &&
|
|
171
175
|
hasGrounding
|
|
172
176
|
) {
|
|
173
177
|
closeAllMenus();
|
|
@@ -176,11 +180,13 @@ export function useDaemonKeyboard(state: KeyboardHandlerState, actions: Keyboard
|
|
|
176
180
|
return;
|
|
177
181
|
}
|
|
178
182
|
|
|
179
|
-
// 'U' key to open URL menu (in IDLE or
|
|
183
|
+
// 'U' key to open URL menu (in IDLE, SPEAKING, or RESPONDING state when hasInteracted)
|
|
180
184
|
if (
|
|
181
185
|
(key.sequence === "u" || key.sequence === "U") &&
|
|
182
186
|
key.eventType === "press" &&
|
|
183
|
-
(currentState === DaemonState.IDLE ||
|
|
187
|
+
(currentState === DaemonState.IDLE ||
|
|
188
|
+
currentState === DaemonState.SPEAKING ||
|
|
189
|
+
currentState === DaemonState.RESPONDING) &&
|
|
184
190
|
hasInteracted
|
|
185
191
|
) {
|
|
186
192
|
closeAllMenus();
|
|
@@ -212,7 +218,7 @@ export function useDaemonKeyboard(state: KeyboardHandlerState, actions: Keyboard
|
|
|
212
218
|
return;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
|
-
// 'T' key to
|
|
221
|
+
// 'T' key to open tools menu (in IDLE, SPEAKING, or RESPONDING state)
|
|
216
222
|
if (
|
|
217
223
|
(key.sequence === "t" || key.sequence === "T") &&
|
|
218
224
|
key.eventType === "press" &&
|
|
@@ -220,7 +226,21 @@ export function useDaemonKeyboard(state: KeyboardHandlerState, actions: Keyboard
|
|
|
220
226
|
currentState === DaemonState.SPEAKING ||
|
|
221
227
|
currentState === DaemonState.RESPONDING)
|
|
222
228
|
) {
|
|
223
|
-
|
|
229
|
+
closeAllMenus();
|
|
230
|
+
actions.setShowToolsMenu(true);
|
|
231
|
+
key.preventDefault();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 'R' key to toggle full reasoning display (in IDLE, SPEAKING, or RESPONDING state)
|
|
236
|
+
if (
|
|
237
|
+
(key.sequence === "r" || key.sequence === "R") &&
|
|
238
|
+
key.eventType === "press" &&
|
|
239
|
+
(currentState === DaemonState.IDLE ||
|
|
240
|
+
currentState === DaemonState.SPEAKING ||
|
|
241
|
+
currentState === DaemonState.RESPONDING)
|
|
242
|
+
) {
|
|
243
|
+
const next = !showFullReasoning;
|
|
224
244
|
actions.setShowFullReasoning(next);
|
|
225
245
|
actions.persistPreferences({ showFullReasoning: next });
|
|
226
246
|
toast.info(`FULL PREVIEWS: ${next ? "ON" : "OFF"}`, {
|
|
@@ -373,7 +393,7 @@ export function useDaemonKeyboard(state: KeyboardHandlerState, actions: Keyboard
|
|
|
373
393
|
escPendingCancel,
|
|
374
394
|
hasInteracted,
|
|
375
395
|
hasGrounding,
|
|
376
|
-
|
|
396
|
+
showFullReasoning,
|
|
377
397
|
state.showToolOutput,
|
|
378
398
|
actions,
|
|
379
399
|
]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ScrollBoxRenderable } from "@opentui/core";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import type { MutableRefObject } from "react";
|
|
4
|
+
|
|
5
|
+
import { useDaemonEvents } from "./use-daemon-events";
|
|
6
|
+
import { useInputHistory } from "./use-input-history";
|
|
7
|
+
import { useReasoningAnimation } from "./use-reasoning-animation";
|
|
8
|
+
import { useResponseTimer } from "./use-response-timer";
|
|
9
|
+
import { useTypingMode } from "./use-typing-mode";
|
|
10
|
+
|
|
11
|
+
import { daemonEvents } from "../state/daemon-events";
|
|
12
|
+
|
|
13
|
+
export interface DaemonRuntimeControllerResult {
|
|
14
|
+
reasoning: ReturnType<typeof useReasoningAnimation>;
|
|
15
|
+
|
|
16
|
+
daemonState: ReturnType<typeof useDaemonEvents>["daemonState"];
|
|
17
|
+
conversationHistory: ReturnType<typeof useDaemonEvents>["conversationHistory"];
|
|
18
|
+
currentTranscription: ReturnType<typeof useDaemonEvents>["currentTranscription"];
|
|
19
|
+
currentResponse: ReturnType<typeof useDaemonEvents>["currentResponse"];
|
|
20
|
+
currentContentBlocks: ReturnType<typeof useDaemonEvents>["currentContentBlocks"];
|
|
21
|
+
error: ReturnType<typeof useDaemonEvents>["error"];
|
|
22
|
+
sessionUsage: ReturnType<typeof useDaemonEvents>["sessionUsage"];
|
|
23
|
+
modelMetadata: ReturnType<typeof useDaemonEvents>["modelMetadata"];
|
|
24
|
+
avatarRef: ReturnType<typeof useDaemonEvents>["avatarRef"];
|
|
25
|
+
currentUserInputRef: ReturnType<typeof useDaemonEvents>["currentUserInputRef"];
|
|
26
|
+
hydrateConversationHistory: ReturnType<typeof useDaemonEvents>["hydrateConversationHistory"];
|
|
27
|
+
setCurrentTranscription: ReturnType<typeof useDaemonEvents>["setCurrentTranscription"];
|
|
28
|
+
setCurrentResponse: ReturnType<typeof useDaemonEvents>["setCurrentResponse"];
|
|
29
|
+
clearCurrentContentBlocks: ReturnType<typeof useDaemonEvents>["clearCurrentContentBlocks"];
|
|
30
|
+
resetSessionUsage: ReturnType<typeof useDaemonEvents>["resetSessionUsage"];
|
|
31
|
+
setSessionUsage: ReturnType<typeof useDaemonEvents>["setSessionUsage"];
|
|
32
|
+
applyAvatarForState: ReturnType<typeof useDaemonEvents>["applyAvatarForState"];
|
|
33
|
+
|
|
34
|
+
typing: ReturnType<typeof useTypingMode>;
|
|
35
|
+
|
|
36
|
+
responseElapsedMs: number;
|
|
37
|
+
|
|
38
|
+
conversationScrollRef: MutableRefObject<ScrollBoxRenderable | null>;
|
|
39
|
+
|
|
40
|
+
hasInteracted: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useDaemonRuntimeController({
|
|
44
|
+
currentModelId,
|
|
45
|
+
preferencesLoaded,
|
|
46
|
+
sessionId,
|
|
47
|
+
sessionIdRef,
|
|
48
|
+
ensureSessionId,
|
|
49
|
+
onFirstMessage,
|
|
50
|
+
}: {
|
|
51
|
+
currentModelId: string;
|
|
52
|
+
preferencesLoaded: boolean;
|
|
53
|
+
sessionId: string | null;
|
|
54
|
+
sessionIdRef: MutableRefObject<string | null>;
|
|
55
|
+
ensureSessionId: () => Promise<string>;
|
|
56
|
+
onFirstMessage: (sessionId: string, message: string) => void;
|
|
57
|
+
}): DaemonRuntimeControllerResult {
|
|
58
|
+
const reasoning = useReasoningAnimation();
|
|
59
|
+
const { addToHistory, navigateUp, navigateDown, resetNavigation } = useInputHistory();
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
daemonState,
|
|
63
|
+
conversationHistory,
|
|
64
|
+
currentTranscription,
|
|
65
|
+
currentResponse,
|
|
66
|
+
currentContentBlocks,
|
|
67
|
+
error,
|
|
68
|
+
sessionUsage,
|
|
69
|
+
modelMetadata,
|
|
70
|
+
avatarRef,
|
|
71
|
+
currentUserInputRef,
|
|
72
|
+
hydrateConversationHistory,
|
|
73
|
+
setCurrentTranscription,
|
|
74
|
+
setCurrentResponse,
|
|
75
|
+
clearCurrentContentBlocks,
|
|
76
|
+
resetSessionUsage,
|
|
77
|
+
setSessionUsage,
|
|
78
|
+
applyAvatarForState,
|
|
79
|
+
} = useDaemonEvents({
|
|
80
|
+
currentModelId,
|
|
81
|
+
preferencesLoaded,
|
|
82
|
+
setReasoningQueue: reasoning.setReasoningQueue,
|
|
83
|
+
setFullReasoning: reasoning.setFullReasoning,
|
|
84
|
+
clearReasoningState: reasoning.clearReasoningState,
|
|
85
|
+
clearReasoningTicker: reasoning.clearReasoningTicker,
|
|
86
|
+
fullReasoningRef: reasoning.fullReasoningRef,
|
|
87
|
+
sessionId,
|
|
88
|
+
sessionIdRef,
|
|
89
|
+
ensureSessionId,
|
|
90
|
+
addToHistory,
|
|
91
|
+
onFirstMessage,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const typing = useTypingMode({
|
|
95
|
+
daemonState,
|
|
96
|
+
currentUserInputRef,
|
|
97
|
+
setCurrentTranscription,
|
|
98
|
+
onTypingActivity: useCallback(() => {
|
|
99
|
+
avatarRef.current?.triggerTypingPulse();
|
|
100
|
+
}, [avatarRef]),
|
|
101
|
+
navigateUp,
|
|
102
|
+
navigateDown,
|
|
103
|
+
resetNavigation,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const handleTranscriptionReady = (text: string) => {
|
|
108
|
+
typing.prefillTypingInput(text);
|
|
109
|
+
};
|
|
110
|
+
daemonEvents.on("transcriptionReady", handleTranscriptionReady);
|
|
111
|
+
return () => {
|
|
112
|
+
daemonEvents.off("transcriptionReady", handleTranscriptionReady);
|
|
113
|
+
};
|
|
114
|
+
}, [typing.prefillTypingInput]);
|
|
115
|
+
|
|
116
|
+
const { responseElapsedMs } = useResponseTimer({ daemonState });
|
|
117
|
+
|
|
118
|
+
const conversationScrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
119
|
+
|
|
120
|
+
const hasInteracted =
|
|
121
|
+
conversationHistory.length > 0 || currentTranscription.length > 0 || currentContentBlocks.length > 0;
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
reasoning,
|
|
125
|
+
daemonState,
|
|
126
|
+
conversationHistory,
|
|
127
|
+
currentTranscription,
|
|
128
|
+
currentResponse,
|
|
129
|
+
currentContentBlocks,
|
|
130
|
+
error,
|
|
131
|
+
sessionUsage,
|
|
132
|
+
modelMetadata,
|
|
133
|
+
avatarRef,
|
|
134
|
+
currentUserInputRef,
|
|
135
|
+
hydrateConversationHistory,
|
|
136
|
+
setCurrentTranscription,
|
|
137
|
+
setCurrentResponse,
|
|
138
|
+
clearCurrentContentBlocks,
|
|
139
|
+
resetSessionUsage,
|
|
140
|
+
setSessionUsage,
|
|
141
|
+
applyAvatarForState,
|
|
142
|
+
typing,
|
|
143
|
+
responseElapsedMs,
|
|
144
|
+
conversationScrollRef,
|
|
145
|
+
hasInteracted,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -9,6 +9,7 @@ export interface OverlayControllerState {
|
|
|
9
9
|
showHotkeysPane: boolean;
|
|
10
10
|
showGroundingMenu: boolean;
|
|
11
11
|
showUrlMenu: boolean;
|
|
12
|
+
showToolsMenu: boolean;
|
|
12
13
|
onboardingActive: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -21,6 +22,7 @@ export interface OverlayControllerActions {
|
|
|
21
22
|
setShowHotkeysPane: (show: boolean) => void;
|
|
22
23
|
setShowGroundingMenu: (show: boolean) => void;
|
|
23
24
|
setShowUrlMenu: (show: boolean) => void;
|
|
25
|
+
setShowToolsMenu: (show: boolean) => void;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export function useOverlayController(state: OverlayControllerState, actions: OverlayControllerActions) {
|
|
@@ -33,6 +35,7 @@ export function useOverlayController(state: OverlayControllerState, actions: Ove
|
|
|
33
35
|
showHotkeysPane,
|
|
34
36
|
showGroundingMenu,
|
|
35
37
|
showUrlMenu,
|
|
38
|
+
showToolsMenu,
|
|
36
39
|
onboardingActive,
|
|
37
40
|
} = state;
|
|
38
41
|
|
|
@@ -46,6 +49,7 @@ export function useOverlayController(state: OverlayControllerState, actions: Ove
|
|
|
46
49
|
showHotkeysPane ||
|
|
47
50
|
showGroundingMenu ||
|
|
48
51
|
showUrlMenu ||
|
|
52
|
+
showToolsMenu ||
|
|
49
53
|
onboardingActive
|
|
50
54
|
);
|
|
51
55
|
}, [
|
|
@@ -57,6 +61,7 @@ export function useOverlayController(state: OverlayControllerState, actions: Ove
|
|
|
57
61
|
showHotkeysPane,
|
|
58
62
|
showGroundingMenu,
|
|
59
63
|
showUrlMenu,
|
|
64
|
+
showToolsMenu,
|
|
60
65
|
onboardingActive,
|
|
61
66
|
]);
|
|
62
67
|
|
|
@@ -69,6 +74,7 @@ export function useOverlayController(state: OverlayControllerState, actions: Ove
|
|
|
69
74
|
actions.setShowHotkeysPane(false);
|
|
70
75
|
actions.setShowGroundingMenu(false);
|
|
71
76
|
actions.setShowUrlMenu(false);
|
|
77
|
+
actions.setShowToolsMenu(false);
|
|
72
78
|
}, [actions]);
|
|
73
79
|
|
|
74
80
|
return {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import type { MutableRefObject } from "react";
|
|
3
|
+
|
|
4
|
+
import { useAppSessions } from "./use-app-sessions";
|
|
5
|
+
import { useGrounding } from "./use-grounding";
|
|
6
|
+
import { useGroundingMenuController } from "./use-grounding-menu-controller";
|
|
7
|
+
|
|
8
|
+
import { getDaemonManager } from "../state/daemon-state";
|
|
9
|
+
|
|
10
|
+
export interface SessionControllerResult {
|
|
11
|
+
currentSessionId: string | null;
|
|
12
|
+
setCurrentSessionIdSafe: (id: string | null) => void;
|
|
13
|
+
currentSessionIdRef: MutableRefObject<string | null>;
|
|
14
|
+
ensureSessionId: () => Promise<string>;
|
|
15
|
+
setSessions: ReturnType<typeof useAppSessions>["setSessions"];
|
|
16
|
+
sessionMenuItems: ReturnType<typeof useAppSessions>["sessionMenuItems"];
|
|
17
|
+
handleFirstMessage: ReturnType<typeof useAppSessions>["handleFirstMessage"];
|
|
18
|
+
|
|
19
|
+
latestGroundingMap: ReturnType<typeof useGrounding>["latestGroundingMap"];
|
|
20
|
+
hasGrounding: boolean;
|
|
21
|
+
|
|
22
|
+
groundingInitialIndex: number;
|
|
23
|
+
groundingSelectedIndex: number;
|
|
24
|
+
setGroundingSelectedIndex: (idx: number) => void;
|
|
25
|
+
onGroundingSelect: (idx: number) => void;
|
|
26
|
+
onGroundingIndexChange: (idx: number) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useSessionController({
|
|
30
|
+
showSessionMenu,
|
|
31
|
+
}: {
|
|
32
|
+
showSessionMenu: boolean;
|
|
33
|
+
}): SessionControllerResult {
|
|
34
|
+
const {
|
|
35
|
+
currentSessionId,
|
|
36
|
+
setCurrentSessionIdSafe,
|
|
37
|
+
currentSessionIdRef,
|
|
38
|
+
ensureSessionId,
|
|
39
|
+
setSessions,
|
|
40
|
+
sessionMenuItems,
|
|
41
|
+
handleFirstMessage,
|
|
42
|
+
} = useAppSessions({ showSessionMenu });
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const manager = getDaemonManager();
|
|
46
|
+
manager.setEnsureSessionId(() => ensureSessionId());
|
|
47
|
+
return () => manager.setEnsureSessionId(null);
|
|
48
|
+
}, [ensureSessionId]);
|
|
49
|
+
|
|
50
|
+
const { latestGroundingMap, hasGrounding } = useGrounding(currentSessionId);
|
|
51
|
+
const {
|
|
52
|
+
groundingInitialIndex,
|
|
53
|
+
groundingSelectedIndex,
|
|
54
|
+
setGroundingSelectedIndex,
|
|
55
|
+
onGroundingSelect,
|
|
56
|
+
onGroundingIndexChange,
|
|
57
|
+
} = useGroundingMenuController({ sessionId: currentSessionId, latestGroundingMap });
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setGroundingSelectedIndex(0);
|
|
61
|
+
}, [currentSessionId, setGroundingSelectedIndex]);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
currentSessionId,
|
|
65
|
+
setCurrentSessionIdSafe,
|
|
66
|
+
currentSessionIdRef,
|
|
67
|
+
ensureSessionId,
|
|
68
|
+
setSessions,
|
|
69
|
+
sessionMenuItems,
|
|
70
|
+
handleFirstMessage,
|
|
71
|
+
latestGroundingMap,
|
|
72
|
+
hasGrounding,
|
|
73
|
+
groundingInitialIndex,
|
|
74
|
+
groundingSelectedIndex,
|
|
75
|
+
setGroundingSelectedIndex,
|
|
76
|
+
onGroundingSelect,
|
|
77
|
+
onGroundingIndexChange,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -31,6 +31,8 @@ export interface MenuState {
|
|
|
31
31
|
setShowGroundingMenu: React.Dispatch<React.SetStateAction<boolean>>;
|
|
32
32
|
showUrlMenu: boolean;
|
|
33
33
|
setShowUrlMenu: React.Dispatch<React.SetStateAction<boolean>>;
|
|
34
|
+
showToolsMenu: boolean;
|
|
35
|
+
setShowToolsMenu: React.Dispatch<React.SetStateAction<boolean>>;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export interface DeviceState {
|
|
@@ -5,19 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
import { AgentTurnRunner } from "../ai/agent-turn-runner";
|
|
7
7
|
import { transcribeAudio } from "../ai/daemon-ai";
|
|
8
|
-
import { debug } from "../utils/debug-logger";
|
|
9
|
-
import { SpeechController } from "../voice/tts/speech-controller";
|
|
10
|
-
import { VoiceInputController } from "../voice/voice-input-controller";
|
|
11
8
|
import type {
|
|
12
|
-
|
|
9
|
+
BashApprovalLevel,
|
|
13
10
|
InteractionMode,
|
|
14
|
-
|
|
15
|
-
SpeechSpeed,
|
|
11
|
+
ModelMessage,
|
|
16
12
|
ReasoningEffort,
|
|
17
|
-
|
|
13
|
+
SpeechSpeed,
|
|
14
|
+
ToolToggles,
|
|
15
|
+
VoiceInteractionType,
|
|
18
16
|
} from "../types";
|
|
17
|
+
import { DEFAULT_TOOL_TOGGLES } from "../types";
|
|
19
18
|
import { DaemonState } from "../types";
|
|
20
|
-
import {
|
|
19
|
+
import { debug } from "../utils/debug-logger";
|
|
20
|
+
import { SpeechController } from "../voice/tts/speech-controller";
|
|
21
|
+
import { VoiceInputController } from "../voice/voice-input-controller";
|
|
22
|
+
import { type DaemonStateEvents, daemonEvents } from "./daemon-events";
|
|
21
23
|
import { ModelHistoryStore } from "./model-history-store";
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -39,6 +41,7 @@ class DaemonStateManager {
|
|
|
39
41
|
private _speechSpeed: SpeechSpeed = 1.25;
|
|
40
42
|
private _reasoningEffort: ReasoningEffort = "medium";
|
|
41
43
|
private _bashApprovalLevel: BashApprovalLevel = "dangerous";
|
|
44
|
+
private _toolToggles: ToolToggles = { ...DEFAULT_TOOL_TOGGLES };
|
|
42
45
|
private _outputDeviceName: string | undefined = undefined;
|
|
43
46
|
private _turnId = 0;
|
|
44
47
|
private speechRunId = 0;
|
|
@@ -136,6 +139,14 @@ class DaemonStateManager {
|
|
|
136
139
|
this._bashApprovalLevel = level;
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
get toolToggles(): ToolToggles {
|
|
143
|
+
return this._toolToggles;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
set toolToggles(toggles: ToolToggles) {
|
|
147
|
+
this._toolToggles = toggles;
|
|
148
|
+
}
|
|
149
|
+
|
|
139
150
|
get outputDeviceName(): string | undefined {
|
|
140
151
|
return this._outputDeviceName;
|
|
141
152
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -259,6 +259,29 @@ export type OnboardingStep =
|
|
|
259
259
|
|
|
260
260
|
export type VoiceInteractionType = "direct" | "review";
|
|
261
261
|
|
|
262
|
+
export type ToolToggleId =
|
|
263
|
+
| "readFile"
|
|
264
|
+
| "runBash"
|
|
265
|
+
| "webSearch"
|
|
266
|
+
| "fetchUrls"
|
|
267
|
+
| "renderUrl"
|
|
268
|
+
| "todoManager"
|
|
269
|
+
| "groundingManager"
|
|
270
|
+
| "subagent";
|
|
271
|
+
|
|
272
|
+
export type ToolToggles = Record<ToolToggleId, boolean>;
|
|
273
|
+
|
|
274
|
+
export const DEFAULT_TOOL_TOGGLES: ToolToggles = {
|
|
275
|
+
readFile: true,
|
|
276
|
+
runBash: true,
|
|
277
|
+
webSearch: true,
|
|
278
|
+
fetchUrls: true,
|
|
279
|
+
renderUrl: true,
|
|
280
|
+
todoManager: true,
|
|
281
|
+
groundingManager: true,
|
|
282
|
+
subagent: true,
|
|
283
|
+
};
|
|
284
|
+
|
|
262
285
|
/**
|
|
263
286
|
* Persisted user preferences.
|
|
264
287
|
*/
|
|
@@ -291,6 +314,8 @@ export interface AppPreferences {
|
|
|
291
314
|
showToolOutput?: boolean;
|
|
292
315
|
/** Bash command approval level */
|
|
293
316
|
bashApprovalLevel?: BashApprovalLevel;
|
|
317
|
+
/** Tool toggles (on/off) */
|
|
318
|
+
toolToggles?: ToolToggles;
|
|
294
319
|
/** Recent user inputs for up/down history navigation (max 20) */
|
|
295
320
|
inputHistory?: string[];
|
|
296
321
|
}
|
package/src/utils/preferences.ts
CHANGED
|
@@ -109,6 +109,16 @@ export function parsePreferences(raw: unknown): AppPreferences | null {
|
|
|
109
109
|
if (typeof raw.showToolOutput === "boolean") {
|
|
110
110
|
prefs.showToolOutput = raw.showToolOutput;
|
|
111
111
|
}
|
|
112
|
+
if (isRecord(raw.toolToggles)) {
|
|
113
|
+
const record = raw.toolToggles;
|
|
114
|
+
const next: Record<string, boolean> = {};
|
|
115
|
+
for (const [k, v] of Object.entries(record)) {
|
|
116
|
+
if (typeof v === "boolean") {
|
|
117
|
+
next[k] = v;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
prefs.toolToggles = next as AppPreferences["toolToggles"];
|
|
121
|
+
}
|
|
112
122
|
if (
|
|
113
123
|
raw.bashApprovalLevel === "none" ||
|
|
114
124
|
raw.bashApprovalLevel === "dangerous" ||
|