Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
16072c1
Add Linux quickdev task (#3521)
sawka Sep 25, 2026
bf4beb8
fix(wcore): add ListAllWorkspaces so wsh CLI sees unsaved/scratch wor…
andrewinalaska Sep 25, 2026
11128da
Bump js-yaml from 3.14.1 to 3.15.2 (#3504)
dependabot[bot] Sep 25, 2026
3e2f57e
Bump fast-uri from 3.1.4 to 3.1.7 (#3491)
dependabot[bot] Sep 25, 2026
0a2c437
fix: preserve reasoning_content for DeepSeek chat completions (#3290)
lizzielizzielizzie Sep 25, 2026
535ad36
Bump image-size from 2.0.2 to 2.0.4 (#3519)
dependabot[bot] Sep 25, 2026
b713d17
Bump joi from 17.13.3 to 17.13.7 (#3503)
dependabot[bot] Sep 25, 2026
c11ec2f
Bump fflate from 0.7.4 to 0.7.5 (#3494)
dependabot[bot] Sep 25, 2026
eab06fa
Bump @humanfs/node from 0.16.7 to 0.16.8 (#3493)
dependabot[bot] Sep 25, 2026
57a4ab3
Bump @xmldom/xmldom from 0.8.13 to 0.8.15 (#3492)
dependabot[bot] Sep 25, 2026
ef4ec56
Bump browserslist from 4.28.1 to 4.28.8 (#3489)
dependabot[bot] Sep 25, 2026
6ae57d2
Bump google.golang.org/grpc from 1.82.1 to 1.83.1 (#3488)
dependabot[bot] Sep 25, 2026
f7d1a80
Bump nanoid from 3.3.11 to 3.3.18 (#3473)
dependabot[bot] Sep 25, 2026
a7e69c4
Bump google.golang.org/grpc from 1.83.1 to 1.83.2 (#3523)
dependabot[bot] Sep 25, 2026
ed0d0a6
Bump brace-expansion (#3470)
dependabot[bot] Sep 25, 2026
1a923fc
Bump ip-address from 10.2.0 to 10.4.0 (#3461)
dependabot[bot] Sep 25, 2026
d9dbbbf
Bump vite from 6.4.2 to 6.4.3 (#3379)
dependabot[bot] Sep 25, 2026
fd3e444
Bump golang.org/x/crypto from 0.55.0 to 0.57.0 (#3398)
dependabot[bot] Sep 25, 2026
cf19309
fix: declare sharp for image optimization (#3524)
sawka Sep 25, 2026
215171d
build(deps): bump lodash from 4.17.23 to 4.18.1 (#3180)
dependabot[bot] Sep 25, 2026
c58bf7f
Fix OSC 8 links using Wave terminal web-link handling (#3528)
sawka Sep 25, 2026
6ed5bd9
Merge remote-tracking branch 'upstream/main' into sync/upstream-2026-…
Atreus-X Sep 28, 2026
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
31 changes: 31 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ tasks:
WCLOUD_ENDPOINT: "https://api-dev.waveterm.dev/central"
WCLOUD_WS_ENDPOINT: "wss://wsapi-dev.waveterm.dev/"

electron:linuxquickdev:
desc: Run the Electron application via the Vite dev server (quick dev - Linux amd64 only, no generate, no wsh).
cmd: npm run dev
deps:
- npm:install
- build:backend:quickdev:linux
env:
WAVETERM_ENVFILE: "{{.ROOT_DIR}}/.env"
WCLOUD_PING_ENDPOINT: "https://ping-dev.waveterm.dev/central"
WCLOUD_ENDPOINT: "https://api-dev.waveterm.dev/central"
WCLOUD_WS_ENDPOINT: "wss://wsapi-dev.waveterm.dev/"
WAVETERM_NOCONFIRMQUIT: "1"

docs:npm:install:
desc: Runs `npm install` in docs directory
internal: true
Expand Down Expand Up @@ -237,6 +250,24 @@ tasks:
generates:
- dist/bin/wavesrv.x64.exe

build:backend:quickdev:linux:
desc: Build only the wavesrv component for quickdev (Linux amd64 only, no generate, no wsh).
platforms: [linux/amd64]
cmds:
- task: build:server:internal
vars:
ARCHS: amd64
deps:
- go:mod:tidy
sources:
- "cmd/server/*.go"
- "pkg/**/*.go"
- "pkg/**/*.json"
- "pkg/**/*.sh"
- "tsunami/**/*.go"
generates:
- dist/bin/wavesrv.x64

build:server:windows:
desc: Build the wavesrv component for Windows platforms (only generates artifacts for the current architecture).
platforms: [windows]
Expand Down
2 changes: 1 addition & 1 deletion cmd/wsh/cmd/wshcmd-blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func blocksListRun(cmd *cobra.Command, args []string) error {

var allBlocks []BlockDetails

workspaces, err := wshclient.WorkspaceListCommand(RpcClient, &wshrpc.RpcOpts{Timeout: int64(blocksTimeout)})
workspaces, err := wshclient.WorkspaceListAllCommand(RpcClient, &wshrpc.RpcOpts{Timeout: int64(blocksTimeout)})
if err != nil {
return fmt.Errorf("failed to list workspaces: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/wsh/cmd/wshcmd-workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var workspaceListCommand = &cobra.Command{
}

func workspaceListRun(cmd *cobra.Command, args []string) {
workspaces, err := wshclient.WorkspaceListCommand(RpcClient, &wshrpc.RpcOpts{Timeout: 2000})
workspaces, err := wshclient.WorkspaceListAllCommand(RpcClient, &wshrpc.RpcOpts{Timeout: 2000})
if err != nil {
WriteStderr("Unable to list workspaces: %v\n", err)
return
Expand Down
6 changes: 6 additions & 0 deletions frontend/app/store/wshclientapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,12 @@ export class RpcApiType {
return client.wshRpcCall("workspacelist", null, opts);
}

// command "workspacelistall" [call]
WorkspaceListAllCommand(client: WshClient, opts?: RpcOpts): Promise<WorkspaceInfoData[]> {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "workspacelistall", null, opts);
return client.wshRpcCall("workspacelistall", null, opts);
}

// command "writeappfile" [call]
WriteAppFileCommand(client: WshClient, data: CommandWriteAppFileData, opts?: RpcOpts): Promise<void> {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "writeappfile", data, opts);
Expand Down
44 changes: 44 additions & 0 deletions frontend/app/view/term/term-links.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";
import { makeTermLinkHandlers } from "./term-links";

function linkEvent(metaKey = false, ctrlKey = false): MouseEvent {
return { metaKey, ctrlKey, preventDefault: vi.fn(), clientX: 12, clientY: 34 } as unknown as MouseEvent;
}

describe("terminal link handlers", () => {
it.each([
{ isMacOS: true, modifier: "metaKey" },
{ isMacOS: false, modifier: "ctrlKey" },
])("only opens $modifier-clicked links on the corresponding platform", ({ isMacOS }) => {
const openUri = vi.fn();
const handlers = makeTermLinkHandlers(isMacOS, openUri, vi.fn());
const plainClick = linkEvent();
handlers.activate(plainClick, "https://example.com/first");
expect(plainClick.preventDefault).toHaveBeenCalledOnce();
expect(openUri).not.toHaveBeenCalled();

const wrongModifier = linkEvent(!isMacOS, isMacOS);
handlers.activate(wrongModifier, "https://example.com/second");
expect(openUri).not.toHaveBeenCalled();

const rightModifier = linkEvent(isMacOS, !isMacOS);
handlers.activate(rightModifier, "https://example.com/third");
expect(openUri).toHaveBeenCalledExactlyOnceWith("https://example.com/third");
});

it("shows the destination for OSC 8 links but keeps plain URL hover unchanged", () => {
const onHover = vi.fn();
const handlers = makeTermLinkHandlers(false, vi.fn(), onHover);
handlers.hover(linkEvent(), "https://example.com/visible");
handlers.osc8Hover(linkEvent(), "https://example.com/hidden");
handlers.leave();
expect(onHover.mock.calls).toEqual([
["https://example.com/visible", 12, 34, false],
["https://example.com/hidden", 12, 34, true],
[null, 0, 0, false],
]);
});
});
21 changes: 21 additions & 0 deletions frontend/app/view/term/term-links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

export function makeTermLinkHandlers(
isMacOS: boolean,
openUri: (uri: string) => void,
onHover: (uri: string | null, x: number, y: number, showUrl: boolean) => void
) {
return {
activate: (event: MouseEvent, uri: string) => {
event.preventDefault();
if (!(isMacOS ? event.metaKey : event.ctrlKey)) {
return;
}
openUri(uri);
},
hover: (event: MouseEvent, uri: string) => onHover(uri, event.clientX, event.clientY, false),
osc8Hover: (event: MouseEvent, uri: string) => onHover(uri, event.clientX, event.clientY, true),
leave: () => onHover(null, 0, 0, false),
};
}
22 changes: 22 additions & 0 deletions frontend/app/view/term/term-tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { TermLinkTooltipContent } from "./term-tooltip";

describe("TermLinkTooltipContent", () => {
it("keeps the existing modifier-click hint for plain URLs", () => {
const markup = renderToStaticMarkup(<TermLinkTooltipContent />);
expect(markup).toContain("-click to open link");
expect(markup).not.toContain("break-all");
});

it("shows and safely escapes the destination for OSC 8 links", () => {
const markup = renderToStaticMarkup(<TermLinkTooltipContent url="https://example.com/?next=<script>&x=1" />);
expect(markup).toContain("-click to open link");
expect(markup).toContain("https://example.com/?next=&lt;script&gt;&amp;x=1");
expect(markup).toContain("break-all");
expect(markup).not.toContain("<script>");
});
});
20 changes: 16 additions & 4 deletions frontend/app/view/term/term-tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ const HoverDelayMs = 600;
const MaxHoverTimeMs = 2200;
const modKey = PLATFORM === PlatformMacOS ? "Cmd" : "Ctrl";

export function TermLinkTooltipContent({ url }: { url?: string }) {
if (!url) {
return <span>{modKey}-click to open link</span>;
}
return (
<div className="max-w-[min(28rem,80vw)]">
<div>{modKey}-click to open link</div>
<div className="break-all">{url}</div>
</div>
);
}

interface TermLinkTooltipProps {
/**
* The live TermWrap instance. Pass the instance directly (not a ref) so
Expand All @@ -91,7 +103,7 @@ interface TermLinkTooltipProps {
* prevents unnecessary re-renders of the parent TerminalView.
*/
export const TermLinkTooltip = React.memo(function TermLinkTooltip({ termWrap }: TermLinkTooltipProps) {
const [mousePos, setMousePos] = React.useState<{ x: number; y: number } | null>(null);
const [mousePos, setMousePos] = React.useState<{ x: number; y: number; url?: string } | null>(null);
const timeoutRef = React.useRef<number | null>(null);
const maxTimeoutRef = React.useRef<number | null>(null);

Expand All @@ -100,7 +112,7 @@ export const TermLinkTooltip = React.memo(function TermLinkTooltip({ termWrap }:
return;
}

termWrap.onLinkHover = (uri: string | null, mouseX: number, mouseY: number) => {
termWrap.onLinkHover = (uri: string | null, mouseX: number, mouseY: number, showUrl: boolean) => {
clearTimeoutRef(timeoutRef);

if (uri == null) {
Expand All @@ -112,7 +124,7 @@ export const TermLinkTooltip = React.memo(function TermLinkTooltip({ termWrap }:
// Show after a short delay so fast mouse movements don't flicker.
timeoutRef.current = window.setTimeout(() => {
timeoutRef.current = null;
setMousePos({ x: mouseX, y: mouseY });
setMousePos({ x: mouseX, y: mouseY, url: showUrl ? uri : undefined });
// Auto-dismiss after MaxHoverTimeMs so the tooltip doesn't linger forever.
clearTimeoutRef(maxTimeoutRef);
maxTimeoutRef.current = window.setTimeout(() => {
Expand All @@ -130,5 +142,5 @@ export const TermLinkTooltip = React.memo(function TermLinkTooltip({ termWrap }:
};
}, [termWrap]);

return <TermTooltip mousePos={mousePos} content={<span>{modKey}-click to open link</span>} />;
return <TermTooltip mousePos={mousePos} content={<TermLinkTooltipContent url={mousePos?.url} />} />;
});
48 changes: 19 additions & 29 deletions frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
isClaudeCodeCommand,
type ShellIntegrationStatus,
} from "./osc-handlers";
import { makeTermLinkHandlers } from "./term-links";
import {
bufferLinesToText,
createTempFileFromBlob,
Expand Down Expand Up @@ -104,7 +105,7 @@ export class TermWrap {
claudeCodeActiveAtom: jotai.PrimitiveAtom<boolean>;
nodeModel: BlockNodeModel; // this can be null
hoveredLinkUri: string | null = null;
onLinkHover?: (uri: string | null, mouseX: number, mouseY: number) => void;
onLinkHover?: (uri: string | null, mouseX: number, mouseY: number, showUrl: boolean) => void;

// Paste deduplication
// xterm.js paste() method triggers onData event, which can cause duplicate sends
Expand Down Expand Up @@ -143,41 +144,30 @@ export class TermWrap {
this.lastCommandAtom = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
this.claudeCodeActiveAtom = jotai.atom(false);
this.webglEnabledAtom = jotai.atom(false) as jotai.PrimitiveAtom<boolean>;
this.terminal = new Terminal(options);
const linkHandlers = makeTermLinkHandlers(
PLATFORM === PlatformMacOS,
(uri) => fireAndForget(() => openLink(uri)),
(uri, x, y, showUrl) => {
this.hoveredLinkUri = uri;
this.onLinkHover?.(uri, x, y, showUrl);
}
);
this.terminal = new Terminal({
...options,
linkHandler: {
activate: linkHandlers.activate,
hover: linkHandlers.osc8Hover,
leave: linkHandlers.leave,
},
});
this.fitAddon = new FitAddon();
this.serializeAddon = new SerializeAddon();
this.searchAddon = new SearchAddon();
this.terminal.loadAddon(this.searchAddon);
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.serializeAddon);
this.terminal.loadAddon(
new WebLinksAddon(
(e, uri) => {
e.preventDefault();
switch (PLATFORM) {
case PlatformMacOS:
if (e.metaKey) {
fireAndForget(() => openLink(uri));
}
break;
default:
if (e.ctrlKey) {
fireAndForget(() => openLink(uri));
}
break;
}
},
{
hover: (e, uri) => {
this.hoveredLinkUri = uri;
this.onLinkHover?.(uri, e.clientX, e.clientY);
},
leave: () => {
this.hoveredLinkUri = null;
this.onLinkHover?.(null, 0, 0);
},
}
)
new WebLinksAddon(linkHandlers.activate, { hover: linkHandlers.hover, leave: linkHandlers.leave })
);
this.setTermRenderer(WebGLSupported && waveOptions.useWebGl ? "webgl" : "dom");
// Register OSC handlers
Expand Down
28 changes: 14 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/wavetermdev/waveterm

go 1.25.6
go 1.26.0

require (
github.com/Microsoft/go-winio v0.6.2
Expand Down Expand Up @@ -32,11 +32,11 @@ require (
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b
github.com/wavetermdev/htmltoken v0.2.0
github.com/wavetermdev/waveterm/tsunami v0.12.3
golang.org/x/crypto v0.54.0
golang.org/x/mod v0.37.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
golang.org/x/term v0.45.0
golang.org/x/crypto v0.57.0
golang.org/x/mod v0.41.0
golang.org/x/sync v0.23.0
golang.org/x/sys v0.48.0
golang.org/x/term v0.46.0
google.golang.org/api v0.277.0
)

Expand Down Expand Up @@ -74,16 +74,16 @@ require (
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
golang.org/x/net v0.56.0 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/grpc v1.83.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Expand Down
Loading
Loading