Skip to content

Commit dfa34c1

Browse files
authored
assorted bugs: tool timeouts, schema files, and verifyinput callbacks (#2511)
1 parent 523cea1 commit dfa34c1

File tree

5 files changed

+76
-13
lines changed

5 files changed

+76
-13
lines changed

electron-builder.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const config = {
3737
asarUnpack: [
3838
"dist/bin/**/*", // wavesrv and wsh binaries
3939
"dist/docsite/**/*", // the static docsite
40+
"dist/schema/**/*", // schema files for Monaco editor
4041
],
4142
mac: {
4243
target: [

frontend/app/aipanel/aitooluse.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ interface AIToolUseBatchProps {
8181

8282
const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => {
8383
const [userApprovalOverride, setUserApprovalOverride] = useState<string | null>(null);
84+
const partsRef = useRef(parts);
85+
partsRef.current = parts;
8486

8587
// All parts in a batch have the same approval status (enforced by grouping logic in AIToolUseGroup)
8688
const firstTool = parts[0].data;
@@ -91,13 +93,13 @@ const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => {
9193
if (!isStreaming || effectiveApproval !== "needs-approval") return;
9294

9395
const interval = setInterval(() => {
94-
parts.forEach((part) => {
96+
partsRef.current.forEach((part) => {
9597
WaveAIModel.getInstance().toolUseKeepalive(part.data.toolcallid);
9698
});
9799
}, 4000);
98100

99101
return () => clearInterval(interval);
100-
}, [isStreaming, effectiveApproval, parts]);
102+
}, [isStreaming, effectiveApproval]);
101103

102104
const handleApprove = () => {
103105
setUserApprovalOverride("user-approved");
@@ -231,6 +233,8 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
231233
const showRestoreModal = restoreModalToolCallId === toolData.toolcallid;
232234
const highlightTimeoutRef = useRef<NodeJS.Timeout | null>(null);
233235
const highlightedBlockIdRef = useRef<string | null>(null);
236+
const toolCallIdRef = useRef(toolData.toolcallid);
237+
toolCallIdRef.current = toolData.toolcallid;
234238

235239
const statusIcon = toolData.status === "completed" ? "✓" : toolData.status === "error" ? "✗" : "•";
236240
const statusColor =
@@ -245,11 +249,11 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
245249
if (!isStreaming || effectiveApproval !== "needs-approval") return;
246250

247251
const interval = setInterval(() => {
248-
WaveAIModel.getInstance().toolUseKeepalive(toolData.toolcallid);
252+
WaveAIModel.getInstance().toolUseKeepalive(toolCallIdRef.current);
249253
}, 4000);
250254

251255
return () => clearInterval(interval);
252-
}, [isStreaming, effectiveApproval, toolData.toolcallid]);
256+
}, [isStreaming, effectiveApproval]);
253257

254258
useEffect(() => {
255259
return () => {
@@ -399,13 +403,17 @@ export const AIToolUseGroup = memo(({ parts, isStreaming }: AIToolUseGroupProps)
399403
const isFileOpPart = isFileOp(part);
400404
const partNeedsApproval = needsApproval(part);
401405

402-
if (isFileOpPart && partNeedsApproval && !addedApprovalBatch) {
403-
groupedItems.push({ type: "batch", parts: readFileNeedsApproval });
404-
addedApprovalBatch = true;
405-
} else if (isFileOpPart && !partNeedsApproval && !addedOtherBatch) {
406-
groupedItems.push({ type: "batch", parts: readFileOther });
407-
addedOtherBatch = true;
408-
} else if (!isFileOpPart) {
406+
if (isFileOpPart && partNeedsApproval) {
407+
if (!addedApprovalBatch) {
408+
groupedItems.push({ type: "batch", parts: readFileNeedsApproval });
409+
addedApprovalBatch = true;
410+
}
411+
} else if (isFileOpPart && !partNeedsApproval) {
412+
if (!addedOtherBatch) {
413+
groupedItems.push({ type: "batch", parts: readFileOther });
414+
addedOtherBatch = true;
415+
}
416+
} else {
409417
groupedItems.push({ type: "single", part });
410418
}
411419
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/aiusechat/tools_readdir.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ package aiusechat
55

66
import (
77
"fmt"
8+
"os"
89

910
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
1011
"github.com/wavetermdev/waveterm/pkg/util/fileutil"
1112
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
13+
"github.com/wavetermdev/waveterm/pkg/wavebase"
1214
)
1315

1416
const ReadDirDefaultMaxEntries = 500
@@ -50,6 +52,29 @@ func parseReadDirInput(input any) (*readDirParams, error) {
5052
return result, nil
5153
}
5254

55+
func verifyReadDirInput(input any, toolUseData *uctypes.UIMessageDataToolUse) error {
56+
params, err := parseReadDirInput(input)
57+
if err != nil {
58+
return err
59+
}
60+
61+
expandedPath, err := wavebase.ExpandHomeDir(params.Path)
62+
if err != nil {
63+
return fmt.Errorf("failed to expand path: %w", err)
64+
}
65+
66+
fileInfo, err := os.Stat(expandedPath)
67+
if err != nil {
68+
return fmt.Errorf("failed to stat path: %w", err)
69+
}
70+
71+
if !fileInfo.IsDir() {
72+
return fmt.Errorf("path is not a directory, cannot be read with the read_dir tool. use the read_text_file tool if available to read files")
73+
}
74+
75+
return nil
76+
}
77+
5378
func readDirCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) {
5479
params, err := parseReadDirInput(input)
5580
if err != nil {
@@ -129,5 +154,6 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition {
129154
ToolApproval: func(input any) string {
130155
return uctypes.ApprovalNeedsApproval
131156
},
157+
ToolVerifyInput: verifyReadDirInput,
132158
}
133159
}

pkg/aiusechat/tools_readfile.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,33 @@ func isBlockedFile(expandedPath string) (bool, string) {
197197
return false, ""
198198
}
199199

200+
func verifyReadTextFileInput(input any, toolUseData *uctypes.UIMessageDataToolUse) error {
201+
params, err := parseReadTextFileInput(input)
202+
if err != nil {
203+
return err
204+
}
205+
206+
expandedPath, err := wavebase.ExpandHomeDir(params.Filename)
207+
if err != nil {
208+
return fmt.Errorf("failed to expand path: %w", err)
209+
}
210+
211+
if blocked, reason := isBlockedFile(expandedPath); blocked {
212+
return fmt.Errorf("access denied: potentially sensitive file: %s", reason)
213+
}
214+
215+
fileInfo, err := os.Stat(expandedPath)
216+
if err != nil {
217+
return fmt.Errorf("failed to stat file: %w", err)
218+
}
219+
220+
if fileInfo.IsDir() {
221+
return fmt.Errorf("path is a directory, cannot be read with the read_text_file tool. use the read_dir tool if available to read directories")
222+
}
223+
224+
return nil
225+
}
226+
200227
func readTextFileCallback(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) {
201228
const ReadLimit = 1024 * 1024 * 1024
202229

@@ -370,5 +397,6 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition {
370397
ToolApproval: func(input any) string {
371398
return uctypes.ApprovalNeedsApproval
372399
},
400+
ToolVerifyInput: verifyReadTextFileInput,
373401
}
374402
}

0 commit comments

Comments
 (0)