joonecli 0.1.1 → 0.2.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.
Files changed (147) hide show
  1. package/dist/cli/index.js +4 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/commands/builtinCommands.js +6 -6
  4. package/dist/commands/builtinCommands.js.map +1 -1
  5. package/dist/commands/commandRegistry.d.ts +3 -1
  6. package/dist/commands/commandRegistry.js.map +1 -1
  7. package/dist/core/agentLoop.d.ts +3 -1
  8. package/dist/core/agentLoop.js +17 -7
  9. package/dist/core/agentLoop.js.map +1 -1
  10. package/dist/core/compactor.js +2 -2
  11. package/dist/core/compactor.js.map +1 -1
  12. package/dist/core/contextGuard.d.ts +5 -0
  13. package/dist/core/contextGuard.js +30 -3
  14. package/dist/core/contextGuard.js.map +1 -1
  15. package/dist/core/events.d.ts +45 -0
  16. package/dist/core/events.js +8 -0
  17. package/dist/core/events.js.map +1 -0
  18. package/dist/core/sessionStore.js +3 -2
  19. package/dist/core/sessionStore.js.map +1 -1
  20. package/dist/core/subAgent.js +2 -2
  21. package/dist/core/subAgent.js.map +1 -1
  22. package/dist/core/tokenCounter.d.ts +8 -1
  23. package/dist/core/tokenCounter.js +28 -0
  24. package/dist/core/tokenCounter.js.map +1 -1
  25. package/dist/middleware/permission.js +1 -0
  26. package/dist/middleware/permission.js.map +1 -1
  27. package/dist/tools/browser.js +4 -1
  28. package/dist/tools/browser.js.map +1 -1
  29. package/dist/tools/index.d.ts +2 -1
  30. package/dist/tools/index.js +11 -3
  31. package/dist/tools/index.js.map +1 -1
  32. package/dist/tools/installHostDeps.d.ts +2 -0
  33. package/dist/tools/installHostDeps.js +37 -0
  34. package/dist/tools/installHostDeps.js.map +1 -0
  35. package/dist/tools/router.js +1 -0
  36. package/dist/tools/router.js.map +1 -1
  37. package/dist/tools/spawnAgent.js +3 -1
  38. package/dist/tools/spawnAgent.js.map +1 -1
  39. package/dist/tracing/sessionTracer.d.ts +1 -0
  40. package/dist/tracing/sessionTracer.js +4 -1
  41. package/dist/tracing/sessionTracer.js.map +1 -1
  42. package/dist/ui/App.js +6 -1
  43. package/dist/ui/App.js.map +1 -1
  44. package/dist/ui/components/ActionLog.d.ts +7 -0
  45. package/dist/ui/components/ActionLog.js +63 -0
  46. package/dist/ui/components/ActionLog.js.map +1 -0
  47. package/dist/ui/components/FileBrowser.d.ts +2 -0
  48. package/dist/ui/components/FileBrowser.js +41 -0
  49. package/dist/ui/components/FileBrowser.js.map +1 -0
  50. package/package.json +3 -5
  51. package/AGENTS.md +0 -56
  52. package/Handover.md +0 -115
  53. package/PROGRESS.md +0 -160
  54. package/docs/01_insights_and_patterns.md +0 -27
  55. package/docs/02_edge_cases_and_mitigations.md +0 -143
  56. package/docs/03_initial_implementation_plan.md +0 -66
  57. package/docs/04_tech_stack_proposal.md +0 -20
  58. package/docs/05_prd.md +0 -87
  59. package/docs/06_user_stories.md +0 -72
  60. package/docs/07_system_architecture.md +0 -138
  61. package/docs/08_roadmap.md +0 -200
  62. package/e2b/Dockerfile +0 -26
  63. package/src/__tests__/bootstrap.test.ts +0 -111
  64. package/src/__tests__/config.test.ts +0 -97
  65. package/src/__tests__/m55.test.ts +0 -238
  66. package/src/__tests__/middleware.test.ts +0 -219
  67. package/src/__tests__/modelFactory.test.ts +0 -63
  68. package/src/__tests__/optimizations.test.ts +0 -201
  69. package/src/__tests__/promptBuilder.test.ts +0 -141
  70. package/src/__tests__/sandbox.test.ts +0 -102
  71. package/src/__tests__/security.test.ts +0 -122
  72. package/src/__tests__/streaming.test.ts +0 -82
  73. package/src/__tests__/toolRouter.test.ts +0 -52
  74. package/src/__tests__/tools.test.ts +0 -146
  75. package/src/__tests__/tracing.test.ts +0 -196
  76. package/src/agents/agentRegistry.ts +0 -69
  77. package/src/agents/agentSpec.ts +0 -67
  78. package/src/agents/builtinAgents.ts +0 -142
  79. package/src/cli/config.ts +0 -124
  80. package/src/cli/index.ts +0 -742
  81. package/src/cli/modelFactory.ts +0 -174
  82. package/src/cli/postinstall.ts +0 -28
  83. package/src/cli/providers.ts +0 -107
  84. package/src/commands/builtinCommands.ts +0 -293
  85. package/src/commands/commandRegistry.ts +0 -194
  86. package/src/core/agentLoop.d.ts.map +0 -1
  87. package/src/core/agentLoop.ts +0 -312
  88. package/src/core/autoSave.ts +0 -95
  89. package/src/core/compactor.ts +0 -252
  90. package/src/core/contextGuard.ts +0 -129
  91. package/src/core/errors.ts +0 -202
  92. package/src/core/promptBuilder.d.ts.map +0 -1
  93. package/src/core/promptBuilder.ts +0 -139
  94. package/src/core/reasoningRouter.ts +0 -121
  95. package/src/core/retry.ts +0 -75
  96. package/src/core/sessionResumer.ts +0 -90
  97. package/src/core/sessionStore.ts +0 -216
  98. package/src/core/subAgent.ts +0 -339
  99. package/src/core/tokenCounter.ts +0 -64
  100. package/src/evals/dataset.ts +0 -67
  101. package/src/evals/evaluator.ts +0 -81
  102. package/src/hitl/bridge.ts +0 -160
  103. package/src/middleware/commandSanitizer.ts +0 -60
  104. package/src/middleware/loopDetection.ts +0 -63
  105. package/src/middleware/permission.ts +0 -72
  106. package/src/middleware/pipeline.ts +0 -75
  107. package/src/middleware/preCompletion.ts +0 -94
  108. package/src/middleware/types.ts +0 -45
  109. package/src/sandbox/bootstrap.ts +0 -121
  110. package/src/sandbox/manager.ts +0 -239
  111. package/src/sandbox/sync.ts +0 -157
  112. package/src/skills/loader.ts +0 -143
  113. package/src/skills/tools.ts +0 -99
  114. package/src/skills/types.ts +0 -13
  115. package/src/test_cache.ts +0 -72
  116. package/src/tools/askUser.ts +0 -47
  117. package/src/tools/browser.ts +0 -137
  118. package/src/tools/index.d.ts.map +0 -1
  119. package/src/tools/index.ts +0 -237
  120. package/src/tools/registry.ts +0 -198
  121. package/src/tools/router.ts +0 -78
  122. package/src/tools/security.ts +0 -220
  123. package/src/tools/spawnAgent.ts +0 -158
  124. package/src/tools/webSearch.ts +0 -142
  125. package/src/tracing/analyzer.ts +0 -265
  126. package/src/tracing/langsmith.ts +0 -63
  127. package/src/tracing/sessionTracer.ts +0 -202
  128. package/src/tracing/types.ts +0 -49
  129. package/src/types/valyu.d.ts +0 -37
  130. package/src/ui/App.tsx +0 -404
  131. package/src/ui/components/HITLPrompt.tsx +0 -119
  132. package/src/ui/components/Header.tsx +0 -51
  133. package/src/ui/components/MessageBubble.tsx +0 -46
  134. package/src/ui/components/StatusBar.tsx +0 -138
  135. package/src/ui/components/StreamingText.tsx +0 -48
  136. package/src/ui/components/ToolCallPanel.tsx +0 -80
  137. package/tests/commands/commands.test.ts +0 -356
  138. package/tests/core/compactor.test.ts +0 -217
  139. package/tests/core/retryAndErrors.test.ts +0 -164
  140. package/tests/core/sessionResumer.test.ts +0 -95
  141. package/tests/core/sessionStore.test.ts +0 -84
  142. package/tests/core/stability.test.ts +0 -165
  143. package/tests/core/subAgent.test.ts +0 -238
  144. package/tests/hitl/hitlBridge.test.ts +0 -115
  145. package/tsconfig.json +0 -16
  146. package/vitest.config.ts +0 -10
  147. package/vitest.out +0 -48
