From bef3b3c91342f05f4b53d814eef14bbf84927905 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:42:56 +0000 Subject: [PATCH 1/7] Initial plan From b24ac2ec9577d41a918cb5dec61dcb857813f910 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:49:05 +0000 Subject: [PATCH 2/7] Plan terminal Claude icon update Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99c2a025b4..ce697130cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.14.2-beta.1", + "version": "0.14.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.14.2-beta.1", + "version": "0.14.2", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ From a07d08d01f15118565ffc224a40fabd477bbadcb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:57:59 +0000 Subject: [PATCH 3/7] Add Claude icon for active terminal sessions Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- frontend/app/view/term/osc-handlers.test.ts | 22 + frontend/app/view/term/osc-handlers.ts | 19 +- frontend/app/view/term/term-model.ts | 14 +- frontend/app/view/term/term.tsx | 10 +- frontend/app/view/term/termwrap.ts | 12 +- package-lock.json | 3651 ++++++++++++++++++- package.json | 1 + 7 files changed, 3635 insertions(+), 94 deletions(-) create mode 100644 frontend/app/view/term/osc-handlers.test.ts diff --git a/frontend/app/view/term/osc-handlers.test.ts b/frontend/app/view/term/osc-handlers.test.ts new file mode 100644 index 0000000000..11e6ed387c --- /dev/null +++ b/frontend/app/view/term/osc-handlers.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; + +import { isClaudeCodeCommand } from "./osc-handlers"; + +describe("isClaudeCodeCommand", () => { + it("matches direct Claude Code invocations", () => { + expect(isClaudeCodeCommand("claude")).toBe(true); + expect(isClaudeCodeCommand("claude --dangerously-skip-permissions")).toBe(true); + expect(isClaudeCodeCommand("/usr/local/bin/claude chat")).toBe(true); + }); + + it("matches Claude Code invocations wrapped with env assignments", () => { + expect(isClaudeCodeCommand('ANTHROPIC_API_KEY="test" claude')).toBe(true); + expect(isClaudeCodeCommand("FOO=bar env claude --print")).toBe(true); + }); + + it("ignores other commands", () => { + expect(isClaudeCodeCommand("claudes")).toBe(false); + expect(isClaudeCodeCommand("echo claude")).toBe(false); + expect(isClaudeCodeCommand("")).toBe(false); + }); +}); diff --git a/frontend/app/view/term/osc-handlers.ts b/frontend/app/view/term/osc-handlers.ts index f44659d2c6..d0547c0158 100644 --- a/frontend/app/view/term/osc-handlers.ts +++ b/frontend/app/view/term/osc-handlers.ts @@ -25,6 +25,8 @@ const Osc52MaxRawLength = 128 * 1024; // includes selector + base64 + whitespace // See aiprompts/wave-osc-16162.md for full documentation export type ShellIntegrationStatus = "ready" | "running-command"; +const ClaudeCodeRegex = /(?:^|\/)claude\b/; + type Osc16162Command = | { command: "A"; data: Record } | { command: "C"; data: { cmd64?: string } } @@ -65,8 +67,7 @@ function checkCommandForTelemetry(decodedCmd: string) { return; } - const claudeRegex = /^claude\b/; - if (claudeRegex.test(decodedCmd)) { + if (isClaudeCodeCommand(decodedCmd)) { recordTEvent("action:term", { "action:type": "claude" }); return; } @@ -78,6 +79,14 @@ function checkCommandForTelemetry(decodedCmd: string) { } } +export function isClaudeCodeCommand(decodedCmd: string): boolean { + if (!decodedCmd) { + return false; + } + const normalizedCmd = decodedCmd.trim().replace(/^(?:\w+=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, "").replace(/^env\s+/, ""); + return ClaudeCodeRegex.test(normalizedCmd); +} + function handleShellIntegrationCommandStart( termWrap: TermWrap, blockId: string, @@ -101,16 +110,19 @@ function handleShellIntegrationCommandStart( const decodedCmd = base64ToString(cmd.data.cmd64); rtInfo["shell:lastcmd"] = decodedCmd; globalStore.set(termWrap.lastCommandAtom, decodedCmd); + globalStore.set(termWrap.claudeCodeActiveAtom, isClaudeCodeCommand(decodedCmd)); checkCommandForTelemetry(decodedCmd); } catch (e) { console.error("Error decoding cmd64:", e); rtInfo["shell:lastcmd"] = null; globalStore.set(termWrap.lastCommandAtom, null); + globalStore.set(termWrap.claudeCodeActiveAtom, false); } } } else { rtInfo["shell:lastcmd"] = null; globalStore.set(termWrap.lastCommandAtom, null); + globalStore.set(termWrap.claudeCodeActiveAtom, false); } rtInfo["shell:lastcmdexitcode"] = null; } @@ -287,6 +299,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo case "A": { rtInfo["shell:state"] = "ready"; globalStore.set(termWrap.shellIntegrationStatusAtom, "ready"); + globalStore.set(termWrap.claudeCodeActiveAtom, false); const marker = terminal.registerMarker(0); if (marker) { termWrap.promptMarkers.push(marker); @@ -324,6 +337,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo } break; case "D": + globalStore.set(termWrap.claudeCodeActiveAtom, false); if (cmd.data.exitcode != null) { rtInfo["shell:lastcmdexitcode"] = cmd.data.exitcode; } else { @@ -337,6 +351,7 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo break; case "R": globalStore.set(termWrap.shellIntegrationStatusAtom, null); + globalStore.set(termWrap.claudeCodeActiveAtom, false); if (terminal.buffer.active.type === "alternate") { terminal.write("\x1b[?1049l"); } diff --git a/frontend/app/view/term/term-model.ts b/frontend/app/view/term/term-model.ts index 9cb1c58720..b4bc0d30f2 100644 --- a/frontend/app/view/term/term-model.ts +++ b/frontend/app/view/term/term-model.ts @@ -10,7 +10,7 @@ import { waveEventSubscribeSingle } from "@/app/store/wps"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil"; -import { TerminalView } from "@/app/view/term/term"; +import { TermClaudeIcon, TerminalView } from "@/app/view/term/term"; import { TermWshClient } from "@/app/view/term/term-wsh"; import { VDomModel } from "@/app/view/vdom/vdom-model"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; @@ -397,10 +397,12 @@ export class TermViewModel implements ViewModel { return null; } const shellIntegrationStatus = get(this.termRef.current.shellIntegrationStatusAtom); + const claudeCodeActive = get(this.termRef.current.claudeCodeActiveAtom); + const icon = claudeCodeActive ? React.createElement(TermClaudeIcon) : "sparkles"; if (shellIntegrationStatus == null) { return { elemtype: "iconbutton", - icon: "sparkles", + icon, className: "text-muted", title: "No shell integration — Wave AI unable to run commands.", noAction: true, @@ -409,14 +411,16 @@ export class TermViewModel implements ViewModel { if (shellIntegrationStatus === "ready") { return { elemtype: "iconbutton", - icon: "sparkles", + icon, className: "text-accent", title: "Shell ready — Wave AI can run commands in this terminal.", noAction: true, }; } if (shellIntegrationStatus === "running-command") { - let title = "Shell busy — Wave AI unable to run commands while another command is running."; + let title = claudeCodeActive + ? "Claude Code running — Wave AI unable to run commands while Claude Code is active." + : "Shell busy — Wave AI unable to run commands while another command is running."; if (this.termRef.current) { const inAltBuffer = this.termRef.current.terminal?.buffer?.active?.type === "alternate"; @@ -429,7 +433,7 @@ export class TermViewModel implements ViewModel { return { elemtype: "iconbutton", - icon: "sparkles", + icon, className: "text-warning", title: title, noAction: true, diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index b167688907..0a6596d4ff 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -1,4 +1,4 @@ -// Copyright 2025, Command Line Inc. +// Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { SubBlock } from "@/app/block/block"; @@ -14,6 +14,7 @@ import type { TermViewModel } from "@/app/view/term/term-model"; import { atoms, getOverrideConfigAtom, getSettingsPrefixAtom, globalStore, WOS } from "@/store/global"; import { fireAndForget, useAtomValueSafe } from "@/util/util"; import { computeBgStyleFromMeta } from "@/util/waveutil"; +import { Claude } from "@lobehub/icons"; import { ISearchOptions } from "@xterm/addon-search"; import clsx from "clsx"; import debug from "debug"; @@ -33,6 +34,12 @@ interface TerminalViewProps { model: TermViewModel; } +const TermClaudeIcon = React.memo(() => { + return