Skip to content

Commit 3c3c665

Browse files
authored
fix tool approval lifecycle to match SSE connection, not keep-alives (#2693)
tie the tool approvals to the lifecycle of the SSE connection, NOT FE keepalive packets. this prevents timeouts when the FE hasn't rendered the tools (or FE tool rendering lifecycle issues). also allows immediate timeouts on refresh. other small fixes: convert to use wshclient in wshcmd-*.go files, cleanup in SSE code
1 parent 36d8767 commit 3c3c665

File tree

21 files changed

+261
-161
lines changed

21 files changed

+261
-161
lines changed

.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"files.associations": {
5656
"*.css": "tailwindcss"
5757
},
58-
"go.lintTool": "staticcheck",
5958
"gopls": {
6059
"analyses": {
6160
"QF1003": false

cmd/wsh/cmd/wshcmd-connserver.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,10 @@ func serverRunRouter(jwtToken string) error {
191191

192192
func checkForUpdate() error {
193193
remoteInfo := wshutil.GetInfo()
194-
needsRestartRaw, err := RpcClient.SendRpcRequest(wshrpc.Command_ConnUpdateWsh, remoteInfo, &wshrpc.RpcOpts{Timeout: 60000})
194+
needsRestart, err := wshclient.ConnUpdateWshCommand(RpcClient, remoteInfo, &wshrpc.RpcOpts{Timeout: 60000})
195195
if err != nil {
196196
return fmt.Errorf("could not update: %w", err)
197197
}
198-
needsRestart, ok := needsRestartRaw.(bool)
199-
if !ok {
200-
return fmt.Errorf("wrong return type from update")
201-
}
202198
if needsRestart {
203199
// run the restart command here
204200
// how to get the correct path?

cmd/wsh/cmd/wshcmd-deleteblock.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/spf13/cobra"
1010
"github.com/wavetermdev/waveterm/pkg/wshrpc"
11+
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
1112
)
1213

1314
var deleteBlockCmd = &cobra.Command{
@@ -35,7 +36,7 @@ func deleteBlockRun(cmd *cobra.Command, args []string) (rtnErr error) {
3536
deleteBlockData := &wshrpc.CommandDeleteBlockData{
3637
BlockId: fullORef.OID,
3738
}
38-
_, err = RpcClient.SendRpcRequest(wshrpc.Command_DeleteBlock, deleteBlockData, &wshrpc.RpcOpts{Timeout: 2000})
39+
err = wshclient.DeleteBlockCommand(RpcClient, *deleteBlockData, &wshrpc.RpcOpts{Timeout: 2000})
3940
if err != nil {
4041
return fmt.Errorf("delete block failed: %v", err)
4142
}

cmd/wsh/cmd/wshcmd-editconfig.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/spf13/cobra"
1010
"github.com/wavetermdev/waveterm/pkg/waveobj"
1111
"github.com/wavetermdev/waveterm/pkg/wshrpc"
12+
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
1213
)
1314

1415
var editConfigMagnified bool
@@ -48,7 +49,7 @@ func editConfigRun(cmd *cobra.Command, args []string) (rtnErr error) {
4849
Focused: true,
4950
}
5051

51-
_, err := RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000})
52+
_, err := wshclient.CreateBlockCommand(RpcClient, *wshCmd, &wshrpc.RpcOpts{Timeout: 2000})
5253
if err != nil {
5354
return fmt.Errorf("opening config file: %w", err)
5455
}

cmd/wsh/cmd/wshcmd-notify.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/spf13/cobra"
1010
"github.com/wavetermdev/waveterm/pkg/wshrpc"
11+
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
1112
"github.com/wavetermdev/waveterm/pkg/wshutil"
1213
)
1314

@@ -38,7 +39,7 @@ func notifyRun(cmd *cobra.Command, args []string) (rtnErr error) {
3839
Body: message,
3940
Silent: notifySilent,
4041
}
41-
_, err := RpcClient.SendRpcRequest(wshrpc.Command_Notify, notificationOptions, &wshrpc.RpcOpts{Timeout: 2000, Route: wshutil.ElectronRoute})
42+
err := wshclient.NotifyCommand(RpcClient, *notificationOptions, &wshrpc.RpcOpts{Timeout: 2000, Route: wshutil.ElectronRoute})
4243
if err != nil {
4344
return fmt.Errorf("sending notification: %w", err)
4445
}

cmd/wsh/cmd/wshcmd-secret.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func secretUiRun(cmd *cobra.Command, args []string) (rtnErr error) {
187187
Focused: true,
188188
}
189189

190-
_, err := RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000})
190+
_, err := wshclient.CreateBlockCommand(RpcClient, *wshCmd, &wshrpc.RpcOpts{Timeout: 2000})
191191
if err != nil {
192192
return fmt.Errorf("opening secrets UI: %w", err)
193193
}

cmd/wsh/cmd/wshcmd-setmeta.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/spf13/cobra"
1515
"github.com/wavetermdev/waveterm/pkg/wshrpc"
16+
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
1617
)
1718

