@makefinks/daemon 0.1.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/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/cli.js +22 -0
- package/package.json +79 -0
- package/src/ai/agent-turn-runner.ts +130 -0
- package/src/ai/daemon-ai.ts +403 -0
- package/src/ai/exa-client.ts +21 -0
- package/src/ai/exa-fetch-cache.ts +104 -0
- package/src/ai/model-config.ts +99 -0
- package/src/ai/sanitize-messages.ts +83 -0
- package/src/ai/system-prompt.ts +363 -0
- package/src/ai/tools/fetch-urls.ts +187 -0
- package/src/ai/tools/grounding-manager.ts +94 -0
- package/src/ai/tools/index.ts +52 -0
- package/src/ai/tools/read-file.ts +100 -0
- package/src/ai/tools/render-url.ts +275 -0
- package/src/ai/tools/run-bash.ts +224 -0
- package/src/ai/tools/subagents.ts +195 -0
- package/src/ai/tools/todo-manager.ts +150 -0
- package/src/ai/tools/web-search.ts +91 -0
- package/src/app/App.tsx +711 -0
- package/src/app/components/AppOverlays.tsx +131 -0
- package/src/app/components/AvatarLayer.tsx +51 -0
- package/src/app/components/ConversationPane.tsx +476 -0
- package/src/avatar/DaemonAvatarRenderable.ts +343 -0
- package/src/avatar/daemon-avatar-rig.ts +1165 -0
- package/src/avatar-preview.ts +186 -0
- package/src/cli.ts +26 -0
- package/src/components/ApiKeyInput.tsx +99 -0
- package/src/components/ApiKeyStep.tsx +95 -0
- package/src/components/ApprovalPicker.tsx +109 -0
- package/src/components/ContentBlockView.tsx +141 -0
- package/src/components/DaemonText.tsx +34 -0
- package/src/components/DeviceMenu.tsx +166 -0
- package/src/components/GroundingBadge.tsx +21 -0
- package/src/components/GroundingMenu.tsx +310 -0
- package/src/components/HotkeysPane.tsx +115 -0
- package/src/components/InlineStatusIndicator.tsx +106 -0
- package/src/components/ModelMenu.tsx +411 -0
- package/src/components/OnboardingOverlay.tsx +446 -0
- package/src/components/ProviderMenu.tsx +177 -0
- package/src/components/SessionMenu.tsx +297 -0
- package/src/components/SettingsMenu.tsx +291 -0
- package/src/components/StatusBar.tsx +126 -0
- package/src/components/TokenUsageDisplay.tsx +92 -0
- package/src/components/ToolCallView.tsx +113 -0
- package/src/components/TypingInputBar.tsx +131 -0
- package/src/components/tool-layouts/components.tsx +120 -0
- package/src/components/tool-layouts/defaults.ts +9 -0
- package/src/components/tool-layouts/index.ts +22 -0
- package/src/components/tool-layouts/layouts/bash.ts +110 -0
- package/src/components/tool-layouts/layouts/grounding.tsx +98 -0
- package/src/components/tool-layouts/layouts/index.ts +8 -0
- package/src/components/tool-layouts/layouts/read-file.ts +59 -0
- package/src/components/tool-layouts/layouts/subagent.tsx +118 -0
- package/src/components/tool-layouts/layouts/system-info.ts +8 -0
- package/src/components/tool-layouts/layouts/todo.tsx +139 -0
- package/src/components/tool-layouts/layouts/url-tools.ts +220 -0
- package/src/components/tool-layouts/layouts/web-search.ts +110 -0
- package/src/components/tool-layouts/registry.ts +17 -0
- package/src/components/tool-layouts/types.ts +94 -0
- package/src/hooks/daemon-event-handlers.ts +944 -0
- package/src/hooks/keyboard-handlers.ts +399 -0
- package/src/hooks/menu-navigation.ts +147 -0
- package/src/hooks/use-app-audio-devices-loader.ts +71 -0
- package/src/hooks/use-app-callbacks.ts +202 -0
- package/src/hooks/use-app-context-builder.ts +159 -0
- package/src/hooks/use-app-display-state.ts +162 -0
- package/src/hooks/use-app-menus.ts +51 -0
- package/src/hooks/use-app-model-pricing-loader.ts +45 -0
- package/src/hooks/use-app-model.ts +123 -0
- package/src/hooks/use-app-openrouter-models-loader.ts +44 -0
- package/src/hooks/use-app-openrouter-provider-loader.ts +35 -0
- package/src/hooks/use-app-preferences-bootstrap.ts +212 -0
- package/src/hooks/use-app-sessions.ts +105 -0
- package/src/hooks/use-app-settings.ts +62 -0
- package/src/hooks/use-conversation-manager.ts +163 -0
- package/src/hooks/use-copy-on-select.ts +50 -0
- package/src/hooks/use-daemon-events.ts +396 -0
- package/src/hooks/use-daemon-keyboard.ts +397 -0
- package/src/hooks/use-grounding.ts +46 -0
- package/src/hooks/use-input-history.ts +92 -0
- package/src/hooks/use-menu-keyboard.ts +93 -0
- package/src/hooks/use-playwright-notification.ts +23 -0
- package/src/hooks/use-reasoning-animation.ts +97 -0
- package/src/hooks/use-response-timer.ts +55 -0
- package/src/hooks/use-tool-approval.tsx +202 -0
- package/src/hooks/use-typing-mode.ts +137 -0
- package/src/hooks/use-voice-dependencies-notification.ts +37 -0
- package/src/index.tsx +48 -0
- package/src/scripts/setup-browsers.ts +42 -0
- package/src/state/app-context.tsx +160 -0
- package/src/state/daemon-events.ts +67 -0
- package/src/state/daemon-state.ts +493 -0
- package/src/state/migrations/001-init.ts +33 -0
- package/src/state/migrations/index.ts +8 -0
- package/src/state/model-history-store.ts +45 -0
- package/src/state/runtime-context.ts +21 -0
- package/src/state/session-store.ts +359 -0
- package/src/types/index.ts +405 -0
- package/src/types/theme.ts +52 -0
- package/src/ui/constants.ts +157 -0
- package/src/utils/clipboard.ts +89 -0
- package/src/utils/debug-logger.ts +69 -0
- package/src/utils/formatters.ts +242 -0
- package/src/utils/js-rendering.ts +77 -0
- package/src/utils/markdown-tables.ts +234 -0
- package/src/utils/model-metadata.ts +191 -0
- package/src/utils/openrouter-endpoints.ts +212 -0
- package/src/utils/openrouter-models.ts +205 -0
- package/src/utils/openrouter-pricing.ts +59 -0
- package/src/utils/openrouter-reported-cost.ts +16 -0
- package/src/utils/paste.ts +33 -0
- package/src/utils/preferences.ts +289 -0
- package/src/utils/text-fragment.ts +39 -0
- package/src/utils/tool-output-preview.ts +250 -0
- package/src/utils/voice-dependencies.ts +107 -0
- package/src/utils/workspace-manager.ts +85 -0
- package/src/voice/audio-recorder.ts +579 -0
- package/src/voice/mic-level.ts +35 -0
- package/src/voice/tts/openai-tts-stream.ts +222 -0
- package/src/voice/tts/speech-controller.ts +64 -0
- package/src/voice/tts/tts-player.ts +257 -0
- package/src/voice/voice-input-controller.ts +96 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the DAEMON application.
|
|
3
|
+
* Consolidates types used across multiple modules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ModelMessage } from "ai";
|
|
7
|
+
|
|
8
|
+
// Re-export AI SDK types for convenience
|
|
9
|
+
export type { ModelMessage } from "ai";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tool result output format expected by the AI SDK.
|
|
13
|
+
*/
|
|
14
|
+
export type ToolResultOutput =
|
|
15
|
+
| { type: "text"; value: string }
|
|
16
|
+
| { type: "json"; value: Record<string, unknown> | unknown[] | string | number | boolean | null }
|
|
17
|
+
| { type: "error-text"; value: string }
|
|
18
|
+
| { type: "error-json"; value: Record<string, unknown> | unknown[] | string | number | boolean | null };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* DAEMON operational states
|
|
22
|
+
*/
|
|
23
|
+
export enum DaemonState {
|
|
24
|
+
IDLE = "idle",
|
|
25
|
+
LISTENING = "listening",
|
|
26
|
+
TRANSCRIBING = "transcribing",
|
|
27
|
+
RESPONDING = "responding",
|
|
28
|
+
SPEAKING = "speaking",
|
|
29
|
+
TYPING = "typing",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Token usage information from API response
|
|
34
|
+
*/
|
|
35
|
+
export interface TokenUsage {
|
|
36
|
+
promptTokens: number;
|
|
37
|
+
completionTokens: number;
|
|
38
|
+
totalTokens: number;
|
|
39
|
+
reasoningTokens?: number;
|
|
40
|
+
cachedInputTokens?: number;
|
|
41
|
+
cost?: number;
|
|
42
|
+
subagentTotalTokens?: number;
|
|
43
|
+
subagentPromptTokens?: number;
|
|
44
|
+
subagentCompletionTokens?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Status of a tool call or subagent step
|
|
49
|
+
* - "streaming": Tool call detected, name known, but input still streaming
|
|
50
|
+
* - "running": Tool call input complete, execution in progress
|
|
51
|
+
* - "completed": Tool execution finished successfully
|
|
52
|
+
* - "failed": Tool execution failed
|
|
53
|
+
*/
|
|
54
|
+
export type ToolCallStatus = "streaming" | "running" | "completed" | "failed" | "awaiting_approval";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A step/action taken by a subagent, displayed in the UI
|
|
58
|
+
*/
|
|
59
|
+
export interface SubagentStep {
|
|
60
|
+
toolName: string;
|
|
61
|
+
status: ToolCallStatus;
|
|
62
|
+
input?: unknown;
|
|
63
|
+
summary?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tool call representation for UI rendering
|
|
68
|
+
*/
|
|
69
|
+
export interface ToolCall {
|
|
70
|
+
name: string;
|
|
71
|
+
input: unknown;
|
|
72
|
+
toolCallId?: string;
|
|
73
|
+
/** For subagent tool calls - tracks nested tool invocations */
|
|
74
|
+
subagentSteps?: SubagentStep[];
|
|
75
|
+
/** Current status of the tool call */
|
|
76
|
+
status?: ToolCallStatus;
|
|
77
|
+
/** Snapshot of todos at the time of the tool call (for todoManager) */
|
|
78
|
+
todoSnapshot?: Array<{ content: string; status: string }>;
|
|
79
|
+
/** Error message when status is "failed" */
|
|
80
|
+
error?: string;
|
|
81
|
+
/** Result of user approval decision (only set for tools that required approval) */
|
|
82
|
+
approvalResult?: "approved" | "denied";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Content block types for interleaved UI display.
|
|
87
|
+
* These are derived from ModelMessage content for rendering.
|
|
88
|
+
*/
|
|
89
|
+
export type ContentBlock =
|
|
90
|
+
| { type: "reasoning"; content: string; durationMs?: number }
|
|
91
|
+
| { type: "tool"; call: ToolCall; result?: unknown }
|
|
92
|
+
| { type: "text"; content: string };
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* UI-specific conversation message that wraps the core AI SDK messages.
|
|
96
|
+
* The `messages` field contains the actual AI SDK messages for API calls.
|
|
97
|
+
* The other fields are for UI display convenience.
|
|
98
|
+
*/
|
|
99
|
+
export interface ConversationMessage {
|
|
100
|
+
id: number;
|
|
101
|
+
type: "user" | "daemon";
|
|
102
|
+
content: string;
|
|
103
|
+
messages: ModelMessage[];
|
|
104
|
+
contentBlocks?: ContentBlock[];
|
|
105
|
+
pending?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Persisted session snapshot for reloading UI and model context.
|
|
110
|
+
*/
|
|
111
|
+
export interface SessionSnapshot {
|
|
112
|
+
conversationHistory: ConversationMessage[];
|
|
113
|
+
sessionUsage: TokenUsage;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Session metadata for listing and selection.
|
|
118
|
+
*/
|
|
119
|
+
export interface SessionInfo {
|
|
120
|
+
id: string;
|
|
121
|
+
title: string;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
updatedAt: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Color theme for avatar states.
|
|
128
|
+
* Used by both daemon-state.ts and daemon-avatar-rig.ts.
|
|
129
|
+
*/
|
|
130
|
+
export interface AvatarColorTheme {
|
|
131
|
+
primary: number;
|
|
132
|
+
glow: number;
|
|
133
|
+
eye: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Alias for avatar color theme used by the rig.
|
|
138
|
+
* @deprecated Use AvatarColorTheme directly
|
|
139
|
+
*/
|
|
140
|
+
export type DaemonColorTheme = AvatarColorTheme;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Transcription result from voice input
|
|
144
|
+
*/
|
|
145
|
+
export interface TranscriptionResult {
|
|
146
|
+
text: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Callbacks for streaming AI responses
|
|
151
|
+
*/
|
|
152
|
+
export interface StreamCallbacks {
|
|
153
|
+
onToken?: (token: string) => void;
|
|
154
|
+
onReasoningToken?: (token: string) => void;
|
|
155
|
+
onToolCallStart?: (toolName: string, toolCallId: string) => void;
|
|
156
|
+
onToolCall?: (toolName: string, args: unknown, toolCallId?: string) => void;
|
|
157
|
+
onToolResult?: (toolName: string, result: unknown, toolCallId?: string) => void;
|
|
158
|
+
onToolApprovalRequest?: (request: ToolApprovalRequest) => void;
|
|
159
|
+
onAwaitingApprovals?: (
|
|
160
|
+
pendingApprovals: ToolApprovalRequest[],
|
|
161
|
+
respondToApprovals: (responses: ToolApprovalResponse[]) => void
|
|
162
|
+
) => void;
|
|
163
|
+
onSubagentToolCall?: (toolCallId: string, toolName: string, input?: unknown) => void;
|
|
164
|
+
onSubagentUsage?: (usage: TokenUsage) => void;
|
|
165
|
+
onSubagentToolResult?: (toolCallId: string, toolName: string, success: boolean) => void;
|
|
166
|
+
onSubagentComplete?: (toolCallId: string, success: boolean) => void;
|
|
167
|
+
onStepUsage?: (usage: TokenUsage) => void;
|
|
168
|
+
onComplete?: (
|
|
169
|
+
fullText: string,
|
|
170
|
+
responseMessages: ModelMessage[],
|
|
171
|
+
usage?: TokenUsage,
|
|
172
|
+
/** The final assistant text only (for TTS - excludes intermediate text from tool-call steps) */
|
|
173
|
+
finalText?: string
|
|
174
|
+
) => void;
|
|
175
|
+
onError?: (error: Error) => void;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Audio device information
|
|
180
|
+
*/
|
|
181
|
+
export interface AudioDevice {
|
|
182
|
+
name: string;
|
|
183
|
+
isDefault?: boolean;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Interaction mode for DAEMON responses.
|
|
188
|
+
* - "text": Terminal output with markdown formatting
|
|
189
|
+
* - "voice": Speech-to-speech, natural conversational responses
|
|
190
|
+
*/
|
|
191
|
+
export type InteractionMode = "text" | "voice";
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Speech speed settings (1.0x to 2.0x)
|
|
195
|
+
*/
|
|
196
|
+
export type SpeechSpeed = 1.0 | 1.25 | 1.5 | 1.75 | 2.0;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Reasoning effort / cognitive depth settings.
|
|
200
|
+
* Controls how deeply the model reasons about responses.
|
|
201
|
+
* - "low": SURFACE - minimal reasoning
|
|
202
|
+
* - "medium": DEEP - moderate reasoning (default)
|
|
203
|
+
* - "high": ABYSSAL - maximum reasoning depth
|
|
204
|
+
*/
|
|
205
|
+
export type ReasoningEffort = "low" | "medium" | "high";
|
|
206
|
+
|
|
207
|
+
/** Display labels for reasoning effort levels */
|
|
208
|
+
export const REASONING_EFFORT_LABELS: Record<ReasoningEffort, string> = {
|
|
209
|
+
low: "LOW",
|
|
210
|
+
medium: "MEDIUM",
|
|
211
|
+
high: "HIGH",
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** Ordered list of reasoning effort levels for cycling */
|
|
215
|
+
export const REASONING_EFFORT_LEVELS: ReasoningEffort[] = ["low", "medium", "high"];
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Bash tool approval level settings.
|
|
219
|
+
* Controls when user approval is required for bash commands.
|
|
220
|
+
* - "none": No approval required for any bash commands
|
|
221
|
+
* - "dangerous": Approval required only for potentially dangerous commands
|
|
222
|
+
* - "all": Approval required for all bash commands
|
|
223
|
+
*/
|
|
224
|
+
export type BashApprovalLevel = "none" | "dangerous" | "all";
|
|
225
|
+
|
|
226
|
+
/** Display labels for bash approval levels */
|
|
227
|
+
export const BASH_APPROVAL_LABELS: Record<BashApprovalLevel, string> = {
|
|
228
|
+
none: "NONE",
|
|
229
|
+
dangerous: "DANGEROUS",
|
|
230
|
+
all: "ALL",
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/** Ordered list of bash approval levels for cycling */
|
|
234
|
+
export const BASH_APPROVAL_LEVELS: BashApprovalLevel[] = ["none", "dangerous", "all"];
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Onboarding flow steps.
|
|
238
|
+
* - intro: Welcome screen
|
|
239
|
+
* - openrouter_key: OpenRouter API key input (for AI models)
|
|
240
|
+
* - openai_key: OpenAI API key input (for transcription)
|
|
241
|
+
* - exa_key: Exa API key input (for web search)
|
|
242
|
+
* - device: Audio device selection
|
|
243
|
+
* - model: AI model selection
|
|
244
|
+
* - complete: Onboarding finished
|
|
245
|
+
*/
|
|
246
|
+
export type OnboardingStep =
|
|
247
|
+
| "intro"
|
|
248
|
+
| "openrouter_key"
|
|
249
|
+
| "openai_key"
|
|
250
|
+
| "exa_key"
|
|
251
|
+
| "device"
|
|
252
|
+
| "model"
|
|
253
|
+
| "settings"
|
|
254
|
+
| "complete";
|
|
255
|
+
|
|
256
|
+
export type VoiceInteractionType = "direct" | "review";
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Persisted user preferences.
|
|
260
|
+
*/
|
|
261
|
+
export interface AppPreferences {
|
|
262
|
+
version: number;
|
|
263
|
+
createdAt: string;
|
|
264
|
+
updatedAt: string;
|
|
265
|
+
onboardingCompleted: boolean;
|
|
266
|
+
audioDeviceName?: string;
|
|
267
|
+
audioOutputDeviceName?: string;
|
|
268
|
+
modelId?: string;
|
|
269
|
+
/**
|
|
270
|
+
* OpenRouter inference provider slug (aka `provider` routing tag), e.g. "openai".
|
|
271
|
+
* When unset, OpenRouter will automatically route to the best available provider.
|
|
272
|
+
*/
|
|
273
|
+
openRouterProviderTag?: string;
|
|
274
|
+
interactionMode?: InteractionMode;
|
|
275
|
+
voiceInteractionType?: VoiceInteractionType;
|
|
276
|
+
speechSpeed?: SpeechSpeed;
|
|
277
|
+
reasoningEffort?: ReasoningEffort;
|
|
278
|
+
/** OpenRouter API key for AI model responses */
|
|
279
|
+
openRouterApiKey?: string;
|
|
280
|
+
/** OpenAI API key for voice transcription */
|
|
281
|
+
openAiApiKey?: string;
|
|
282
|
+
/** Exa API key for web search */
|
|
283
|
+
exaApiKey?: string;
|
|
284
|
+
/** Show full reasoning blocks instead of compact ticker */
|
|
285
|
+
showFullReasoning?: boolean;
|
|
286
|
+
/** Show tool output previews */
|
|
287
|
+
showToolOutput?: boolean;
|
|
288
|
+
/** Bash command approval level */
|
|
289
|
+
bashApprovalLevel?: BashApprovalLevel;
|
|
290
|
+
/** Recent user inputs for up/down history navigation (max 20) */
|
|
291
|
+
inputHistory?: string[];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Model pricing information (per 1M tokens)
|
|
296
|
+
*/
|
|
297
|
+
export interface ModelPricing {
|
|
298
|
+
prompt: number;
|
|
299
|
+
completion: number;
|
|
300
|
+
/**
|
|
301
|
+
* Discounted price for cache reads (per 1M input tokens).
|
|
302
|
+
* Present only for some providers/models on OpenRouter.
|
|
303
|
+
*/
|
|
304
|
+
inputCacheRead?: number;
|
|
305
|
+
/**
|
|
306
|
+
* Price for cache writes (per 1M input tokens).
|
|
307
|
+
* Present only for some providers/models on OpenRouter.
|
|
308
|
+
*/
|
|
309
|
+
inputCacheWrite?: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Model option for selection menus
|
|
314
|
+
*/
|
|
315
|
+
export interface ModelOption {
|
|
316
|
+
id: string;
|
|
317
|
+
name: string;
|
|
318
|
+
pricing?: ModelPricing;
|
|
319
|
+
contextLength?: number;
|
|
320
|
+
supportsCaching?: boolean;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Todo item status
|
|
325
|
+
*/
|
|
326
|
+
export type TodoStatus = "pending" | "in_progress" | "completed" | "cancelled";
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Tool approval request from the AI SDK.
|
|
330
|
+
* Emitted when a tool with needsApproval: true is called.
|
|
331
|
+
*/
|
|
332
|
+
export interface ToolApprovalRequest {
|
|
333
|
+
approvalId: string;
|
|
334
|
+
toolName: string;
|
|
335
|
+
toolCallId: string;
|
|
336
|
+
input: unknown;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Tool approval response to send back to the agent.
|
|
341
|
+
*/
|
|
342
|
+
export interface ToolApprovalResponse {
|
|
343
|
+
approvalId: string;
|
|
344
|
+
approved: boolean;
|
|
345
|
+
reason?: string;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Todo item for task tracking
|
|
350
|
+
*/
|
|
351
|
+
export interface TodoItem {
|
|
352
|
+
content: string;
|
|
353
|
+
status: TodoStatus;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Event emitter interface for subagent progress updates.
|
|
358
|
+
* Used to connect subagent tool execution to the UI.
|
|
359
|
+
*/
|
|
360
|
+
export interface SubagentProgressEmitter {
|
|
361
|
+
onSubagentToolCall: (toolCallId: string, toolName: string, input?: unknown) => void;
|
|
362
|
+
onSubagentUsage: (usage: {
|
|
363
|
+
promptTokens: number;
|
|
364
|
+
completionTokens: number;
|
|
365
|
+
totalTokens: number;
|
|
366
|
+
reasoningTokens?: number;
|
|
367
|
+
cachedInputTokens?: number;
|
|
368
|
+
cost?: number;
|
|
369
|
+
}) => void;
|
|
370
|
+
onSubagentToolResult: (toolCallId: string, toolName: string, success: boolean) => void;
|
|
371
|
+
onSubagentComplete: (toolCallId: string, success: boolean) => void;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Grounding: Source information for a grounded statement.
|
|
376
|
+
*/
|
|
377
|
+
export interface GroundingSource {
|
|
378
|
+
url: string;
|
|
379
|
+
title?: string;
|
|
380
|
+
/** Short excerpt copied from fetched content (human-readable). */
|
|
381
|
+
quote: string;
|
|
382
|
+
/** Text fragment for deep-linking (short verbatim phrase from source). */
|
|
383
|
+
textFragment: string;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Grounding: A single grounded statement with its source.
|
|
388
|
+
*/
|
|
389
|
+
export interface GroundedStatement {
|
|
390
|
+
id: string;
|
|
391
|
+
statement: string;
|
|
392
|
+
source: GroundingSource;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Grounding: A map of grounded statements for a single response.
|
|
397
|
+
*/
|
|
398
|
+
export interface GroundingMap {
|
|
399
|
+
id: string;
|
|
400
|
+
sessionId: string;
|
|
401
|
+
/** The DAEMON response message this map belongs to (or turn id). */
|
|
402
|
+
messageId: number;
|
|
403
|
+
createdAt: string;
|
|
404
|
+
items: GroundedStatement[];
|
|
405
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatar and UI color themes for different daemon states.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DaemonState } from "./index";
|
|
6
|
+
import type { AvatarColorTheme } from "./index";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Avatar color themes for each daemon state
|
|
10
|
+
*/
|
|
11
|
+
export const STATE_COLORS: Record<DaemonState, AvatarColorTheme> = {
|
|
12
|
+
[DaemonState.IDLE]: {
|
|
13
|
+
primary: 0x9ca3af, // Gray
|
|
14
|
+
glow: 0x67e8f9, // Cyan glow
|
|
15
|
+
eye: 0xff0000, // Red
|
|
16
|
+
},
|
|
17
|
+
[DaemonState.LISTENING]: {
|
|
18
|
+
primary: 0x22d3ee, // Bright cyan
|
|
19
|
+
glow: 0x67e8f9,
|
|
20
|
+
eye: 0x22d3ee,
|
|
21
|
+
},
|
|
22
|
+
[DaemonState.TRANSCRIBING]: {
|
|
23
|
+
primary: 0xa78bfa, // Purple
|
|
24
|
+
glow: 0xc4b5fd,
|
|
25
|
+
eye: 0xa78bfa,
|
|
26
|
+
},
|
|
27
|
+
[DaemonState.RESPONDING]: {
|
|
28
|
+
primary: 0x4ade80, // Green
|
|
29
|
+
glow: 0x86efac,
|
|
30
|
+
eye: 0x4ade80,
|
|
31
|
+
},
|
|
32
|
+
[DaemonState.SPEAKING]: {
|
|
33
|
+
primary: 0x22d3ee, // Cyan - voice output
|
|
34
|
+
glow: 0x67e8f9,
|
|
35
|
+
eye: 0x22d3ee,
|
|
36
|
+
},
|
|
37
|
+
[DaemonState.TYPING]: {
|
|
38
|
+
primary: 0x9ca3af,
|
|
39
|
+
glow: 0x67e8f9,
|
|
40
|
+
eye: 0xff0000,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Special color theme for reasoning phase (before speaking).
|
|
46
|
+
* Soft blue-gray - contemplative, calm, less aggressive.
|
|
47
|
+
*/
|
|
48
|
+
export const REASONING_COLORS: AvatarColorTheme = {
|
|
49
|
+
primary: 0x64748b, // Slate gray-blue
|
|
50
|
+
glow: 0x94a3b8, // Lighter slate
|
|
51
|
+
eye: 0x94a3b8, // Soft glow
|
|
52
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI constants including colors, status text, and markdown syntax styles.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SyntaxStyle, RGBA } from "@opentui/core";
|
|
6
|
+
import { DaemonState } from "../types";
|
|
7
|
+
|
|
8
|
+
// Status text displayed for each daemon state
|
|
9
|
+
export const STATUS_TEXT: Record<DaemonState, string> = {
|
|
10
|
+
[DaemonState.IDLE]: "SPACE speak · SHIFT+TAB type · M models · S settings · L sessions · ? hotkeys",
|
|
11
|
+
[DaemonState.LISTENING]: "LISTENING · SPACE stop · ESC cancel",
|
|
12
|
+
[DaemonState.TRANSCRIBING]: "PROCESSING VOICE... · ESC cancel",
|
|
13
|
+
[DaemonState.RESPONDING]: "DAEMON SPEAKS... · ESC cancel · T reasoning",
|
|
14
|
+
[DaemonState.SPEAKING]: "DAEMON SPEAKS... · ESC stop",
|
|
15
|
+
[DaemonState.TYPING]: "TYPE MODE · ENTER submit · ESC cancel",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Hex colors for status bar text per state
|
|
19
|
+
export const STATE_COLOR_HEX: Record<DaemonState, string> = {
|
|
20
|
+
[DaemonState.IDLE]: "#9ca3af",
|
|
21
|
+
[DaemonState.LISTENING]: "#22d3ee",
|
|
22
|
+
[DaemonState.TRANSCRIBING]: "#a78bfa",
|
|
23
|
+
[DaemonState.RESPONDING]: "#4ade80",
|
|
24
|
+
[DaemonState.SPEAKING]: "#22d3ee",
|
|
25
|
+
[DaemonState.TYPING]: "#fbbf24",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Animation settings for reasoning text ticker
|
|
29
|
+
export const REASONING_ANIMATION = {
|
|
30
|
+
LINE_WIDTH: 120,
|
|
31
|
+
CHARS_PER_TICK: 6,
|
|
32
|
+
TICK_INTERVAL_MS: 16,
|
|
33
|
+
INTENSITY: 0.5,
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
// UI colors
|
|
37
|
+
export const COLORS = {
|
|
38
|
+
BACKGROUND: "#050509",
|
|
39
|
+
LISTENING_DIM: "#050509A8",
|
|
40
|
+
ERROR: "#ef4444",
|
|
41
|
+
DAEMON_ERROR: "#ef4444",
|
|
42
|
+
REASONING: "#a78bfa",
|
|
43
|
+
REASONING_DIM: "#525252",
|
|
44
|
+
TOOLS: "#3f4651",
|
|
45
|
+
TOOL_INPUT_BG: "#0a0a0f60",
|
|
46
|
+
TOOL_INPUT_BORDER: "#3f465180",
|
|
47
|
+
TOOL_INPUT_TEXT: "#9ca3af",
|
|
48
|
+
USER_LABEL: "#bfdbfe",
|
|
49
|
+
USER_TEXT: "#ffffff",
|
|
50
|
+
USER_BG: "#1e293b",
|
|
51
|
+
DAEMON_LABEL: "#22d3ee",
|
|
52
|
+
DAEMON_TEXT: "#4ade80",
|
|
53
|
+
TYPING_PROMPT: "#fbbf24",
|
|
54
|
+
EMPTY_STATE: "#374151",
|
|
55
|
+
MENU_BORDER: "#22d3ee",
|
|
56
|
+
MENU_BG: "#0a0a0f",
|
|
57
|
+
MENU_SELECTED_BG: "#1a1a2e",
|
|
58
|
+
MENU_TEXT: "#9ca3af",
|
|
59
|
+
TOKEN_USAGE: "#6b7280",
|
|
60
|
+
TOKEN_USAGE_LABEL: "#525252",
|
|
61
|
+
STATUS_BORDER: "#3f4651",
|
|
62
|
+
WORKING_SPINNER_BORDER: "#1e293b",
|
|
63
|
+
|
|
64
|
+
// General status colors (used across tool views, todos, subagent steps)
|
|
65
|
+
STATUS_RUNNING: "#fbbf24",
|
|
66
|
+
STATUS_COMPLETED: "#4ade80",
|
|
67
|
+
STATUS_FAILED: "#ef4444",
|
|
68
|
+
STATUS_PENDING: "#9ca3af",
|
|
69
|
+
STATUS_DONE_DIM: "#2d333d",
|
|
70
|
+
STATUS_APPROVAL: "#f472b6",
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
// Markdown syntax style for daemon responses - alien/cultic aesthetic
|
|
74
|
+
export const DAEMON_MARKDOWN_STYLE = SyntaxStyle.fromStyles({
|
|
75
|
+
// Default text inherits daemon green
|
|
76
|
+
default: { fg: RGBA.fromHex(COLORS.DAEMON_TEXT) },
|
|
77
|
+
// Headings - cyan hierarchy (daemon label color)
|
|
78
|
+
"markup.heading.1": { fg: RGBA.fromHex("#22d3ee"), bold: true },
|
|
79
|
+
"markup.heading.2": { fg: RGBA.fromHex("#06b6d4"), bold: true },
|
|
80
|
+
"markup.heading.3": { fg: RGBA.fromHex("#0891b2"), bold: true },
|
|
81
|
+
"markup.heading.4": { fg: RGBA.fromHex("#0e7490") },
|
|
82
|
+
"markup.heading.5": { fg: RGBA.fromHex("#155e75") },
|
|
83
|
+
"markup.heading.6": { fg: RGBA.fromHex("#164e63") },
|
|
84
|
+
// Emphasis
|
|
85
|
+
"markup.strong": { fg: RGBA.fromHex("#ffffff"), bold: true },
|
|
86
|
+
"markup.italic": { fg: RGBA.fromHex("#a78bfa"), italic: true },
|
|
87
|
+
"markup.strikethrough": { fg: RGBA.fromHex("#6b7280"), dim: true },
|
|
88
|
+
// Code - purple/violet (reasoning color)
|
|
89
|
+
"markup.raw": { fg: RGBA.fromHex("#c4b5fd") },
|
|
90
|
+
"markup.raw.block": { fg: RGBA.fromHex("#c4b5fd") },
|
|
91
|
+
// Links - cyan like daemon label
|
|
92
|
+
"markup.link": { fg: RGBA.fromHex("#22d3ee") },
|
|
93
|
+
"markup.link.url": { fg: RGBA.fromHex("#67e8f9"), underline: true },
|
|
94
|
+
"markup.link.label": { fg: RGBA.fromHex("#22d3ee") },
|
|
95
|
+
// Lists
|
|
96
|
+
"markup.list": { fg: RGBA.fromHex("#9ca3af") },
|
|
97
|
+
"markup.list.checked": { fg: RGBA.fromHex("#4ade80") },
|
|
98
|
+
"markup.list.unchecked": { fg: RGBA.fromHex("#6b7280") },
|
|
99
|
+
// Quotes - dimmed
|
|
100
|
+
"markup.quote": { fg: RGBA.fromHex("#9ca3af"), italic: true },
|
|
101
|
+
// Code block syntax highlighting
|
|
102
|
+
keyword: { fg: RGBA.fromHex("#f472b6") },
|
|
103
|
+
string: { fg: RGBA.fromHex("#a5d6ff") },
|
|
104
|
+
comment: { fg: RGBA.fromHex("#6b7280"), italic: true },
|
|
105
|
+
number: { fg: RGBA.fromHex("#fbbf24") },
|
|
106
|
+
function: { fg: RGBA.fromHex("#7dd3fc") },
|
|
107
|
+
type: { fg: RGBA.fromHex("#c4b5fd") },
|
|
108
|
+
variable: { fg: RGBA.fromHex("#4ade80") },
|
|
109
|
+
constant: { fg: RGBA.fromHex("#fbbf24") },
|
|
110
|
+
operator: { fg: RGBA.fromHex("#f472b6") },
|
|
111
|
+
punctuation: { fg: RGBA.fromHex("#9ca3af") },
|
|
112
|
+
"punctuation.special": { fg: RGBA.fromHex("#6b7280") },
|
|
113
|
+
label: { fg: RGBA.fromHex("#67e8f9") },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Markdown syntax style for reasoning - dimmed/gray aesthetic
|
|
117
|
+
export const REASONING_MARKDOWN_STYLE = SyntaxStyle.fromStyles({
|
|
118
|
+
// Default text inherits reasoning dim color
|
|
119
|
+
default: { fg: RGBA.fromHex(COLORS.REASONING_DIM) },
|
|
120
|
+
// Headings - slightly brighter
|
|
121
|
+
"markup.heading.1": { fg: RGBA.fromHex("#a3a3a3"), bold: true },
|
|
122
|
+
"markup.heading.2": { fg: RGBA.fromHex("#a3a3a3"), bold: true },
|
|
123
|
+
"markup.heading.3": { fg: RGBA.fromHex("#a3a3a3"), bold: true },
|
|
124
|
+
"markup.heading.4": { fg: RGBA.fromHex("#a3a3a3") },
|
|
125
|
+
"markup.heading.5": { fg: RGBA.fromHex("#a3a3a3") },
|
|
126
|
+
"markup.heading.6": { fg: RGBA.fromHex("#a3a3a3") },
|
|
127
|
+
// Emphasis
|
|
128
|
+
"markup.strong": { fg: RGBA.fromHex("#d4d4d4"), bold: true },
|
|
129
|
+
"markup.italic": { fg: RGBA.fromHex("#a78bfa"), italic: true }, // Keep purple for italic
|
|
130
|
+
"markup.strikethrough": { fg: RGBA.fromHex("#404040"), dim: true },
|
|
131
|
+
// Code - keep purple theme but distinct
|
|
132
|
+
"markup.raw": { fg: RGBA.fromHex("#8b5cf6") }, // Violet-500
|
|
133
|
+
"markup.raw.block": { fg: RGBA.fromHex("#8b5cf6") },
|
|
134
|
+
// Links
|
|
135
|
+
"markup.link": { fg: RGBA.fromHex("#0891b2") }, // Cyan-600
|
|
136
|
+
"markup.link.url": { fg: RGBA.fromHex("#0891b2"), underline: true },
|
|
137
|
+
"markup.link.label": { fg: RGBA.fromHex("#0891b2") },
|
|
138
|
+
// Lists
|
|
139
|
+
"markup.list": { fg: RGBA.fromHex("#525252") },
|
|
140
|
+
"markup.list.checked": { fg: RGBA.fromHex("#a3a3a3") },
|
|
141
|
+
"markup.list.unchecked": { fg: RGBA.fromHex("#525252") },
|
|
142
|
+
// Quotes
|
|
143
|
+
"markup.quote": { fg: RGBA.fromHex("#525252"), italic: true },
|
|
144
|
+
// Code block syntax highlighting - dimmed versions
|
|
145
|
+
keyword: { fg: RGBA.fromHex("#db2777") }, // Pink-600
|
|
146
|
+
string: { fg: RGBA.fromHex("#60a5fa") }, // Blue-400
|
|
147
|
+
comment: { fg: RGBA.fromHex("#404040"), italic: true },
|
|
148
|
+
number: { fg: RGBA.fromHex("#d97706") }, // Amber-600
|
|
149
|
+
function: { fg: RGBA.fromHex("#0ea5e9") }, // Sky-500
|
|
150
|
+
type: { fg: RGBA.fromHex("#8b5cf6") }, // Violet-500
|
|
151
|
+
variable: { fg: RGBA.fromHex("#22c55e") }, // Green-500
|
|
152
|
+
constant: { fg: RGBA.fromHex("#d97706") },
|
|
153
|
+
operator: { fg: RGBA.fromHex("#db2777") },
|
|
154
|
+
punctuation: { fg: RGBA.fromHex("#525252") },
|
|
155
|
+
"punctuation.special": { fg: RGBA.fromHex("#525252") },
|
|
156
|
+
label: { fg: RGBA.fromHex("#0891b2") },
|
|
157
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { platform, release } from "os";
|
|
2
|
+
|
|
3
|
+
async function readFromCommand(command: string, args: string[]): Promise<string> {
|
|
4
|
+
try {
|
|
5
|
+
const proc = Bun.spawn([command, ...args], {
|
|
6
|
+
stdout: "pipe",
|
|
7
|
+
stderr: "ignore",
|
|
8
|
+
});
|
|
9
|
+
const text = await new Response(proc.stdout).text();
|
|
10
|
+
await proc.exited;
|
|
11
|
+
return text;
|
|
12
|
+
} catch {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function writeToCommand(command: string, args: string[], text: string): Promise<boolean> {
|
|
18
|
+
try {
|
|
19
|
+
const proc = Bun.spawn([command, ...args], {
|
|
20
|
+
stdin: "pipe",
|
|
21
|
+
stdout: "ignore",
|
|
22
|
+
stderr: "ignore",
|
|
23
|
+
});
|
|
24
|
+
proc.stdin.write(text);
|
|
25
|
+
proc.stdin.end();
|
|
26
|
+
const exitCode = await proc.exited;
|
|
27
|
+
return exitCode === 0;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function readClipboardText(): Promise<string> {
|
|
34
|
+
const os = platform();
|
|
35
|
+
|
|
36
|
+
if (os === "darwin" && Bun.which("pbpaste")) {
|
|
37
|
+
return readFromCommand("pbpaste", []);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (os === "win32" || release().includes("WSL")) {
|
|
41
|
+
return readFromCommand("powershell", ["-command", "Get-Clipboard -Raw"]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (os === "linux") {
|
|
45
|
+
if (process.env.WAYLAND_DISPLAY && Bun.which("wl-paste")) {
|
|
46
|
+
return readFromCommand("wl-paste", ["-n"]);
|
|
47
|
+
}
|
|
48
|
+
if (Bun.which("xclip")) {
|
|
49
|
+
return readFromCommand("xclip", ["-selection", "clipboard", "-o"]);
|
|
50
|
+
}
|
|
51
|
+
if (Bun.which("xsel")) {
|
|
52
|
+
return readFromCommand("xsel", ["--clipboard", "--output"]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function writeClipboardText(text: string): Promise<boolean> {
|
|
60
|
+
if (!text) return false;
|
|
61
|
+
|
|
62
|
+
const os = platform();
|
|
63
|
+
|
|
64
|
+
if (os === "darwin" && Bun.which("pbcopy")) {
|
|
65
|
+
return writeToCommand("pbcopy", [], text);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (os === "win32" || release().includes("WSL")) {
|
|
69
|
+
return writeToCommand(
|
|
70
|
+
"powershell",
|
|
71
|
+
["-command", "Set-Clipboard -Value ([Console]::In.ReadToEnd())"],
|
|
72
|
+
text
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (os === "linux") {
|
|
77
|
+
if (process.env.WAYLAND_DISPLAY && Bun.which("wl-copy")) {
|
|
78
|
+
return writeToCommand("wl-copy", [], text);
|
|
79
|
+
}
|
|
80
|
+
if (Bun.which("xclip")) {
|
|
81
|
+
return writeToCommand("xclip", ["-selection", "clipboard"], text);
|
|
82
|
+
}
|
|
83
|
+
if (Bun.which("xsel")) {
|
|
84
|
+
return writeToCommand("xsel", ["--clipboard", "--input"], text);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}
|