joonecli 0.1.0 → 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.
- package/README.md +12 -12
- package/dist/__tests__/optimizations.test.js.map +1 -1
- package/dist/__tests__/promptBuilder.test.js +14 -20
- package/dist/__tests__/promptBuilder.test.js.map +1 -1
- package/dist/agents/agentRegistry.d.ts +37 -0
- package/dist/agents/agentRegistry.js +58 -0
- package/dist/agents/agentRegistry.js.map +1 -0
- package/dist/agents/agentSpec.d.ts +54 -0
- package/dist/agents/agentSpec.js +9 -0
- package/dist/agents/agentSpec.js.map +1 -0
- package/dist/agents/builtinAgents.d.ts +20 -0
- package/{src/agents/builtinAgents.ts → dist/agents/builtinAgents.js} +84 -101
- package/dist/agents/builtinAgents.js.map +1 -0
- package/dist/cli/config.d.ts +4 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/index.js +29 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/postinstall.d.ts +2 -0
- package/dist/cli/postinstall.js +25 -0
- package/dist/cli/postinstall.js.map +1 -0
- package/dist/commands/builtinCommands.d.ts +21 -0
- package/dist/commands/builtinCommands.js +241 -0
- package/dist/commands/builtinCommands.js.map +1 -0
- package/dist/commands/commandRegistry.d.ts +92 -0
- package/dist/commands/commandRegistry.js +128 -0
- package/dist/commands/commandRegistry.js.map +1 -0
- package/dist/core/agentLoop.d.ts +7 -2
- package/dist/core/agentLoop.js +35 -13
- package/dist/core/agentLoop.js.map +1 -1
- package/dist/core/autoSave.d.ts +41 -0
- package/dist/core/autoSave.js +69 -0
- package/dist/core/autoSave.js.map +1 -0
- package/dist/core/compactor.d.ts +66 -0
- package/dist/core/compactor.js +170 -0
- package/dist/core/compactor.js.map +1 -0
- package/dist/core/contextGuard.d.ts +38 -0
- package/dist/core/contextGuard.js +122 -0
- package/dist/core/contextGuard.js.map +1 -0
- package/dist/core/events.d.ts +45 -0
- package/dist/core/events.js +8 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/promptBuilder.d.ts +16 -1
- package/dist/core/promptBuilder.js +27 -14
- package/dist/core/promptBuilder.js.map +1 -1
- package/dist/core/sessionResumer.js +3 -3
- package/dist/core/sessionResumer.js.map +1 -1
- package/dist/core/sessionStore.js +3 -2
- package/dist/core/sessionStore.js.map +1 -1
- package/dist/core/subAgent.d.ts +56 -0
- package/dist/core/subAgent.js +240 -0
- package/dist/core/subAgent.js.map +1 -0
- package/dist/core/tokenCounter.d.ts +8 -1
- package/dist/core/tokenCounter.js +28 -0
- package/dist/core/tokenCounter.js.map +1 -1
- package/dist/debug_google.d.ts +1 -0
- package/dist/debug_google.js +23 -0
- package/dist/debug_google.js.map +1 -0
- package/dist/middleware/permission.js +1 -0
- package/dist/middleware/permission.js.map +1 -1
- package/dist/test_google.d.ts +1 -0
- package/dist/test_google.js +32 -89
- package/dist/test_google.js.map +1 -0
- package/dist/tools/browser.js +4 -1
- package/dist/tools/browser.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +11 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/installHostDeps.d.ts +2 -0
- package/dist/tools/installHostDeps.js +37 -0
- package/dist/tools/installHostDeps.js.map +1 -0
- package/dist/tools/router.js +3 -0
- package/dist/tools/router.js.map +1 -1
- package/dist/tools/spawnAgent.d.ts +19 -0
- package/dist/tools/spawnAgent.js +132 -0
- package/dist/tools/spawnAgent.js.map +1 -0
- package/dist/tracing/sessionTracer.d.ts +1 -0
- package/dist/tracing/sessionTracer.js +4 -1
- package/dist/tracing/sessionTracer.js.map +1 -1
- package/dist/ui/App.js +94 -6
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActionLog.d.ts +7 -0
- package/dist/ui/components/ActionLog.js +63 -0
- package/dist/ui/components/ActionLog.js.map +1 -0
- package/dist/ui/components/FileBrowser.d.ts +2 -0
- package/dist/ui/components/FileBrowser.js +41 -0
- package/dist/ui/components/FileBrowser.js.map +1 -0
- package/package.json +5 -6
- package/AGENTS.md +0 -56
- package/Handover.md +0 -115
- package/PROGRESS.md +0 -160
- package/docs/01_insights_and_patterns.md +0 -27
- package/docs/02_edge_cases_and_mitigations.md +0 -143
- package/docs/03_initial_implementation_plan.md +0 -66
- package/docs/04_tech_stack_proposal.md +0 -20
- package/docs/05_prd.md +0 -87
- package/docs/06_user_stories.md +0 -72
- package/docs/07_system_architecture.md +0 -138
- package/docs/08_roadmap.md +0 -200
- package/e2b/Dockerfile +0 -26
- package/src/__tests__/bootstrap.test.ts +0 -111
- package/src/__tests__/config.test.ts +0 -97
- package/src/__tests__/m55.test.ts +0 -238
- package/src/__tests__/middleware.test.ts +0 -219
- package/src/__tests__/modelFactory.test.ts +0 -63
- package/src/__tests__/optimizations.test.ts +0 -201
- package/src/__tests__/promptBuilder.test.ts +0 -141
- package/src/__tests__/sandbox.test.ts +0 -102
- package/src/__tests__/security.test.ts +0 -122
- package/src/__tests__/streaming.test.ts +0 -82
- package/src/__tests__/toolRouter.test.ts +0 -52
- package/src/__tests__/tools.test.ts +0 -146
- package/src/__tests__/tracing.test.ts +0 -196
- package/src/agents/agentRegistry.ts +0 -69
- package/src/agents/agentSpec.ts +0 -67
- package/src/cli/config.ts +0 -124
- package/src/cli/index.ts +0 -730
- package/src/cli/modelFactory.ts +0 -174
- package/src/cli/providers.ts +0 -107
- package/src/commands/builtinCommands.ts +0 -293
- package/src/commands/commandRegistry.ts +0 -194
- package/src/core/agentLoop.d.ts.map +0 -1
- package/src/core/agentLoop.ts +0 -312
- package/src/core/autoSave.ts +0 -95
- package/src/core/compactor.ts +0 -252
- package/src/core/contextGuard.ts +0 -129
- package/src/core/errors.ts +0 -202
- package/src/core/promptBuilder.d.ts.map +0 -1
- package/src/core/promptBuilder.ts +0 -139
- package/src/core/reasoningRouter.ts +0 -121
- package/src/core/retry.ts +0 -75
- package/src/core/sessionResumer.ts +0 -90
- package/src/core/sessionStore.ts +0 -215
- package/src/core/subAgent.ts +0 -339
- package/src/core/tokenCounter.ts +0 -64
- package/src/evals/dataset.ts +0 -67
- package/src/evals/evaluator.ts +0 -81
- package/src/hitl/bridge.ts +0 -160
- package/src/middleware/commandSanitizer.ts +0 -60
- package/src/middleware/loopDetection.ts +0 -63
- package/src/middleware/permission.ts +0 -72
- package/src/middleware/pipeline.ts +0 -75
- package/src/middleware/preCompletion.ts +0 -94
- package/src/middleware/types.ts +0 -45
- package/src/sandbox/bootstrap.ts +0 -121
- package/src/sandbox/manager.ts +0 -239
- package/src/sandbox/sync.ts +0 -157
- package/src/skills/loader.ts +0 -143
- package/src/skills/tools.ts +0 -99
- package/src/skills/types.ts +0 -13
- package/src/test_cache.ts +0 -72
- package/src/test_google.js +0 -40
- package/src/test_google.ts +0 -40
- package/src/tools/askUser.ts +0 -47
- package/src/tools/browser.ts +0 -137
- package/src/tools/index.d.ts.map +0 -1
- package/src/tools/index.ts +0 -237
- package/src/tools/registry.ts +0 -198
- package/src/tools/router.ts +0 -78
- package/src/tools/security.ts +0 -220
- package/src/tools/spawnAgent.ts +0 -158
- package/src/tools/webSearch.ts +0 -142
- package/src/tracing/analyzer.ts +0 -265
- package/src/tracing/langsmith.ts +0 -63
- package/src/tracing/sessionTracer.ts +0 -202
- package/src/tracing/types.ts +0 -49
- package/src/types/valyu.d.ts +0 -37
- package/src/ui/App.tsx +0 -404
- package/src/ui/components/HITLPrompt.tsx +0 -119
- package/src/ui/components/Header.tsx +0 -51
- package/src/ui/components/MessageBubble.tsx +0 -46
- package/src/ui/components/StatusBar.tsx +0 -138
- package/src/ui/components/StreamingText.tsx +0 -48
- package/src/ui/components/ToolCallPanel.tsx +0 -80
- package/tests/commands/commands.test.ts +0 -356
- package/tests/core/compactor.test.ts +0 -217
- package/tests/core/retryAndErrors.test.ts +0 -164
- package/tests/core/sessionResumer.test.ts +0 -95
- package/tests/core/sessionStore.test.ts +0 -84
- package/tests/core/stability.test.ts +0 -165
- package/tests/core/subAgent.test.ts +0 -238
- package/tests/hitl/hitlBridge.test.ts +0 -115
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -10
- package/vitest.out +0 -48
package/src/tracing/types.ts
DELETED
|
@@ -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
|
-
}
|
package/src/types/valyu.d.ts
DELETED
|
@@ -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
|
-
};
|