1819
var setMetaCmd = &cobra.Command{
@@ -192,7 +193,7 @@ func setMetaRun(cmd *cobra.Command, args []string) (rtnErr error) {
192193
ORef: *fullORef,
193194
Meta: fullMeta,
194195
}
195-
_, err = RpcClient.SendRpcRequest(wshrpc.Command_SetMeta, setMetaWshCmd, &wshrpc.RpcOpts{Timeout: 2000})
196+
err = wshclient.SetMetaCommand(RpcClient, *setMetaWshCmd, &wshrpc.RpcOpts{Timeout: 2000})
196197
if err != nil {
197198
return fmt.Errorf("setting metadata: %v", err)
198199
}

cmd/wsh/cmd/wshcmd-view.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/spf13/cobra"
1414
"github.com/wavetermdev/waveterm/pkg/waveobj"
1515
"github.com/wavetermdev/waveterm/pkg/wshrpc"
16+
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
1617
)
1718

1819
var viewMagnified bool
@@ -99,7 +100,7 @@ func viewRun(cmd *cobra.Command, args []string) (rtnErr error) {
99100
wshCmd.BlockDef.Meta[waveobj.MetaKey_Connection] = conn
100101
}
101102
}
102-
_, err := RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, wshCmd, &wshrpc.RpcOpts{Timeout: 2000})
103+
_, err := wshclient.CreateBlockCommand(RpcClient, *wshCmd, &wshrpc.RpcOpts{Timeout: 2000})
103104
if err != nil {
104105
return fmt.Errorf("running view command: %w", err)
105106
}

frontend/app/aipanel/aitooluse.tsx

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -146,26 +146,11 @@ interface AIToolUseBatchProps {
146146

147147
const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => {
148148
const [userApprovalOverride, setUserApprovalOverride] = useState<string | null>(null);
149-
const partsRef = useRef(parts);
150-
partsRef.current = parts;
151149

152-
// All parts in a batch have the same approval status (enforced by grouping logic in AIToolUseGroup)
153150
const firstTool = parts[0].data;
154151
const baseApproval = userApprovalOverride || firstTool.approval;
155152
const effectiveApproval = getEffectiveApprovalStatus(baseApproval, isStreaming);
156153

157-
useEffect(() => {
158-
if (!isStreaming || effectiveApproval !== "needs-approval") return;
159-
160-
const interval = setInterval(() => {
161-
partsRef.current.forEach((part) => {
162-
WaveAIModel.getInstance().toolUseKeepalive(part.data.toolcallid);
163-
});
164-
}, 4000);
165-
166-
return () => clearInterval(interval);
167-
}, [isStreaming, effectiveApproval]);
168-
169154
const handleApprove = () => {
170155
setUserApprovalOverride("user-approved");
171156
parts.forEach((part) => {
@@ -212,8 +197,6 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
212197
const showRestoreModal = restoreModalToolCallId === toolData.toolcallid;
213198
const highlightTimeoutRef = useRef<NodeJS.Timeout | null>(null);
214199
const highlightedBlockIdRef = useRef<string | null>(null);
215-
const toolCallIdRef = useRef(toolData.toolcallid);
216-
toolCallIdRef.current = toolData.toolcallid;
217200

218201
const statusIcon = toolData.status === "completed" ? "✓" : toolData.status === "error" ? "✗" : "•";
219202
const statusColor =
@@ -224,16 +207,6 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
224207

225208
const isFileWriteTool = toolData.toolname === "write_text_file" || toolData.toolname === "edit_text_file";
226209

227-
useEffect(() => {
228-
if (!isStreaming || effectiveApproval !== "needs-approval") return;
229-
230-
const interval = setInterval(() => {
231-
WaveAIModel.getInstance().toolUseKeepalive(toolCallIdRef.current);
232-
}, 4000);
233-
234-
return () => clearInterval(interval);
235-
}, [isStreaming, effectiveApproval]);
236-
237210
useEffect(() => {
238211
return () => {
239212
if (highlightTimeoutRef.current) {

frontend/app/store/global.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,16 @@ function useBlockDataLoaded(blockId: string): boolean {
458458
return useAtomValue(loadedAtom);
459459
}
460460

461+
/**
462+
* Safely read an atom value, returning null if the atom is null.
463+
*/
464+
function readAtom<T>(atom: Atom<T>): T {
465+
if (atom == null) {
466+
return null;
467+
}
468+
return globalStore.get(atom);
469+
}
470+
461471
/**
462472
* Get the preload api.
463473
*/
@@ -863,6 +873,7 @@ export {
863873
getUserName,
864874
globalPrimaryTabStartup,
865875
globalStore,
876+
readAtom,
866877
initGlobal,
867878
initGlobalWaveEventSubs,
868879
isDev,

0 commit comments

Comments
 (0)