Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aiprompts/wave-osc-16162.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Sends shell metadata information (typically only once at shell initialization).
shell?: string; // Shell name (e.g., "zsh", "bash")
shellversion?: string; // Version string of the shell
uname?: string; // Output of "uname -smr" (e.g., "Darwin 23.0.0 arm64")
integration?: boolean; // Whether shell integration is active (true) or disabled (false)
}
```

Expand Down
12 changes: 0 additions & 12 deletions cmd/wsh/cmd/wshcmd-ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,6 @@ func sshRun(cmd *cobra.Command, args []string) (rtnErr error) {
if err != nil {
return fmt.Errorf("setting connection in block: %w", err)
}

// Clear the cmd:hascurcwd rtinfo field
rtInfoData := wshrpc.CommandSetRTInfoData{
ORef: waveobj.MakeORef(waveobj.OType_Block, blockId),
Data: map[string]any{
"cmd:hascurcwd": nil,
},
}
err = wshclient.SetRTInfoCommand(RpcClient, rtInfoData, nil)
if err != nil {
return fmt.Errorf("setting RTInfo in block: %w", err)
}
WriteStderr("switched connection to %q\n", sshArg)
return nil
}
8 changes: 0 additions & 8 deletions frontend/app/modals/conntypeahead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,6 @@ const ChangeConnectionBlockModal = React.memo(
meta: { connection: connName, file: newFile, "cmd:cwd": null },
});

const rtInfo = { "cmd:hascurcwd": null };
const rtInfoData: CommandSetRTInfoData = {
oref: WOS.makeORef("block", blockId),
data: rtInfo
};
RpcApi.SetRTInfoCommand(TabRpcClient, rtInfoData).catch((e) =>
console.log("error setting RT info", e)
);
try {
await RpcApi.ConnEnsureCommand(
TabRpcClient,
Expand Down
15 changes: 13 additions & 2 deletions frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ function handleOsc7Command(data: string, blockId: string, loaded: boolean): bool
// Strip leading slash and normalize to forward slashes
pathPart = pathPart.substring(1).replace(/\\/g, "/");
}

// Handle UNC paths (e.g., /\\server\share)
if (pathPart.startsWith("/\\\\")) {
// Strip leading slash but keep backslashes for UNC
pathPart = pathPart.substring(1);
}
} catch (e) {
console.log("Invalid OSC 7 command received (parse error)", data, e);
return true;
Expand All @@ -152,7 +158,7 @@ function handleOsc7Command(data: string, blockId: string, loaded: boolean): bool
"cmd:cwd": pathPart,
});

const rtInfo = { "cmd:hascurcwd": true };
const rtInfo = { "shell:hascurcwd": true };
const rtInfoData: CommandSetRTInfoData = {
oref: WOS.makeORef("block", blockId),
data: rtInfo,
Expand All @@ -170,7 +176,7 @@ function handleOsc7Command(data: string, blockId: string, loaded: boolean): bool
type Osc16162Command =
| { command: "A"; data: {} }
| { command: "C"; data: { cmd64?: string } }
| { command: "M"; data: { shell?: string; shellversion?: string; uname?: string } }
| { command: "M"; data: { shell?: string; shellversion?: string; uname?: string; integration?: boolean } }
| { command: "D"; data: { exitcode?: number } }
| { command: "I"; data: { inputempty?: boolean } }
| { command: "R"; data: {} };
Expand Down Expand Up @@ -219,6 +225,8 @@ function handleOsc16162Command(data: string, blockId: string, loaded: boolean, t
} else {
rtInfo["shell:lastcmd"] = null;
}
// also clear lastcmdexitcode (since we've now started a new command)
rtInfo["shell:lastcmdexitcode"] = null;
break;
case "M":
if (cmd.data.shell) {
Expand All @@ -230,6 +238,9 @@ function handleOsc16162Command(data: string, blockId: string, loaded: boolean, t
if (cmd.data.uname) {
rtInfo["shell:uname"] = cmd.data.uname;
}
if (cmd.data.integration != null) {
rtInfo["shell:integration"] = cmd.data.integration;
}
break;
case "D":
if (cmd.data.exitcode != null) {
Expand Down
3 changes: 2 additions & 1 deletion frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,11 +710,12 @@ declare global {
"tsunami:title"?: string;
"tsunami:shortdesc"?: string;
"tsunami:schemas"?: any;
"cmd:hascurcwd"?: boolean;
"shell:hascurcwd"?: boolean;
"shell:state"?: string;
"shell:type"?: string;
"shell:version"?: string;
"shell:uname"?: string;
"shell:integration"?: boolean;
"shell:inputempty"?: boolean;
"shell:lastcmd"?: string;
"shell:lastcmdexitcode"?: number;
Expand Down
79 changes: 59 additions & 20 deletions pkg/aiusechat/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,67 @@ import (
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wstore"
)

func makeTerminalBlockDesc(block *waveobj.Block) string {
connection, hasConnection := block.Meta["connection"].(string)
cwd, hasCwd := block.Meta["cmd:cwd"].(string)
Comment on lines +21 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add defensive nil checks for robustness.

While MakeBlockShortDesc validates inputs before calling this helper, makeTerminalBlockDesc is a standalone function that could be invoked from other contexts within the package. Adding nil checks would prevent potential panics.

Apply this diff to add defensive checks:

 func makeTerminalBlockDesc(block *waveobj.Block) string {
+	if block == nil || block.Meta == nil {
+		return "terminal block (metadata unavailable)"
+	}
 	connection, hasConnection := block.Meta["connection"].(string)
 	cwd, hasCwd := block.Meta["cmd:cwd"].(string)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func makeTerminalBlockDesc(block *waveobj.Block) string {
connection, hasConnection := block.Meta["connection"].(string)
cwd, hasCwd := block.Meta["cmd:cwd"].(string)
func makeTerminalBlockDesc(block *waveobj.Block) string {
if block == nil || block.Meta == nil {
return "terminal block (metadata unavailable)"
}
connection, hasConnection := block.Meta["connection"].(string)
cwd, hasCwd := block.Meta["cmd:cwd"].(string)
🤖 Prompt for AI Agents
In pkg/aiusechat/tools.go around lines 21 to 23, add defensive nil checks so the
helper never panics when called with unexpected input: return an empty string
immediately if block is nil or if block.Meta is nil, then proceed to extract
"connection" and "cmd:cwd" using the existing comma-ok type assertions and fall
back to empty strings when keys or type assertions fail; ensure the function
always returns a valid string without dereferencing nil maps or values.


blockORef := waveobj.MakeORef(waveobj.OType_Block, block.OID)
rtInfo := wstore.GetRTInfo(blockORef)
hasCurCwd := rtInfo != nil && rtInfo.ShellHasCurCwd

var desc string
if hasConnection && connection != "" {
desc = fmt.Sprintf("CLI terminal connected to %q", connection)
} else {
desc = "local CLI terminal"
}

if rtInfo != nil && rtInfo.ShellType != "" {
desc += fmt.Sprintf(" (%s", rtInfo.ShellType)
if rtInfo.ShellVersion != "" {
desc += fmt.Sprintf(" %s", rtInfo.ShellVersion)
}
desc += ")"
}

if rtInfo != nil {
if rtInfo.ShellIntegration {
var stateStr string
switch rtInfo.ShellState {
case "ready":
stateStr = "waiting for input"
case "running-command":
stateStr = "running command"
if rtInfo.ShellLastCmd != "" {
cmdStr := rtInfo.ShellLastCmd
if len(cmdStr) > 30 {
cmdStr = cmdStr[:27] + "..."
}
cmdJSON := utilfn.MarshalJSONString(cmdStr)
stateStr = fmt.Sprintf("running command %s", cmdJSON)
}
default:
stateStr = "state unknown"
}
desc += fmt.Sprintf(", %s", stateStr)
} else {
desc += ", no shell integration"
}
}

if hasCurCwd && hasCwd && cwd != "" {
desc += fmt.Sprintf(", in directory %q", cwd)
}

return desc
}

func MakeBlockShortDesc(block *waveobj.Block) string {
if block.Meta == nil {
return ""
Expand All @@ -29,25 +85,7 @@ func MakeBlockShortDesc(block *waveobj.Block) string {

switch viewType {
case "term":
connection, hasConnection := block.Meta["connection"].(string)
cwd, hasCwd := block.Meta["cmd:cwd"].(string)

blockORef := waveobj.MakeORef(waveobj.OType_Block, block.OID)
rtInfo := wstore.GetRTInfo(blockORef)
hasCurCwd := rtInfo != nil && rtInfo.CmdHasCurCwd

var desc string
if hasConnection && connection != "" {
desc = fmt.Sprintf("CLI terminal on %q", connection)
} else {
desc = "local CLI terminal"
}

if hasCurCwd && hasCwd && cwd != "" {
desc += fmt.Sprintf(" in directory %q", cwd)
}

return desc
return makeTerminalBlockDesc(block)
case "preview":
file, hasFile := block.Meta["file"].(string)
connection, hasConnection := block.Meta["connection"].(string)
Expand Down Expand Up @@ -111,6 +149,8 @@ func GenerateTabStateAndTools(ctx context.Context, tabid string, widgetAccess bo
}
}
tabState := GenerateCurrentTabStatePrompt(blocks, widgetAccess)
// for debugging
// log.Printf("TABPROMPT %s\n", tabState)
var tools []uctypes.ToolDefinition
if widgetAccess {
tools = append(tools, GetCaptureScreenshotToolDefinition(tabid))
Expand Down Expand Up @@ -176,7 +216,6 @@ func GenerateCurrentTabStatePrompt(blocks []*waveobj.Block, widgetAccess bool) s
}
prompt.WriteString("</current_tab_state>")
rtn := prompt.String()
// log.Printf("%s\n", rtn)
return rtn
}

Expand Down
46 changes: 37 additions & 9 deletions pkg/aiusechat/tools_term.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
"time"

"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wcore"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
"github.com/wavetermdev/waveterm/pkg/wshutil"
"github.com/wavetermdev/waveterm/pkg/wstore"
)

type TermGetScrollbackToolInput struct {
Expand All @@ -23,15 +25,22 @@ type TermGetScrollbackToolInput struct {
Count int `json:"count,omitempty"`
}

type CommandInfo struct {
Command string `json:"command"`
Status string `json:"status"`
ExitCode *int `json:"exitcode,omitempty"`
}

type TermGetScrollbackToolOutput struct {
TotalLines int `json:"total_lines"`
LineStart int `json:"line_start"`
LineEnd int `json:"line_end"`
ReturnedLines int `json:"returned_lines"`
Content string `json:"content"`
SinceLastOutputSec *int `json:"since_last_output_sec,omitempty"`
HasMore bool `json:"has_more"`
NextStart *int `json:"next_start"`
TotalLines int `json:"totallines"`
LineStart int `json:"linestart"`
LineEnd int `json:"lineend"`
ReturnedLines int `json:"returnedlines"`
Content string `json:"content"`
SinceLastOutputSec *int `json:"sincelastoutputsec,omitempty"`
HasMore bool `json:"hasmore"`
NextStart *int `json:"nextstart"`
LastCommand *CommandInfo `json:"lastcommand,omitempty"`
}

func parseTermGetScrollbackInput(input any) (*TermGetScrollbackToolInput, error) {
Expand Down Expand Up @@ -76,7 +85,7 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "term_get_scrollback",
DisplayName: "Get Terminal Scrollback",
Description: "Fetch terminal scrollback from a widget as plain text. Index 0 is the most recent line; indices increase going upward (older lines).",
Description: "Fetch terminal scrollback from a widget as plain text. Index 0 is the most recent line; indices increase going upward (older lines). Also returns last command and exit code if shell integration is enabled.",
ToolLogName: "term:getscrollback",
InputSchema: map[string]any{
"type": "object",
Expand Down Expand Up @@ -155,6 +164,24 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition {
nextStart = &effectiveLineEnd
}

blockORef := waveobj.MakeORef(waveobj.OType_Block, fullBlockId)
rtInfo := wstore.GetRTInfo(blockORef)

var lastCommand *CommandInfo
if rtInfo != nil && rtInfo.ShellIntegration && rtInfo.ShellLastCmd != "" {
cmdInfo := &CommandInfo{
Command: rtInfo.ShellLastCmd,
}
if rtInfo.ShellState == "running-command" {
cmdInfo.Status = "running"
} else if rtInfo.ShellState == "ready" {
cmdInfo.Status = "completed"
exitCode := rtInfo.ShellLastCmdExitCode
cmdInfo.ExitCode = &exitCode
}
lastCommand = cmdInfo
}

return &TermGetScrollbackToolOutput{
TotalLines: result.TotalLines,
LineStart: result.LineStart,
Expand All @@ -164,6 +191,7 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition {
SinceLastOutputSec: sinceLastOutputSec,
HasMore: hasMore,
NextStart: nextStart,
LastCommand: lastCommand,
}, nil
},
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/blockcontroller/blockcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func registerController(blockId string, controller Controller) {

if existingController != nil {
existingController.Stop(false, Status_Done)
wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, blockId))
}
}

Expand Down Expand Up @@ -243,6 +244,7 @@ func StopBlockController(blockId string) {
return
}
controller.Stop(true, Status_Done)
wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, blockId))
}

func StopBlockControllerAndSetStatus(blockId string, newStatus string) {
Expand All @@ -251,6 +253,7 @@ func StopBlockControllerAndSetStatus(blockId string, newStatus string) {
return
}
controller.Stop(true, newStatus)
wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, blockId))
}

func SendInput(blockId string, inputUnion *BlockInputUnion) error {
Expand All @@ -268,6 +271,7 @@ func StopAllBlockControllers() {
if status != nil && status.ShellProcStatus == Status_Running {
go func(id string, c Controller) {
c.Stop(true, Status_Done)
wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, id))
}(blockId, controller)
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/remote/conncontroller/conncontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type SSHConn struct {

var ConnServerCmdTemplate = strings.TrimSpace(
strings.Join([]string{
"%s version 2> /dev/null || (echo -n \"not-installed \"; uname -sm);",
"%s version 2> /dev/null || (echo -n \"not-installed \"; uname -sm; exit 0);",
"exec %s connserver",
}, "\n"))

Expand Down
Loading
Loading