@@ -1,49 +0,0 @@
1
- /**
2
- * Core tracing data types for session instrumentation.
3
- */
4
-
5
- /**
6
- * A single traced event during agent execution.
7
- */
8
- export interface TraceEvent {
9
- /** Event category. */
10
- type: "llm_call" | "tool_call" | "error" | "compaction";
11
- /** Unix timestamp (ms) when the event started. */
12
- timestamp: number;
13
- /** Duration in milliseconds (if applicable). */
14
- duration?: number;
15
- /** Event-specific payload. */
16
- data: Record<string, any>;
17
- }
18
-
19
- /**
20
- * Aggregated metrics for a complete session.
21
- */
22
- export interface TraceSummary {
23
- totalTokens: number;
24
- promptTokens: number;
25
- completionTokens: number;
26
- /** Estimated cost in USD. */
27
- totalCost: number;
28
- /** Cache hit rate: 0-1 (ratio of cached prompt tokens to total prompt tokens). */
29
- cacheHitRate: number;
30
- /** Number of tool calls made during the session. */
31
- toolCallCount: number;
32
- /** Number of errors encountered. */
33
- errorCount: number;
34
- /** Total session duration in ms. */
35
- totalDuration: number;
36
- /** Number of LLM turns. */
37
- turnCount: number;
38
- }
39
-
40
- /**
41
- * Full trace for a single agent session, suitable for persistence and analysis.
42
- */
43
- export interface SessionTrace {
44
- sessionId: string;
45
- startedAt: number;
46
- endedAt?: number;
47
- events: TraceEvent[];
48
- summary: TraceSummary;
49
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * Type declaration for @valyu/ai-sdk.
3
- *
4
- * This is a minimal stub so TypeScript compiles without requiring the
5
- * package to be installed. The actual SDK is dynamically imported at runtime.
6
- */
7
- declare module "@valyu/ai-sdk" {
8
- interface ValyuOptions {
9
- apiKey: string;
10
- }
11
-
12
- interface SearchParams {
13
- query: string;
14
- maxResults?: number;
15
- }
16
-
17
- interface SearchResult {
18
- title?: string;
19
- url?: string;
20
- snippet?: string;
21
- content?: string;
22
- }
23
-
24
- interface SearchResponse {
25
- results: SearchResult[];
26
- }
27
-
28
- export class Valyu {
29
- constructor(options: ValyuOptions);
30
- search(params: SearchParams): Promise<SearchResponse>;
31
- paperSearch(params: SearchParams): Promise<SearchResponse>;
32
- financeSearch(params: SearchParams): Promise<SearchResponse>;
33
- patentSearch(params: SearchParams): Promise<SearchResponse>;
34
- secSearch(params: SearchParams): Promise<SearchResponse>;
35
- companyResearch(params: SearchParams): Promise<SearchResponse>;
36
- }
37
- }
package/src/ui/App.tsx DELETED
@@ -1,404 +0,0 @@
1
- import React, { useState, useEffect, useMemo } from "react";
2
- import { Box, Text, useInput, useApp } from "ink";
3
- import TextInput from "ink-text-input";
4
- import { Header } from "./components/Header.js";
5
- import { StatusBar } from "./components/StatusBar.js";
6
- import { MessageBubble } from "./components/MessageBubble.js";
7
- import { StreamingText } from "./components/StreamingText.js";
8
- import { ToolCallPanel, ToolCallStatus } from "./components/ToolCallPanel.js";
9
- import { HITLPrompt } from "./components/HITLPrompt.js";
10
- import { ExecutionHarness } from "../core/agentLoop.js";
11
- import { ContextState } from "../core/promptBuilder.js";
12
- import { countMessageTokens } from "../core/tokenCounter.js";
13
- import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
14
- import {
15
- HITLBridge,
16
- HITLQuestion,
17
- HITLPermissionRequest,
18
- } from "../hitl/bridge.js";
19
- import { createDefaultRegistry } from "../commands/builtinCommands.js";
20
- import { CommandRegistry } from "../commands/commandRegistry.js";
21
-
22
- export interface Message {
23
- role: "user" | "agent" | "system";
24
- content: string;
25
- }
26
-
27
- export interface ActiveToolCall {
28
- name: string;
29
- args: Record<string, unknown>;
30
- status: ToolCallStatus;
31
- result?: string;
32
- }
33
-
34
- interface AppProps {
35
- provider: string;
36
- model: string;
37
- streaming: boolean;
38
- harness: ExecutionHarness;
39
- initialState: ContextState;
40
- maxTokens: number;
41
- }
42
-
43
- export const App: React.FC<AppProps> = ({
44
- provider,
45
- model,
46
- streaming,
47
- harness,
48
- initialState,
49
- maxTokens,
50
- }) => {
51
- const { exit } = useApp();
52
-
53
- // Slash Command Registry — initialized once, stable across renders
54
- const commandRegistry = useMemo(() => createDefaultRegistry(), []);
55
-
56
- // UI State
57
- const [messages, setMessages] = useState<Message[]>([
58
- { role: "system", content: "Session started. Type your request below." },
59
- ]);
60
- const [inputValue, setInputValue] = useState("");
61
- const [isProcessing, setIsProcessing] = useState(false);
62
-
63
- // Streaming & Tool State
64
- const [streamingTokens, setStreamingTokens] = useState<string[]>([]);
65
- const [isStreaming, setIsStreaming] = useState(false);
66
- const [activeToolCall, setActiveToolCall] = useState<ActiveToolCall | null>(
67
- null,
68
- );
69
-
70
- // Core Engine State
71
- const [contextState, setContextState] = useState<ContextState>(initialState);
72
-
73
- // HITL State
74
- const [hitlQuestion, setHitlQuestion] = useState<HITLQuestion | undefined>(
75
- undefined,
76
- );
77
- const [hitlPermission, setHitlPermission] = useState<
78
- HITLPermissionRequest | undefined
79
- >(undefined);
80
-
81
- // Listen for HITL events from the bridge
82
- useEffect(() => {
83
- const bridge = HITLBridge.getInstance();
84
-
85
- const onQuestion = (q: HITLQuestion) => setHitlQuestion(q);
86
- const onPermission = (p: HITLPermissionRequest) => setHitlPermission(p);
87
-
88
- bridge.on("question", onQuestion);
89
- bridge.on("permission", onPermission);
90
-
91
- // Clear prompts when resolved
92
- const origResolve = bridge.resolveAnswer.bind(bridge);
93
- bridge.resolveAnswer = (id: string, answer: string) => {
94
- origResolve(id, answer);
95
- setHitlQuestion(undefined);
96
- setHitlPermission(undefined);
97
- };
98
-
99
- return () => {
100
- bridge.off("question", onQuestion);
101
- bridge.off("permission", onPermission);
102
- };
103
- }, []);
104
-
105
- // StatusBar Metrics
106
- const [startTime] = useState(Date.now());
107
- const [elapsed, setElapsed] = useState("0s");
108
-
109
- // Update elapsed time
110
- useEffect(() => {
111
- const interval = setInterval(() => {
112
- const seconds = Math.floor((Date.now() - startTime) / 1000);
113
- if (seconds < 60) {
114
- setElapsed(`${seconds}s`);
115
- } else {
116
- const mins = Math.floor(seconds / 60);
117
- const secs = seconds % 60;
118
- setElapsed(`${mins}m ${secs}s`);
119
- }
120
- }, 1000);
121
- return () => clearInterval(interval);
122
- }, [startTime]);
123
-
124
- // Hold the latest state in a ref for the unmount/signal handlers
125
- const stateRef = React.useRef(contextState);
126
- useEffect(() => {
127
- stateRef.current = contextState;
128
- }, [contextState]);
129
-
130
- const performGracefulExit = async () => {
131
- try {
132
- await harness.autoSave.forceSave({
133
- config: { provider, model },
134
- state: stateRef.current,
135
- });
136
- } catch (e) {
137
- // Ignore errors during exit
138
- } finally {
139
- exit();
140
- process.exit(0);
141
- }
142
- };
143
-
144
- useEffect(() => {
145
- const handleSignal = () => {
146
- performGracefulExit();
147
- };
148
-
149
- process.on("SIGINT", handleSignal);
150
- process.on("SIGTERM", handleSignal);
151
-
152
- return () => {
153
- process.off("SIGINT", handleSignal);
154
- process.off("SIGTERM", handleSignal);
155
- };
156
- }, [provider, model, harness, exit]);
157
-
158
- // Handle Ctrl+C (Keyboard)
159
- useInput((input, key) => {
160
- if (key.ctrl && input === "c") {
161
- performGracefulExit();
162
- }
163
- });
164
-
165
- const runAgentLoop = async (currentState: ContextState) => {
166
- try {
167
- setIsStreaming(true);
168
- setStreamingTokens([]);
169
-
170
- let aiResponse: AIMessage;
171
-
172
- if (streaming) {
173
- aiResponse = await harness.streamStep(currentState, {
174
- onToken: (token) => {
175
- setStreamingTokens((prev) => [...prev, token]);
176
- },
177
- });
178
- } else {
179
- aiResponse = await harness.step(currentState);
180
- }
181
-
182
- setIsStreaming(false);
183
-
184
- // Add AI text to UI if any
185
- if (
186
- aiResponse.content &&
187
- typeof aiResponse.content === "string" &&
188
- aiResponse.content.trim() !== ""
189
- ) {
190
- setMessages((prev) => [
191
- ...prev,
192
- { role: "agent", content: aiResponse.content as string },
193
- ]);
194
- }
195
-
196
- // Add AI message to memory for following tools
197
- let nextHistory = [...currentState.conversationHistory, aiResponse];
198
-
199
- // Handle Tools
200
- if (aiResponse.tool_calls && aiResponse.tool_calls.length > 0) {
201
- // Execute all tool calls once
202
- const toolMessages = await harness.executeToolCalls(
203
- aiResponse,
204
- currentState,
205
- );
206
-
207
- // Update UI sequentially for each tool call
208
- for (const call of aiResponse.tool_calls) {
209
- setActiveToolCall({
210
- name: call.name,
211
- args: call.args,
212
- status: "running",
213
- });
214
-
215
- // Brief delay to show running state
216
- await new Promise((resolve) => setTimeout(resolve, 300));
217
-
218
- setActiveToolCall({
219
- name: call.name,
220
- args: call.args,
221
- status: "success",
222
- result:
223
- toolMessages.length > 0
224
- ? "Tool execution completed."
225
- : "No output.",
226
- });
227
- }
228
-
229
- // Add tool results to history once
230
- nextHistory = [...nextHistory, ...toolMessages];
231
-
232
- // Wait a sec so user sees the success state
233
- await new Promise((resolve) => setTimeout(resolve, 800));
234
- setActiveToolCall(null);
235
-
236
- // Recurse: Agent needs to react to the tool output
237
- const nextState = { ...currentState, conversationHistory: nextHistory };
238
- setContextState(nextState);
239
- await runAgentLoop(nextState);
240
- } else {
241
- // Turn complete
242
- setContextState({ ...currentState, conversationHistory: nextHistory });
243
- setIsProcessing(false);
244
- }
245
- } catch (error: any) {
246
- setIsStreaming(false);
247
- setActiveToolCall(null);
248
- setIsProcessing(false);
249
- setMessages((prev) => [
250
- ...prev,
251
- { role: "system", content: `Error: ${error.message}` },
252
- ]);
253
- }
254
- };
255
-
256
- const handleSubmit = async (query: string) => {
257
- if (!query.trim() || isProcessing) return;
258
-
259
- const userText = query.trim();
260
- setInputValue("");
261
-
262
- // ── Slash Command Interception ──
263
- // Commands starting with "/" are handled locally at zero LLM cost.
264
- if (commandRegistry.isCommand(userText)) {
265
- setMessages((prev) => [...prev, { role: "user", content: userText }]);
266
-
267
- const commandContext = {
268
- config: {
269
- provider,
270
- model,
271
- maxTokens,
272
- streaming,
273
- temperature: 0,
274
- } as any,
275
- configPath: "",
276
- harness,
277
- contextState,
278
- setContextState,
279
- addSystemMessage: (content: string) => {
280
- setMessages((prev) => [...prev, { role: "system", content }]);
281
- },
282
- provider,
283
- model,
284
- maxTokens,
285
- };
286
-
287
- try {
288
- const result = await commandRegistry.execute(userText, commandContext);
289
-
290
- // Handle /exit signal
291
- if (result === "__EXIT__") {
292
- setMessages((prev) => [
293
- ...prev,
294
- { role: "system", content: "Goodbye! 👋" },
295
- ]);
296
- setTimeout(() => {
297
- exit();
298
- process.exit(0);
299
- }, 500);
300
- return;
301
- }
302
-
303
- if (result) {
304
- setMessages((prev) => [...prev, { role: "system", content: result }]);
305
- }
306
- } catch (err: any) {
307
- setMessages((prev) => [
308
- ...prev,
309
- { role: "system", content: `Command error: ${err.message}` },
310
- ]);
311
- }
312
- return;
313
- }
314
-
315
- // ── Normal Agent Message ──
316
- setIsProcessing(true);
317
-
318
- // 1. Update UI
319
- setMessages((prev) => [...prev, { role: "user", content: userText }]);
320
-
321
- // 2. Update Engine State
322
- const humanMsg = new HumanMessage(userText);
323
- const updatedState = {
324
- ...contextState,
325
- conversationHistory: [...contextState.conversationHistory, humanMsg],
326
- };
327
- setContextState(updatedState);
328
-
329
- // 3. Start Turn
330
- runAgentLoop(updatedState);
331
- };
332
-
333
- const summary = harness.tracer.getSummary();
334
- const contextTokens = countMessageTokens(contextState.conversationHistory);
335
-
336
- return (
337
- <Box flexDirection="column" minHeight={15}>
338
- <Header provider={provider} model={model} streaming={streaming} />
339
-
340
- <Box flexDirection="column" paddingY={1}>
341
- {messages.map((msg, i) => (
342
- <MessageBubble key={i} role={msg.role} content={msg.content} />
343
- ))}
344
- </Box>
345
-
346
- {isStreaming && (
347
- <Box paddingX={1} marginBottom={1}>
348
- <Box marginLeft={2}>
349
- <StreamingText tokens={streamingTokens} isStreaming={isStreaming} />
350
- </Box>
351
- </Box>
352
- )}
353
-
354
- {activeToolCall && (
355
- <Box paddingX={1} marginBottom={1}>
356
- <ToolCallPanel
357
- toolName={activeToolCall.name}
358
- args={activeToolCall.args}
359
- status={activeToolCall.status}
360
- result={activeToolCall.result}
361
- />
362
- </Box>
363
- )}
364
-
365
- {/* Interactive Prompt Area */}
366
- {(hitlQuestion || hitlPermission) && (
367
- <HITLPrompt question={hitlQuestion} permission={hitlPermission} />
368
- )}
369
-
370
- {!isProcessing && !hitlQuestion && !hitlPermission && (
371
- <Box paddingX={1} marginBottom={1}>
372
- <Box marginRight={1}>
373
- <Text color="green" bold>
374
-
375
- </Text>
376
- </Box>
377
- <TextInput
378
- value={inputValue}
379
- onChange={setInputValue}
380
- onSubmit={handleSubmit}
381
- placeholder="What should we build today?"
382
- />
383
- </Box>
384
- )}
385
-
386
- {isProcessing && !isStreaming && !activeToolCall && (
387
- <Box paddingX={1} marginBottom={1}>
388
- <Text dimColor>Thinking...</Text>
389
- </Box>
390
- )}
391
-
392
- <StatusBar
393
- contextTokens={contextTokens}
394
- maxContextTokens={maxTokens}
395
- totalTokens={summary.totalTokens}
396
- cacheHitRate={summary.cacheHitRate}
397
- toolCalls={summary.toolCallCount}
398
- turns={summary.turnCount}
399
- cost={summary.totalCost}
400
- elapsed={elapsed}
401
- />
402
- </Box>
403
- );
404
- };
@@ -1,119 +0,0 @@
1
- import React, { useState } from "react";
2
- import { Box, Text } from "ink";
3
- import TextInput from "ink-text-input";
4
- import {
5
- HITLQuestion,
6
- HITLPermissionRequest,
7
- HITLBridge,
8
- } from "../../hitl/bridge.js";
9
-
10
- interface HITLPromptProps {
11
- /** The active HITL question or permission request. */
12
- question?: HITLQuestion;
13
- permission?: HITLPermissionRequest;
14
- }
15
-
16
- /**
17
- * HITLPrompt — renders a blocking question/permission prompt in the TUI.
18
- *
19
- * When a tool calls `ask_user_question` or the PermissionMiddleware fires,
20
- * this component takes over the input area to capture the user's response.
21
- */
22
- export const HITLPrompt: React.FC<HITLPromptProps> = ({
23
- question,
24
- permission,
25
- }) => {
26
- const [inputValue, setInputValue] = useState("");
27
-
28
- const handleSubmit = (value: string) => {
29
- const bridge = HITLBridge.getInstance();
30
- if (question) {
31
- bridge.resolveAnswer(question.id, value);
32
- } else if (permission) {
33
- bridge.resolveAnswer(permission.id, value);
34
- }
35
- setInputValue("");
36
- };
37
-
38
- if (question) {
39
- return (
40
- <Box flexDirection="column" paddingX={1} marginBottom={1}>
41
- <Box marginBottom={1}>
42
- <Text color="yellow" bold>
43
- ❓ Agent is asking:
44
- </Text>
45
- </Box>
46
- <Box marginLeft={2} marginBottom={1}>
47
- <Text>{question.question}</Text>
48
- </Box>
49
- {question.options && question.options.length > 0 && (
50
- <Box flexDirection="column" marginLeft={2} marginBottom={1}>
51
- {question.options.map((opt, i) => (
52
- <Text key={i} dimColor>
53
- {` ${i + 1}. ${opt}`}
54
- </Text>
55
- ))}
56
- </Box>
57
- )}
58
- <Box>
59
- <Box marginRight={1}>
60
- <Text color="yellow" bold>
61
-
62
- </Text>
63
- </Box>
64
- <TextInput
65
- value={inputValue}
66
- onChange={setInputValue}
67
- onSubmit={handleSubmit}
68
- placeholder="Type your answer..."
69
- />
70
- </Box>
71
- </Box>
72
- );
73
- }
74
-
75
- if (permission) {
76
- const argsSummary = Object.entries(permission.args)
77
- .map(
78
- ([k, v]) =>
79
- `${k}: ${typeof v === "string" ? v.substring(0, 60) : JSON.stringify(v)}`,
80
- )
81
- .join(", ");
82
-
83
- return (
84
- <Box flexDirection="column" paddingX={1} marginBottom={1}>
85
- <Box marginBottom={1}>
86
- <Text color="red" bold>
87
- ⚠ Permission Required:
88
- </Text>
89
- </Box>
90
- <Box marginLeft={2} marginBottom={1}>
91
- <Text>
92
- The agent wants to execute{" "}
93
- <Text bold color="white">
94
- {permission.toolName}
95
- </Text>
96
- </Text>
97
- </Box>
98
- <Box marginLeft={2} marginBottom={1}>
99
- <Text dimColor>{argsSummary}</Text>
100
- </Box>
101
- <Box>
102
- <Box marginRight={1}>
103
- <Text color="red" bold>
104
- Allow? (y/n):
105
- </Text>
106
- </Box>
107
- <TextInput
108
- value={inputValue}
109
- onChange={setInputValue}
110
- onSubmit={handleSubmit}
111
- placeholder="y or n"
112
- />
113
- </Box>
114
- </Box>
115
- );
116
- }
117
-
118
- return null;
119
- };
@@ -1,51 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import chalk from "chalk";
4
-
5
- interface HeaderProps {
6
- provider: string;
7
- model: string;
8
- streaming: boolean;
9
- }
10
-
11
- export const Header: React.FC<HeaderProps> = ({
12
- provider,
13
- model,
14
- streaming,
15
- }) => {
16
- return (
17
- <Box
18
- flexDirection="column"
19
- paddingX={1}
20
- borderStyle="round"
21
- borderColor="cyan"
22
- >
23
- <Box justifyContent="space-between">
24
- <Text bold color="cyan">
25
- ◆ joone
26
- </Text>
27
- <Text dimColor>v0.1.0</Text>
28
- </Box>
29
- <Box marginTop={0} gap={2}>
30
- <Text>
31
- <Text dimColor>provider</Text>{" "}
32
- <Text color="white" bold>
33
- {provider}
34
- </Text>
35
- </Text>
36
- <Text>
37
- <Text dimColor>model</Text>{" "}
38
- <Text color="white" bold>
39
- {model}
40
- </Text>
41
- </Text>
42
- <Text>
43
- <Text dimColor>stream</Text>{" "}
44
- <Text color={streaming ? "green" : "yellow"}>
45
- {streaming ? "on" : "off"}
46
- </Text>
47
- </Text>
48
- </Box>
49
- </Box>
50
- );
51
- };
@@ -1,46 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
-
4
- type MessageRole = "user" | "agent" | "system";
5
-
6
- interface MessageBubbleProps {
7
- role: MessageRole;
8
- content: string;
9
- }
10
-
11
- /**
12
- * Renders a single conversation message with role-based styling.
13
- * - User messages: cyan accent, labeled "you"
14
- * - Agent messages: green accent, labeled "joone"
15
- * - System messages: centered, dim yellow
16
- */ export const MessageBubble: React.FC<MessageBubbleProps> = ({
17
- role,
18
- content,
19
- }) => {
20
- if (role === "system") {
21
- return (
22
- <Box paddingX={1} justifyContent="center">
23
- <Text dimColor italic color="yellow">
24
- ⚙ {content}
25
- </Text>
26
- </Box>
27
- );
28
- }
29
-
30
- const isUser = role === "user";
31
- const accentColor = isUser ? "cyan" : "green";
32
- const label = isUser ? "you" : "joone";
33
-
34
- return (
35
- <Box flexDirection="column" paddingX={1}>
36
- <Text bold color={accentColor}>
37
- {label}
38
- </Text>
39
- <Box marginLeft={2}>
40
- <Text color="white" wrap="wrap">
41
- {content}
42
- </Text>
43
- </Box>
44
- </Box>
45
- );
46
- };